@capillarytech/creatives-library 8.0.316-alpha.4 → 8.0.317-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 +1 -0
- package/package.json +1 -1
- package/services/api.js +6 -0
- package/services/tests/api.test.js +7 -0
- package/utils/common.js +6 -1
- package/utils/tests/tagValidations.test.js +34 -0
- package/v2Components/CapTagList/index.js +15 -22
- package/v2Components/CapTagList/style.scss +48 -0
- package/v2Components/CapTagListWithInput/__tests__/CapTagListWithInput.test.js +63 -0
- package/v2Components/CapTagListWithInput/index.js +4 -0
- package/v2Components/CapWhatsappCTA/index.js +2 -0
- package/v2Components/CommonTestAndPreview/tests/CustomValuesEditor.test.js +180 -0
- package/v2Components/CommonTestAndPreview/tests/DeliverySettings/utils/parseSenderDetailsResponse.test.js +96 -0
- package/v2Components/CommonTestAndPreview/tests/UnifiedPreview/index.test.js +99 -0
- package/v2Components/CommonTestAndPreview/tests/index.test.js +113 -3
- package/v2Components/FormBuilder/index.js +7 -0
- package/v2Components/HtmlEditor/HTMLEditor.js +6 -1
- package/v2Components/HtmlEditor/__tests__/HTMLEditor.apiErrors.test.js +1 -0
- package/v2Components/HtmlEditor/__tests__/HTMLEditor.test.js +927 -2
- package/v2Components/HtmlEditor/components/CodeEditorPane/index.js +3 -0
- package/v2Components/SmsFallback/tests/useLocalTemplateList.test.js +95 -0
- package/v2Containers/BeeEditor/index.js +3 -0
- package/v2Containers/CommunicationFlow/CommunicationFlow.js +291 -0
- package/v2Containers/CommunicationFlow/CommunicationFlow.scss +25 -0
- package/v2Containers/CommunicationFlow/Tests/CommunicationFlow.test.js +255 -0
- package/v2Containers/CommunicationFlow/constants.js +200 -0
- package/v2Containers/CommunicationFlow/index.js +102 -0
- package/v2Containers/CommunicationFlow/messages.js +346 -0
- package/v2Containers/CommunicationFlow/steps/ChannelSelectionStep/ChannelSelectionStep.js +522 -0
- package/v2Containers/CommunicationFlow/steps/ChannelSelectionStep/ChannelSelectionStep.scss +170 -0
- package/v2Containers/CommunicationFlow/steps/ChannelSelectionStep/Tests/ChannelSelectionStep.test.js +796 -0
- package/v2Containers/CommunicationFlow/steps/ChannelSelectionStep/index.js +5 -0
- package/v2Containers/CommunicationFlow/steps/CommunicationStrategyStep/CommunicationStrategyStep.js +95 -0
- package/v2Containers/CommunicationFlow/steps/CommunicationStrategyStep/Tests/CommunicationStrategyStep.test.js +133 -0
- package/v2Containers/CommunicationFlow/steps/CommunicationStrategyStep/index.js +5 -0
- package/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/DeliverySettingsSection.js +289 -0
- package/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/DeliverySettingsSection.scss +70 -0
- package/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/SenderDetails.js +319 -0
- package/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/SenderDetails.scss +69 -0
- package/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/Tests/DeliverySettingsSection.test.js +616 -0
- package/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/Tests/SenderDetails.test.js +577 -0
- package/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/Tests/deliverySettingsConfig.test.js +1111 -0
- package/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/deliverySettingsConfig.js +696 -0
- package/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/index.js +7 -0
- package/v2Containers/CommunicationFlow/steps/DynamicControlsStep/DynamicControlsStep.js +102 -0
- package/v2Containers/CommunicationFlow/steps/DynamicControlsStep/DynamicControlsStep.scss +36 -0
- package/v2Containers/CommunicationFlow/steps/DynamicControlsStep/Tests/DynamicControlsStep.test.js +91 -0
- package/v2Containers/CommunicationFlow/steps/DynamicControlsStep/index.js +5 -0
- package/v2Containers/CommunicationFlow/steps/MessageTypeStep/MessageTypeStep.js +86 -0
- package/v2Containers/CommunicationFlow/steps/MessageTypeStep/Tests/MessageTypeStep.test.js +100 -0
- package/v2Containers/CommunicationFlow/steps/MessageTypeStep/index.js +5 -0
- package/v2Containers/CommunicationFlow/utils/getEnabledSteps.js +30 -0
- package/v2Containers/CreativesContainer/SlideBoxContent.js +28 -1
- package/v2Containers/CreativesContainer/constants.js +3 -0
- package/v2Containers/CreativesContainer/index.js +3 -0
- package/v2Containers/CreativesContainer/tests/SlideBoxFooter.test.js +104 -0
- package/v2Containers/CreativesContainer/tests/SlideBoxHeader.test.js +110 -0
- package/v2Containers/CreativesContainer/tests/__snapshots__/SlideBoxHeader.test.js.snap +363 -0
- package/v2Containers/CreativesContainer/tests/embeddedSlideboxUtils.test.js +258 -0
- package/v2Containers/Email/index.js +1 -0
- package/v2Containers/EmailWrapper/components/EmailHTMLEditor.js +7 -1
- package/v2Containers/EmailWrapper/components/EmailWrapperView.js +3 -0
- package/v2Containers/EmailWrapper/components/__tests__/EmailHTMLEditor.test.js +20 -2
- package/v2Containers/EmailWrapper/components/__tests__/EmailWrapperView.test.js +16 -1
- package/v2Containers/EmailWrapper/hooks/useEmailWrapper.js +3 -0
- package/v2Containers/EmailWrapper/index.js +4 -0
- package/v2Containers/EmailWrapper/tests/useEmailWrapper.edgeCases.test.js +1 -0
- package/v2Containers/EmailWrapper/tests/useEmailWrapper.test.js +9 -0
- package/v2Containers/InAppWrapper/hooks/__tests__/useInAppWrapper.test.js +19 -0
- package/v2Containers/InAppWrapper/hooks/useInAppWrapper.js +3 -0
- package/v2Containers/InAppWrapper/index.js +3 -0
- package/v2Containers/MobilePush/Create/index.js +2 -0
- package/v2Containers/MobilePush/Edit/index.js +2 -0
- package/v2Containers/MobilepushWrapper/index.js +3 -1
- package/v2Containers/Rcs/index.js +1 -0
- package/v2Containers/Sms/Create/index.js +2 -0
- package/v2Containers/Sms/Edit/index.js +2 -0
- package/v2Containers/Sms/tests/smsFormDataHelpers.test.js +253 -0
- package/v2Containers/SmsTrai/Edit/index.js +2 -0
- package/v2Containers/SmsWrapper/index.js +2 -0
- package/v2Containers/TagList/index.js +41 -2
- package/v2Containers/TagList/messages.js +4 -0
- package/v2Containers/TagList/tests/TagList.test.js +122 -20
- package/v2Containers/TagList/tests/mockdata.js +17 -0
- package/v2Containers/Templates/tests/sagas.test.js +83 -0
- package/v2Containers/Viber/index.js +5 -0
- package/v2Containers/WebPush/Create/hooks/useTagManagement.js +0 -2
- package/v2Containers/WebPush/Create/index.js +9 -1
- package/v2Containers/Whatsapp/index.js +5 -0
- package/v2Containers/Whatsapp/tests/__snapshots__/index.test.js.snap +20 -0
- package/v2Containers/Zalo/index.js +2 -0
|
@@ -1130,6 +1130,7 @@ export class Create extends React.Component { // eslint-disable-line react/prefe
|
|
|
1130
1130
|
onTestContentClicked={this.props.onTestContentClicked}
|
|
1131
1131
|
onPreviewContentClicked={this.props.onPreviewContentClicked}
|
|
1132
1132
|
eventContextTags={this.props?.eventContextTags}
|
|
1133
|
+
waitEventContextTags={this.props?.waitEventContextTags}
|
|
1133
1134
|
tagListGetPopupContainer={this.props.tagListGetPopupContainer}
|
|
1134
1135
|
tagListPopoverOverlayStyle={this.props.tagListPopoverOverlayStyle}
|
|
1135
1136
|
tagListPopoverOverlayClassName={this.props.tagListPopoverOverlayClassName}
|
|
@@ -1170,6 +1171,7 @@ Create.propTypes = {
|
|
|
1170
1171
|
isLoadingMetaEntities: PropTypes.bool,
|
|
1171
1172
|
selectedOfferDetails: PropTypes.array,
|
|
1172
1173
|
eventContextTags: PropTypes.array,
|
|
1174
|
+
waitEventContextTags: PropTypes.object,
|
|
1173
1175
|
showTestAndPreviewSlidebox: PropTypes.bool,
|
|
1174
1176
|
handleTestAndPreview: PropTypes.func,
|
|
1175
1177
|
handleCloseTestAndPreview: PropTypes.func,
|
|
@@ -1091,6 +1091,7 @@ export class Edit extends React.Component { // eslint-disable-line react/prefer-
|
|
|
1091
1091
|
onPreviewContentClicked={this.props.onPreviewContentClicked}
|
|
1092
1092
|
onTestContentClicked={this.props.onTestContentClicked}
|
|
1093
1093
|
eventContextTags={this.props?.eventContextTags}
|
|
1094
|
+
waitEventContextTags={this.props?.waitEventContextTags}
|
|
1094
1095
|
messageDetails={this.props?.messageDetails}
|
|
1095
1096
|
/>
|
|
1096
1097
|
</CapColumn>
|
|
@@ -1131,6 +1132,7 @@ Edit.propTypes = {
|
|
|
1131
1132
|
injectedTags: PropTypes.object,
|
|
1132
1133
|
selectedOfferDetails: PropTypes.array,
|
|
1133
1134
|
eventContextTags: PropTypes.array,
|
|
1135
|
+
waitEventContextTags: PropTypes.object,
|
|
1134
1136
|
messageDetails: PropTypes.object,
|
|
1135
1137
|
showTestAndPreviewSlidebox: PropTypes.bool,
|
|
1136
1138
|
handleTestAndPreview: PropTypes.func,
|
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
import {
|
|
2
|
+
getSmsMessageFromFormData,
|
|
3
|
+
getSmsEmbeddedFooterValidity,
|
|
4
|
+
} from '../smsFormDataHelpers';
|
|
5
|
+
|
|
6
|
+
// ---------------------------------------------------------------------------
|
|
7
|
+
// getSmsMessageFromFormData
|
|
8
|
+
// ---------------------------------------------------------------------------
|
|
9
|
+
|
|
10
|
+
describe('getSmsMessageFromFormData', () => {
|
|
11
|
+
describe('null / invalid formData guard', () => {
|
|
12
|
+
it('returns empty string when formData is null', () => {
|
|
13
|
+
expect(getSmsMessageFromFormData(null, 1)).toBe('');
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it('returns empty string when formData is undefined', () => {
|
|
17
|
+
expect(getSmsMessageFromFormData(undefined, 1)).toBe('');
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it('returns empty string when formData is a string (not an object)', () => {
|
|
21
|
+
expect(getSmsMessageFromFormData('bad', 1)).toBe('');
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it('returns empty string when formData is a number', () => {
|
|
25
|
+
expect(getSmsMessageFromFormData(42, 1)).toBe('');
|
|
26
|
+
});
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
describe('currentTab normalisation', () => {
|
|
30
|
+
it('defaults to tab 1 when currentTab is null', () => {
|
|
31
|
+
const formData = { 0: { 'sms-editor': 'hello' } };
|
|
32
|
+
expect(getSmsMessageFromFormData(formData, null)).toBe('hello');
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it('defaults to tab 1 when currentTab is undefined', () => {
|
|
36
|
+
const formData = { 0: { 'sms-editor': 'hello' } };
|
|
37
|
+
expect(getSmsMessageFromFormData(formData, undefined)).toBe('hello');
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it('defaults to tab 1 when currentTab is 0', () => {
|
|
41
|
+
const formData = { 0: { 'sms-editor': 'hello' } };
|
|
42
|
+
expect(getSmsMessageFromFormData(formData, 0)).toBe('hello');
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it('defaults to tab 1 when currentTab is negative', () => {
|
|
46
|
+
const formData = { 0: { 'sms-editor': 'hello' } };
|
|
47
|
+
expect(getSmsMessageFromFormData(formData, -5)).toBe('hello');
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
describe('versioned key lookup', () => {
|
|
52
|
+
it('uses "sms-editor" key for tab 1', () => {
|
|
53
|
+
const formData = { 0: { 'sms-editor': 'tab1 msg' } };
|
|
54
|
+
expect(getSmsMessageFromFormData(formData, 1)).toBe('tab1 msg');
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it('uses "sms-editor2" versioned key for tab 2', () => {
|
|
58
|
+
const formData = { 1: { 'sms-editor2': 'tab2 msg', 'sms-editor': 'fallback' } };
|
|
59
|
+
expect(getSmsMessageFromFormData(formData, 2)).toBe('tab2 msg');
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it('uses "sms-editor3" versioned key for tab 3', () => {
|
|
63
|
+
const formData = { 2: { 'sms-editor3': 'tab3 msg' } };
|
|
64
|
+
expect(getSmsMessageFromFormData(formData, 3)).toBe('tab3 msg');
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it('returns empty string when versioned key exists but is null (cleared version)', () => {
|
|
68
|
+
const formData = { 1: { 'sms-editor2': null } };
|
|
69
|
+
expect(getSmsMessageFromFormData(formData, 2)).toBe('');
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it('returns empty string when versioned key exists but is empty string', () => {
|
|
73
|
+
const formData = { 1: { 'sms-editor2': '' } };
|
|
74
|
+
expect(getSmsMessageFromFormData(formData, 2)).toBe('');
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it('coerces a numeric value in versioned key to string', () => {
|
|
78
|
+
const formData = { 1: { 'sms-editor2': 12345 } };
|
|
79
|
+
expect(getSmsMessageFromFormData(formData, 2)).toBe('12345');
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
describe('"sms-editor" flat key fallback (versioned key absent)', () => {
|
|
84
|
+
it('falls back to "sms-editor" when versioned key is absent for tab 2', () => {
|
|
85
|
+
const formData = { 1: { 'sms-editor': 'flat fallback' } };
|
|
86
|
+
expect(getSmsMessageFromFormData(formData, 2)).toBe('flat fallback');
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
describe('activeTab nested fallback', () => {
|
|
91
|
+
it('falls back to activeTab["sms-editor"] when neither versioned key nor flat "sms-editor" is present', () => {
|
|
92
|
+
// The versioned key must be absent entirely for the activeTab fallback to be reached.
|
|
93
|
+
// (If 'sms-editor' exists but is null, the function commits to '' without falling through.)
|
|
94
|
+
const formData = {
|
|
95
|
+
0: {
|
|
96
|
+
activeTab: 'variant1',
|
|
97
|
+
variant1: { 'sms-editor': 'variant msg' },
|
|
98
|
+
},
|
|
99
|
+
};
|
|
100
|
+
expect(getSmsMessageFromFormData(formData, 1)).toBe('variant msg');
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it('falls back to base["sms-editor"] when activeTab entry lacks "sms-editor" and flat key is absent', () => {
|
|
104
|
+
const formData = {
|
|
105
|
+
0: {
|
|
106
|
+
activeTab: 'variant1',
|
|
107
|
+
variant1: {},
|
|
108
|
+
base: { 'sms-editor': 'base msg' },
|
|
109
|
+
},
|
|
110
|
+
};
|
|
111
|
+
expect(getSmsMessageFromFormData(formData, 1)).toBe('base msg');
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
it('defaults activeTab to "base" when activeTab prop is not set and flat key is absent', () => {
|
|
115
|
+
const formData = {
|
|
116
|
+
0: {
|
|
117
|
+
base: { 'sms-editor': 'default base msg' },
|
|
118
|
+
},
|
|
119
|
+
};
|
|
120
|
+
expect(getSmsMessageFromFormData(formData, 1)).toBe('default base msg');
|
|
121
|
+
});
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
describe('root formData.base fallback', () => {
|
|
125
|
+
it('falls back to formData.base["sms-editor"] when tab slot is missing', () => {
|
|
126
|
+
const formData = {
|
|
127
|
+
base: { 'sms-editor': 'root base msg' },
|
|
128
|
+
};
|
|
129
|
+
expect(getSmsMessageFromFormData(formData, 1)).toBe('root base msg');
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
it('returns empty string when even root base has no "sms-editor"', () => {
|
|
133
|
+
const formData = { base: {} };
|
|
134
|
+
expect(getSmsMessageFromFormData(formData, 1)).toBe('');
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
it('returns empty string when formData has no relevant keys at all', () => {
|
|
138
|
+
expect(getSmsMessageFromFormData({}, 1)).toBe('');
|
|
139
|
+
});
|
|
140
|
+
});
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
// ---------------------------------------------------------------------------
|
|
144
|
+
// getSmsEmbeddedFooterValidity
|
|
145
|
+
// ---------------------------------------------------------------------------
|
|
146
|
+
|
|
147
|
+
describe('getSmsEmbeddedFooterValidity', () => {
|
|
148
|
+
describe('isTemplateNameEmpty', () => {
|
|
149
|
+
it('is true when template-name is absent', () => {
|
|
150
|
+
const formData = { 0: { 'sms-editor': 'msg' } };
|
|
151
|
+
const { isTemplateNameEmpty } = getSmsEmbeddedFooterValidity(formData, 1);
|
|
152
|
+
expect(isTemplateNameEmpty).toBe(true);
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
it('is true when template-name is empty string', () => {
|
|
156
|
+
const formData = { 'template-name': '', 0: { 'sms-editor': 'msg' } };
|
|
157
|
+
const { isTemplateNameEmpty } = getSmsEmbeddedFooterValidity(formData, 1);
|
|
158
|
+
expect(isTemplateNameEmpty).toBe(true);
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
it('is true when template-name is whitespace only', () => {
|
|
162
|
+
const formData = { 'template-name': ' ', 0: { 'sms-editor': 'msg' } };
|
|
163
|
+
const { isTemplateNameEmpty } = getSmsEmbeddedFooterValidity(formData, 1);
|
|
164
|
+
expect(isTemplateNameEmpty).toBe(true);
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
it('is false when template-name is a non-empty string', () => {
|
|
168
|
+
const formData = { 'template-name': 'My Template', 0: { 'sms-editor': 'msg' } };
|
|
169
|
+
const { isTemplateNameEmpty } = getSmsEmbeddedFooterValidity(formData, 1);
|
|
170
|
+
expect(isTemplateNameEmpty).toBe(false);
|
|
171
|
+
});
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
describe('isMessageEmpty — single tab', () => {
|
|
175
|
+
it('is true when message is empty string', () => {
|
|
176
|
+
const formData = { 'template-name': 'T', 0: { 'sms-editor': '' } };
|
|
177
|
+
const { isMessageEmpty } = getSmsEmbeddedFooterValidity(formData, 1);
|
|
178
|
+
expect(isMessageEmpty).toBe(true);
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
it('is true when message is whitespace only', () => {
|
|
182
|
+
const formData = { 'template-name': 'T', 0: { 'sms-editor': ' ' } };
|
|
183
|
+
const { isMessageEmpty } = getSmsEmbeddedFooterValidity(formData, 1);
|
|
184
|
+
expect(isMessageEmpty).toBe(true);
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
it('is true when no sms-editor key at all', () => {
|
|
188
|
+
const formData = { 'template-name': 'T', 0: {} };
|
|
189
|
+
const { isMessageEmpty } = getSmsEmbeddedFooterValidity(formData, 1);
|
|
190
|
+
expect(isMessageEmpty).toBe(true);
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
it('is false when message is non-empty', () => {
|
|
194
|
+
const formData = { 'template-name': 'T', 0: { 'sms-editor': 'Hello world' } };
|
|
195
|
+
const { isMessageEmpty } = getSmsEmbeddedFooterValidity(formData, 1);
|
|
196
|
+
expect(isMessageEmpty).toBe(false);
|
|
197
|
+
});
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
describe('isMessageEmpty — multiple tabs', () => {
|
|
201
|
+
it('is false when all tabs have non-empty messages', () => {
|
|
202
|
+
const formData = {
|
|
203
|
+
'template-name': 'T',
|
|
204
|
+
0: { 'sms-editor': 'Tab 1 message' },
|
|
205
|
+
1: { 'sms-editor2': 'Tab 2 message' },
|
|
206
|
+
2: { 'sms-editor3': 'Tab 3 message' },
|
|
207
|
+
};
|
|
208
|
+
const { isMessageEmpty } = getSmsEmbeddedFooterValidity(formData, 3);
|
|
209
|
+
expect(isMessageEmpty).toBe(false);
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
it('is true when the second tab has an empty message', () => {
|
|
213
|
+
const formData = {
|
|
214
|
+
'template-name': 'T',
|
|
215
|
+
0: { 'sms-editor': 'Tab 1 message' },
|
|
216
|
+
1: { 'sms-editor2': '' },
|
|
217
|
+
};
|
|
218
|
+
const { isMessageEmpty } = getSmsEmbeddedFooterValidity(formData, 2);
|
|
219
|
+
expect(isMessageEmpty).toBe(true);
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
it('is true when the third tab has no message', () => {
|
|
223
|
+
const formData = {
|
|
224
|
+
'template-name': 'T',
|
|
225
|
+
0: { 'sms-editor': 'msg1' },
|
|
226
|
+
1: { 'sms-editor2': 'msg2' },
|
|
227
|
+
2: {},
|
|
228
|
+
};
|
|
229
|
+
const { isMessageEmpty } = getSmsEmbeddedFooterValidity(formData, 3);
|
|
230
|
+
expect(isMessageEmpty).toBe(true);
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
it('treats tabCount=1 as single-tab check even when not explicitly provided', () => {
|
|
234
|
+
const formData = { 'template-name': 'T', 0: { 'sms-editor': 'msg' } };
|
|
235
|
+
const { isMessageEmpty } = getSmsEmbeddedFooterValidity(formData);
|
|
236
|
+
expect(isMessageEmpty).toBe(false);
|
|
237
|
+
});
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
describe('both fields together', () => {
|
|
241
|
+
it('returns both empty when template name absent and message empty', () => {
|
|
242
|
+
const formData = { 0: { 'sms-editor': '' } };
|
|
243
|
+
const result = getSmsEmbeddedFooterValidity(formData, 1);
|
|
244
|
+
expect(result).toEqual({ isTemplateNameEmpty: true, isMessageEmpty: true });
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
it('returns both non-empty when template name and message are present', () => {
|
|
248
|
+
const formData = { 'template-name': 'My Template', 0: { 'sms-editor': 'Hello!' } };
|
|
249
|
+
const result = getSmsEmbeddedFooterValidity(formData, 1);
|
|
250
|
+
expect(result).toEqual({ isTemplateNameEmpty: false, isMessageEmpty: false });
|
|
251
|
+
});
|
|
252
|
+
});
|
|
253
|
+
});
|
|
@@ -100,6 +100,7 @@ export const SmsTraiEdit = (props) => {
|
|
|
100
100
|
templateData = {},
|
|
101
101
|
selectedOfferDetails,
|
|
102
102
|
eventContextTags,
|
|
103
|
+
waitEventContextTags,
|
|
103
104
|
fetchingLiquidTags,
|
|
104
105
|
getLiquidTags,
|
|
105
106
|
showLiquidErrorInFooter,
|
|
@@ -1120,6 +1121,7 @@ export const SmsTraiEdit = (props) => {
|
|
|
1120
1121
|
disabled={!isRcsSmsFallback && disablehandler()}
|
|
1121
1122
|
selectedOfferDetails={selectedOfferDetails}
|
|
1122
1123
|
eventContextTags={eventContextTags}
|
|
1124
|
+
waitEventContextTags={waitEventContextTags}
|
|
1123
1125
|
popoverOverlayStyle={isRcsSmsFallback ? { zIndex: 10020 } : undefined}
|
|
1124
1126
|
popoverOverlayClassName={isRcsSmsFallback ? 'sms-fallback-taglist-popover rcs-sms-fallback-taglist-popover' : undefined}
|
|
1125
1127
|
/>
|
|
@@ -32,6 +32,7 @@ const SmsWrapper = (props) => {
|
|
|
32
32
|
smsRegister,
|
|
33
33
|
onShowTemplates,
|
|
34
34
|
eventContextTags,
|
|
35
|
+
waitEventContextTags,
|
|
35
36
|
showLiquidErrorInFooter,
|
|
36
37
|
getLiquidTags,
|
|
37
38
|
showTestAndPreviewSlidebox,
|
|
@@ -73,6 +74,7 @@ const SmsWrapper = (props) => {
|
|
|
73
74
|
onPreviewContentClicked,
|
|
74
75
|
onTestContentClicked,
|
|
75
76
|
eventContextTags,
|
|
77
|
+
waitEventContextTags,
|
|
76
78
|
showLiquidErrorInFooter,
|
|
77
79
|
getLiquidTags,
|
|
78
80
|
showTestAndPreviewSlidebox,
|
|
@@ -167,7 +167,7 @@ export class TagList extends React.Component { // eslint-disable-line react/pref
|
|
|
167
167
|
let injectedTags = {};
|
|
168
168
|
const eventContextTagsObj = {};
|
|
169
169
|
|
|
170
|
-
const {selectedOfferDetails, eventContextTags } = props;
|
|
170
|
+
const {selectedOfferDetails, eventContextTags, waitEventContextTags } = props;
|
|
171
171
|
if (props.injectedTags && !_.isEmpty(props.injectedTags)) {
|
|
172
172
|
const formattedInjectedTags = handleInjectedData(
|
|
173
173
|
props.injectedTags,
|
|
@@ -219,6 +219,43 @@ export class TagList extends React.Component { // eslint-disable-line react/pref
|
|
|
219
219
|
};
|
|
220
220
|
});
|
|
221
221
|
}
|
|
222
|
+
// Wait event context tags should be displayed in the Add Labels when node is next to Event based wait node.
|
|
223
|
+
if (waitEventContextTags && Object.keys(waitEventContextTags)?.length) {
|
|
224
|
+
|
|
225
|
+
Object.keys(waitEventContextTags).forEach((blockId) => {
|
|
226
|
+
const WAIT_EVENT_HEADER_MSG_LABEL = `${waitEventContextTags[blockId].eventName} (${waitEventContextTags[blockId].blockName})`;
|
|
227
|
+
eventContextTagsObj[blockId] = {
|
|
228
|
+
"name": WAIT_EVENT_HEADER_MSG_LABEL,
|
|
229
|
+
"desc": WAIT_EVENT_HEADER_MSG_LABEL,
|
|
230
|
+
"resolved": true,
|
|
231
|
+
'tag-header': true,
|
|
232
|
+
"subtags": {},
|
|
233
|
+
};
|
|
234
|
+
|
|
235
|
+
waitEventContextTags?.[blockId]?.tags?.forEach((tag) => {
|
|
236
|
+
const {
|
|
237
|
+
tagName, label, profileId, profileName, blockName, eventName
|
|
238
|
+
} = tag || {};
|
|
239
|
+
if (!profileId || !tagName || !label || !profileName) return;
|
|
240
|
+
// Initializing the tags profile if it doesn't exist
|
|
241
|
+
if (!eventContextTagsObj?.[blockId]?.subtags?.[profileId]) {
|
|
242
|
+
eventContextTagsObj[blockId].subtags[profileId] = {
|
|
243
|
+
"name": profileName,
|
|
244
|
+
"desc": profileName,
|
|
245
|
+
"resolved": true,
|
|
246
|
+
'tag-header': true,
|
|
247
|
+
"subtags": {},
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
// Adding the current tag to the profile group
|
|
251
|
+
eventContextTagsObj[blockId].subtags[profileId].subtags[tagName] = {
|
|
252
|
+
name: label,
|
|
253
|
+
desc: label,
|
|
254
|
+
resolved: true,
|
|
255
|
+
};
|
|
256
|
+
});
|
|
257
|
+
});
|
|
258
|
+
}
|
|
222
259
|
this.setState({tags: _.merge( {}, tags, injectedTags, eventContextTagsObj )});
|
|
223
260
|
}
|
|
224
261
|
|
|
@@ -406,7 +443,7 @@ export class TagList extends React.Component { // eslint-disable-line react/pref
|
|
|
406
443
|
isDisabled = true;
|
|
407
444
|
tooltipMsg = intl.formatMessage(messages.personalizationNotSupportedAnonymous);
|
|
408
445
|
}
|
|
409
|
-
|
|
446
|
+
|
|
410
447
|
return (
|
|
411
448
|
<div className={this.props.className ? this.props.className : ''}>
|
|
412
449
|
<CapTagList
|
|
@@ -440,6 +477,7 @@ TagList.defaultProps = {
|
|
|
440
477
|
isNewVersionFlow: false,
|
|
441
478
|
userLocale: 'en',
|
|
442
479
|
eventContextTags: [],
|
|
480
|
+
waitEventContextTags: {},
|
|
443
481
|
};
|
|
444
482
|
|
|
445
483
|
TagList.propTypes = {
|
|
@@ -460,6 +498,7 @@ TagList.propTypes = {
|
|
|
460
498
|
disabled: PropTypes.bool,
|
|
461
499
|
fetchingSchemaError: PropTypes.bool,
|
|
462
500
|
eventContextTags: PropTypes.array,
|
|
501
|
+
waitEventContextTags: PropTypes.object,
|
|
463
502
|
popoverPlacement: PropTypes.string,
|
|
464
503
|
// message to show when Add Label button is disabled (e.g. personalization restriction)
|
|
465
504
|
disableTooltipMsg: PropTypes.string,
|
|
@@ -19,4 +19,8 @@ export default defineMessages({
|
|
|
19
19
|
id: `${scope}.personalizationNotSupportedAnonymous`,
|
|
20
20
|
defaultMessage: 'Personalization tags are not supported for anonymous customers',
|
|
21
21
|
},
|
|
22
|
+
waitEvent: {
|
|
23
|
+
id: `${scope}.waitEvent`,
|
|
24
|
+
defaultMessage: 'Wait Event',
|
|
25
|
+
},
|
|
22
26
|
});
|
|
@@ -5,28 +5,34 @@ import { initialReducer } from '../../../initialReducer';
|
|
|
5
5
|
import { injectIntl } from "react-intl";
|
|
6
6
|
import { fireEvent } from "@testing-library/react";
|
|
7
7
|
import { TagList } from '../index';
|
|
8
|
-
import { TagListData, eventContextTags } from './mockdata';
|
|
8
|
+
import { TagListData, eventContextTags, waitEventContextTags } from './mockdata';
|
|
9
|
+
import { OfferTag, badgesTags, offer } from '../../../utils/tests/common.mockdata';
|
|
10
|
+
import * as commonUtils from "../../../utils/common";
|
|
9
11
|
import { Provider } from 'react-redux';
|
|
10
12
|
import { screen, render } from '../../../utils/test-utils';
|
|
11
13
|
import history from '../../../utils/history';
|
|
12
14
|
const { getByText, queryByText } = screen;
|
|
13
15
|
|
|
16
|
+
const buildProps = (props = {}) => ({
|
|
17
|
+
...TagListData,
|
|
18
|
+
onTagSelect: jest.fn(),
|
|
19
|
+
...props,
|
|
20
|
+
});
|
|
14
21
|
|
|
15
|
-
const initializeTagList = (props) => {
|
|
22
|
+
const initializeTagList = (props = {}) => {
|
|
16
23
|
const store = configureStore({}, initialReducer, history);
|
|
17
24
|
const Component = injectIntl(TagList);
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
...
|
|
21
|
-
|
|
22
|
-
|
|
25
|
+
const propsObj = buildProps(props);
|
|
26
|
+
return {
|
|
27
|
+
...render(
|
|
28
|
+
<Provider store={store}>
|
|
29
|
+
<Component {...propsObj} />
|
|
30
|
+
</Provider>
|
|
31
|
+
),
|
|
32
|
+
store,
|
|
33
|
+
Component,
|
|
34
|
+
propsObj,
|
|
23
35
|
};
|
|
24
|
-
|
|
25
|
-
return render(
|
|
26
|
-
<Provider store={store}>
|
|
27
|
-
<Component {...propsObj} />
|
|
28
|
-
</Provider>
|
|
29
|
-
);
|
|
30
36
|
};
|
|
31
37
|
|
|
32
38
|
const addLabelBtnAssertion = () => {
|
|
@@ -41,19 +47,115 @@ describe("TagList test : UNIT", () => {
|
|
|
41
47
|
addLabelBtnAssertion();
|
|
42
48
|
});
|
|
43
49
|
|
|
44
|
-
it('should render event context
|
|
45
|
-
initializeTagList({eventContextTags});
|
|
50
|
+
it('should render event context tag section from generateTags', () => {
|
|
51
|
+
initializeTagList({ eventContextTags, moduleFilterEnabled: false });
|
|
46
52
|
addLabelBtnAssertion();
|
|
47
53
|
const EVENT_CONTEXT_TAG_HEADER = getByText(/Entry event/i);
|
|
48
54
|
expect(EVENT_CONTEXT_TAG_HEADER).toBeInTheDocument();
|
|
49
|
-
fireEvent.click(EVENT_CONTEXT_TAG_HEADER);
|
|
50
|
-
// Customer profile tags
|
|
51
|
-
const CUSTOMER_PROFILE = getByText(/Current Customer/i);
|
|
52
|
-
fireEvent.click(CUSTOMER_PROFILE);
|
|
53
|
-
expect(getByText(/lifetimePurchases/i)).toBeInTheDocument();
|
|
54
55
|
|
|
55
56
|
// Behavioural event profile tags should not be visible as label and profile name is not present
|
|
56
57
|
const BEHAVIOURAL_EVENT_PROFILE = queryByText(/Behavioural event/i);
|
|
57
58
|
expect(BEHAVIOURAL_EVENT_PROFILE).not.toBeInTheDocument();
|
|
58
59
|
});
|
|
60
|
+
|
|
61
|
+
it('should render wait event context section when waitEventContextTags is provided', () => {
|
|
62
|
+
initializeTagList({ waitEventContextTags, moduleFilterEnabled: false });
|
|
63
|
+
addLabelBtnAssertion();
|
|
64
|
+
const WAIT_EVENT_HEADER = getByText(/Order Placed \(Wait Block\)/i);
|
|
65
|
+
expect(WAIT_EVENT_HEADER).toBeInTheDocument();
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it('should merge empty waitEventContextTags with entry event tags', () => {
|
|
69
|
+
initializeTagList({ eventContextTags, waitEventContextTags: {}, moduleFilterEnabled: false });
|
|
70
|
+
addLabelBtnAssertion();
|
|
71
|
+
expect(getByText(/Entry event/i)).toBeInTheDocument();
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it('calls parent onContextChange with Outbound when tags and injectedTags are empty on mount', () => {
|
|
75
|
+
const onContextChange = jest.fn();
|
|
76
|
+
initializeTagList({
|
|
77
|
+
tags: [],
|
|
78
|
+
injectedTags: {},
|
|
79
|
+
onContextChange,
|
|
80
|
+
onTagSelect: jest.fn(),
|
|
81
|
+
});
|
|
82
|
+
expect(onContextChange).toHaveBeenCalledWith('Outbound');
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it('applies fetchingSchemaError from props via componentWillReceiveProps', () => {
|
|
86
|
+
const { rerender, Component, propsObj, store } = initializeTagList({ fetchingSchemaError: false });
|
|
87
|
+
addLabelBtnAssertion();
|
|
88
|
+
rerender(
|
|
89
|
+
<Provider store={store}>
|
|
90
|
+
<Component {...propsObj} fetchingSchemaError />
|
|
91
|
+
</Provider>
|
|
92
|
+
);
|
|
93
|
+
expect(screen.getByText(/add label/i)).toBeInTheDocument();
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it('disables Add label when restrictPersonalization is true', () => {
|
|
97
|
+
initializeTagList({
|
|
98
|
+
restrictPersonalization: true,
|
|
99
|
+
disabled: false,
|
|
100
|
+
moduleFilterEnabled: false,
|
|
101
|
+
});
|
|
102
|
+
const btn = screen.getByText(/add label/i).closest('button');
|
|
103
|
+
expect(btn).toBeDisabled();
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it('calls transformCouponTags when selectedOfferDetails and coupon tags are present', () => {
|
|
107
|
+
const spy = jest.spyOn(TagList.prototype, 'transformCouponTags');
|
|
108
|
+
initializeTagList({
|
|
109
|
+
tags: OfferTag,
|
|
110
|
+
injectedTags: {},
|
|
111
|
+
selectedOfferDetails: [{ id: 'c1', couponName: 'Promo Coupon', couponSeriesId: 'c1' }],
|
|
112
|
+
moduleFilterEnabled: false,
|
|
113
|
+
});
|
|
114
|
+
expect(spy).toHaveBeenCalled();
|
|
115
|
+
spy.mockRestore();
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
it('calls transformBadgeTags from common when badge offer and Badge tags are present', () => {
|
|
119
|
+
const spy = jest.spyOn(commonUtils, 'transformBadgeTags');
|
|
120
|
+
initializeTagList({
|
|
121
|
+
tags: badgesTags,
|
|
122
|
+
injectedTags: {},
|
|
123
|
+
selectedOfferDetails: offer,
|
|
124
|
+
moduleFilterEnabled: false,
|
|
125
|
+
});
|
|
126
|
+
expect(spy).toHaveBeenCalled();
|
|
127
|
+
spy.mockRestore();
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
it('unmounts without throwing', () => {
|
|
131
|
+
const { unmount } = initializeTagList();
|
|
132
|
+
expect(() => unmount()).not.toThrow();
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
it('regenerates tags when props.tags change (componentDidUpdate)', () => {
|
|
136
|
+
const { rerender, Component, store } = initializeTagList({ tags: TagListData.tags });
|
|
137
|
+
const extra = [
|
|
138
|
+
...TagListData.tags,
|
|
139
|
+
{
|
|
140
|
+
_id: 'extra-tag',
|
|
141
|
+
type: 'TAG',
|
|
142
|
+
definition: {
|
|
143
|
+
label: { en: 'Extra' },
|
|
144
|
+
value: 'extra_value',
|
|
145
|
+
subtags: [],
|
|
146
|
+
'tag-header': false,
|
|
147
|
+
supportedModules: [{ context: 'default', layout: 'sms', mandatory: false }],
|
|
148
|
+
},
|
|
149
|
+
scope: { tag: 'STANDARD', orgId: -1, verticals: [] },
|
|
150
|
+
isActive: true,
|
|
151
|
+
},
|
|
152
|
+
];
|
|
153
|
+
rerender(
|
|
154
|
+
<Provider store={store}>
|
|
155
|
+
<Component {...buildProps({ tags: extra })} />
|
|
156
|
+
</Provider>
|
|
157
|
+
);
|
|
158
|
+
addLabelBtnAssertion();
|
|
159
|
+
expect(screen.getByText(/add label/i)).toBeInTheDocument();
|
|
160
|
+
});
|
|
59
161
|
});
|
|
@@ -149,3 +149,20 @@ export const eventContextTags = [
|
|
|
149
149
|
"isDynamicFact": false
|
|
150
150
|
}
|
|
151
151
|
];
|
|
152
|
+
|
|
153
|
+
export const waitEventContextTags = {
|
|
154
|
+
block1: {
|
|
155
|
+
eventName: 'Order Placed',
|
|
156
|
+
blockName: 'Wait Block',
|
|
157
|
+
tags: [
|
|
158
|
+
{
|
|
159
|
+
tagName: 'waitEvent.orderId',
|
|
160
|
+
label: 'Order ID',
|
|
161
|
+
profileId: 'ORDER_PROFILE',
|
|
162
|
+
profileName: 'Order Profile',
|
|
163
|
+
blockName: 'Wait Block',
|
|
164
|
+
eventName: 'Order Placed',
|
|
165
|
+
},
|
|
166
|
+
],
|
|
167
|
+
},
|
|
168
|
+
};
|