@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.
Files changed (91) hide show
  1. package/constants/unified.js +1 -0
  2. package/package.json +1 -1
  3. package/services/api.js +6 -0
  4. package/services/tests/api.test.js +7 -0
  5. package/utils/common.js +6 -1
  6. package/utils/tests/tagValidations.test.js +34 -0
  7. package/v2Components/CapTagList/index.js +15 -22
  8. package/v2Components/CapTagList/style.scss +48 -0
  9. package/v2Components/CapTagListWithInput/__tests__/CapTagListWithInput.test.js +63 -0
  10. package/v2Components/CapTagListWithInput/index.js +4 -0
  11. package/v2Components/CapWhatsappCTA/index.js +2 -0
  12. package/v2Components/CommonTestAndPreview/tests/CustomValuesEditor.test.js +180 -0
  13. package/v2Components/CommonTestAndPreview/tests/DeliverySettings/utils/parseSenderDetailsResponse.test.js +96 -0
  14. package/v2Components/CommonTestAndPreview/tests/UnifiedPreview/index.test.js +99 -0
  15. package/v2Components/CommonTestAndPreview/tests/index.test.js +113 -3
  16. package/v2Components/FormBuilder/index.js +7 -0
  17. package/v2Components/HtmlEditor/HTMLEditor.js +6 -1
  18. package/v2Components/HtmlEditor/__tests__/HTMLEditor.apiErrors.test.js +1 -0
  19. package/v2Components/HtmlEditor/__tests__/HTMLEditor.test.js +927 -2
  20. package/v2Components/HtmlEditor/components/CodeEditorPane/index.js +3 -0
  21. package/v2Components/SmsFallback/tests/useLocalTemplateList.test.js +95 -0
  22. package/v2Containers/BeeEditor/index.js +3 -0
  23. package/v2Containers/CommunicationFlow/CommunicationFlow.js +291 -0
  24. package/v2Containers/CommunicationFlow/CommunicationFlow.scss +25 -0
  25. package/v2Containers/CommunicationFlow/Tests/CommunicationFlow.test.js +255 -0
  26. package/v2Containers/CommunicationFlow/constants.js +200 -0
  27. package/v2Containers/CommunicationFlow/index.js +102 -0
  28. package/v2Containers/CommunicationFlow/messages.js +346 -0
  29. package/v2Containers/CommunicationFlow/steps/ChannelSelectionStep/ChannelSelectionStep.js +522 -0
  30. package/v2Containers/CommunicationFlow/steps/ChannelSelectionStep/ChannelSelectionStep.scss +170 -0
  31. package/v2Containers/CommunicationFlow/steps/ChannelSelectionStep/Tests/ChannelSelectionStep.test.js +796 -0
  32. package/v2Containers/CommunicationFlow/steps/ChannelSelectionStep/index.js +5 -0
  33. package/v2Containers/CommunicationFlow/steps/CommunicationStrategyStep/CommunicationStrategyStep.js +95 -0
  34. package/v2Containers/CommunicationFlow/steps/CommunicationStrategyStep/Tests/CommunicationStrategyStep.test.js +133 -0
  35. package/v2Containers/CommunicationFlow/steps/CommunicationStrategyStep/index.js +5 -0
  36. package/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/DeliverySettingsSection.js +289 -0
  37. package/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/DeliverySettingsSection.scss +70 -0
  38. package/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/SenderDetails.js +319 -0
  39. package/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/SenderDetails.scss +69 -0
  40. package/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/Tests/DeliverySettingsSection.test.js +616 -0
  41. package/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/Tests/SenderDetails.test.js +577 -0
  42. package/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/Tests/deliverySettingsConfig.test.js +1111 -0
  43. package/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/deliverySettingsConfig.js +696 -0
  44. package/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/index.js +7 -0
  45. package/v2Containers/CommunicationFlow/steps/DynamicControlsStep/DynamicControlsStep.js +102 -0
  46. package/v2Containers/CommunicationFlow/steps/DynamicControlsStep/DynamicControlsStep.scss +36 -0
  47. package/v2Containers/CommunicationFlow/steps/DynamicControlsStep/Tests/DynamicControlsStep.test.js +91 -0
  48. package/v2Containers/CommunicationFlow/steps/DynamicControlsStep/index.js +5 -0
  49. package/v2Containers/CommunicationFlow/steps/MessageTypeStep/MessageTypeStep.js +86 -0
  50. package/v2Containers/CommunicationFlow/steps/MessageTypeStep/Tests/MessageTypeStep.test.js +100 -0
  51. package/v2Containers/CommunicationFlow/steps/MessageTypeStep/index.js +5 -0
  52. package/v2Containers/CommunicationFlow/utils/getEnabledSteps.js +30 -0
  53. package/v2Containers/CreativesContainer/SlideBoxContent.js +28 -1
  54. package/v2Containers/CreativesContainer/constants.js +3 -0
  55. package/v2Containers/CreativesContainer/index.js +3 -0
  56. package/v2Containers/CreativesContainer/tests/SlideBoxFooter.test.js +104 -0
  57. package/v2Containers/CreativesContainer/tests/SlideBoxHeader.test.js +110 -0
  58. package/v2Containers/CreativesContainer/tests/__snapshots__/SlideBoxHeader.test.js.snap +363 -0
  59. package/v2Containers/CreativesContainer/tests/embeddedSlideboxUtils.test.js +258 -0
  60. package/v2Containers/Email/index.js +1 -0
  61. package/v2Containers/EmailWrapper/components/EmailHTMLEditor.js +7 -1
  62. package/v2Containers/EmailWrapper/components/EmailWrapperView.js +3 -0
  63. package/v2Containers/EmailWrapper/components/__tests__/EmailHTMLEditor.test.js +20 -2
  64. package/v2Containers/EmailWrapper/components/__tests__/EmailWrapperView.test.js +16 -1
  65. package/v2Containers/EmailWrapper/hooks/useEmailWrapper.js +3 -0
  66. package/v2Containers/EmailWrapper/index.js +4 -0
  67. package/v2Containers/EmailWrapper/tests/useEmailWrapper.edgeCases.test.js +1 -0
  68. package/v2Containers/EmailWrapper/tests/useEmailWrapper.test.js +9 -0
  69. package/v2Containers/InAppWrapper/hooks/__tests__/useInAppWrapper.test.js +19 -0
  70. package/v2Containers/InAppWrapper/hooks/useInAppWrapper.js +3 -0
  71. package/v2Containers/InAppWrapper/index.js +3 -0
  72. package/v2Containers/MobilePush/Create/index.js +2 -0
  73. package/v2Containers/MobilePush/Edit/index.js +2 -0
  74. package/v2Containers/MobilepushWrapper/index.js +3 -1
  75. package/v2Containers/Rcs/index.js +1 -0
  76. package/v2Containers/Sms/Create/index.js +2 -0
  77. package/v2Containers/Sms/Edit/index.js +2 -0
  78. package/v2Containers/Sms/tests/smsFormDataHelpers.test.js +253 -0
  79. package/v2Containers/SmsTrai/Edit/index.js +2 -0
  80. package/v2Containers/SmsWrapper/index.js +2 -0
  81. package/v2Containers/TagList/index.js +41 -2
  82. package/v2Containers/TagList/messages.js +4 -0
  83. package/v2Containers/TagList/tests/TagList.test.js +122 -20
  84. package/v2Containers/TagList/tests/mockdata.js +17 -0
  85. package/v2Containers/Templates/tests/sagas.test.js +83 -0
  86. package/v2Containers/Viber/index.js +5 -0
  87. package/v2Containers/WebPush/Create/hooks/useTagManagement.js +0 -2
  88. package/v2Containers/WebPush/Create/index.js +9 -1
  89. package/v2Containers/Whatsapp/index.js +5 -0
  90. package/v2Containers/Whatsapp/tests/__snapshots__/index.test.js.snap +20 -0
  91. 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
- const propsObj = {
20
- ...TagListData,
21
- onTagSelect: jest.fn(),
22
- ...props,
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 tags correctly from generateTags and show tags under profile', () => {
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
+ };