@capillarytech/creatives-library 8.0.353-alpha.5 → 8.0.353-alpha.6

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 (136) hide show
  1. package/constants/unified.js +29 -0
  2. package/package.json +1 -1
  3. package/services/tests/api.test.js +35 -20
  4. package/utils/commonUtils.js +19 -1
  5. package/utils/rcsPayloadUtils.js +92 -0
  6. package/utils/templateVarUtils.js +201 -0
  7. package/utils/tests/rcsPayloadUtils.test.js +226 -0
  8. package/utils/tests/templateVarUtils.test.js +204 -0
  9. package/v2Components/CapActionButton/constants.js +7 -0
  10. package/v2Components/CapActionButton/index.js +166 -108
  11. package/v2Components/CapActionButton/index.scss +157 -6
  12. package/v2Components/CapActionButton/messages.js +19 -3
  13. package/v2Components/CapActionButton/tests/index.test.js +41 -17
  14. package/v2Components/CapTagList/index.js +10 -0
  15. package/v2Components/CommonTestAndPreview/CustomValuesEditor.js +72 -49
  16. package/v2Components/CommonTestAndPreview/DeliverySettings/DeliverySettings.scss +8 -2
  17. package/v2Components/CommonTestAndPreview/DeliverySettings/ModifyDeliverySettings.js +213 -21
  18. package/v2Components/CommonTestAndPreview/DeliverySettings/constants.js +16 -0
  19. package/v2Components/CommonTestAndPreview/DeliverySettings/index.js +85 -10
  20. package/v2Components/CommonTestAndPreview/DeliverySettings/messages.js +30 -0
  21. package/v2Components/CommonTestAndPreview/DeliverySettings/utils/parseSenderDetailsResponse.js +79 -11
  22. package/v2Components/CommonTestAndPreview/SendTestMessage.js +10 -5
  23. package/v2Components/CommonTestAndPreview/UnifiedPreview/PreviewHeader.js +0 -17
  24. package/v2Components/CommonTestAndPreview/UnifiedPreview/RcsPreviewContent.js +157 -15
  25. package/v2Components/CommonTestAndPreview/UnifiedPreview/_unifiedPreview.scss +346 -146
  26. package/v2Components/CommonTestAndPreview/UnifiedPreview/index.js +138 -48
  27. package/v2Components/CommonTestAndPreview/_commonTestAndPreview.scss +11 -0
  28. package/v2Components/CommonTestAndPreview/constants.js +38 -4
  29. package/v2Components/CommonTestAndPreview/index.js +691 -235
  30. package/v2Components/CommonTestAndPreview/messages.js +45 -3
  31. package/v2Components/CommonTestAndPreview/previewApiUtils.js +59 -0
  32. package/v2Components/CommonTestAndPreview/sagas.js +25 -6
  33. package/v2Components/CommonTestAndPreview/tests/CustomValuesEditor.test.js +308 -284
  34. package/v2Components/CommonTestAndPreview/tests/DeliverySettings/ModifyDeliverySettings.test.js +231 -65
  35. package/v2Components/CommonTestAndPreview/tests/DeliverySettings/index.test.js +118 -5
  36. package/v2Components/CommonTestAndPreview/tests/DeliverySettings/utils/parseSenderDetailsResponse.test.js +341 -0
  37. package/v2Components/CommonTestAndPreview/tests/PreviewSection.test.js +8 -1
  38. package/v2Components/CommonTestAndPreview/tests/SendTestMessage.test.js +34 -13
  39. package/v2Components/CommonTestAndPreview/tests/UnifiedPreview/PreviewHeader.test.js +0 -159
  40. package/v2Components/CommonTestAndPreview/tests/UnifiedPreview/RcsPreviewContent.test.js +281 -283
  41. package/v2Components/CommonTestAndPreview/tests/UnifiedPreview/index.test.js +199 -256
  42. package/v2Components/CommonTestAndPreview/tests/constants.test.js +1 -2
  43. package/v2Components/CommonTestAndPreview/tests/index.test.js +132 -198
  44. package/v2Components/CommonTestAndPreview/tests/previewApiUtils.test.js +67 -0
  45. package/v2Components/CommonTestAndPreview/tests/sagas.test.js +36 -26
  46. package/v2Components/FormBuilder/index.js +11 -6
  47. package/v2Components/SmsFallback/SmsFallbackLocalSelector.js +91 -0
  48. package/v2Components/SmsFallback/constants.js +73 -0
  49. package/v2Components/SmsFallback/index.js +956 -0
  50. package/v2Components/SmsFallback/index.scss +265 -0
  51. package/v2Components/SmsFallback/messages.js +78 -0
  52. package/v2Components/SmsFallback/smsFallbackUtils.js +119 -0
  53. package/v2Components/SmsFallback/tests/SmsFallbackLocalSelector.test.js +50 -0
  54. package/v2Components/SmsFallback/tests/rcsSmsFallback.acceptance.test.js +147 -0
  55. package/v2Components/SmsFallback/tests/smsFallbackHandlers.test.js +304 -0
  56. package/v2Components/SmsFallback/tests/smsFallbackUi.test.js +223 -0
  57. package/v2Components/SmsFallback/tests/smsFallbackUtils.test.js +309 -0
  58. package/v2Components/SmsFallback/tests/useLocalTemplateList.test.js +422 -0
  59. package/v2Components/SmsFallback/useLocalTemplateList.js +92 -0
  60. package/v2Components/TemplatePreview/_templatePreview.scss +38 -23
  61. package/v2Components/TemplatePreview/constants.js +2 -0
  62. package/v2Components/TemplatePreview/index.js +143 -31
  63. package/v2Components/TemplatePreview/tests/index.test.js +142 -0
  64. package/v2Components/TestAndPreviewSlidebox/index.js +15 -3
  65. package/v2Components/TestAndPreviewSlidebox/sagas.js +11 -4
  66. package/v2Components/TestAndPreviewSlidebox/tests/saga.test.js +3 -1
  67. package/v2Components/VarSegmentMessageEditor/constants.js +2 -0
  68. package/v2Components/VarSegmentMessageEditor/index.js +125 -0
  69. package/v2Components/VarSegmentMessageEditor/index.scss +46 -0
  70. package/v2Containers/App/constants.js +0 -3
  71. package/v2Containers/CreativesContainer/CreativesSlideBoxWrapper.js +43 -0
  72. package/v2Containers/CreativesContainer/SlideBoxContent.js +36 -4
  73. package/v2Containers/CreativesContainer/SlideBoxFooter.js +10 -1
  74. package/v2Containers/CreativesContainer/SlideBoxHeader.js +29 -4
  75. package/v2Containers/CreativesContainer/constants.js +9 -0
  76. package/v2Containers/CreativesContainer/embeddedSlideboxUtils.js +79 -0
  77. package/v2Containers/CreativesContainer/index.js +322 -103
  78. package/v2Containers/CreativesContainer/index.scss +51 -1
  79. package/v2Containers/CreativesContainer/tests/SlideBoxContent.localTemplates.test.js +90 -0
  80. package/v2Containers/CreativesContainer/tests/SlideBoxFooter.test.js +78 -34
  81. package/v2Containers/CreativesContainer/tests/SlideBoxHeader.test.js +79 -16
  82. package/v2Containers/CreativesContainer/tests/__snapshots__/SlideBoxContent.test.js.snap +8 -0
  83. package/v2Containers/CreativesContainer/tests/__snapshots__/SlideBoxHeader.test.js.snap +357 -98
  84. package/v2Containers/CreativesContainer/tests/__snapshots__/index.test.js.snap +20 -15
  85. package/v2Containers/CreativesContainer/tests/embeddedSlideboxUtils.test.js +258 -0
  86. package/v2Containers/CreativesContainer/tests/index.test.js +71 -9
  87. package/v2Containers/CreativesContainer/tests/useLocalTemplatesProp.test.js +125 -0
  88. package/v2Containers/MobilePush/Create/test/saga.test.js +2 -2
  89. package/v2Containers/Rcs/constants.js +119 -10
  90. package/v2Containers/Rcs/index.js +2445 -813
  91. package/v2Containers/Rcs/index.scss +280 -8
  92. package/v2Containers/Rcs/messages.js +34 -3
  93. package/v2Containers/Rcs/rcsLibraryHydrationUtils.js +225 -0
  94. package/v2Containers/Rcs/tests/__snapshots__/index.test.js.snap +98018 -70073
  95. package/v2Containers/Rcs/tests/__snapshots__/utils.test.js.snap +0 -5
  96. package/v2Containers/Rcs/tests/index.test.js +152 -121
  97. package/v2Containers/Rcs/tests/mockData.js +38 -0
  98. package/v2Containers/Rcs/tests/rcsLibraryHydrationUtils.test.js +318 -0
  99. package/v2Containers/Rcs/tests/utils.test.js +646 -30
  100. package/v2Containers/Rcs/utils.js +478 -11
  101. package/v2Containers/Sms/Create/index.js +106 -40
  102. package/v2Containers/Sms/smsFormDataHelpers.js +67 -0
  103. package/v2Containers/Sms/tests/smsFormDataHelpers.test.js +253 -0
  104. package/v2Containers/SmsTrai/Create/index.js +9 -4
  105. package/v2Containers/SmsTrai/Edit/constants.js +2 -0
  106. package/v2Containers/SmsTrai/Edit/index.js +640 -130
  107. package/v2Containers/SmsTrai/Edit/index.scss +121 -0
  108. package/v2Containers/SmsTrai/Edit/messages.js +14 -4
  109. package/v2Containers/SmsTrai/Edit/tests/__snapshots__/index.test.js.snap +4328 -2375
  110. package/v2Containers/SmsWrapper/index.js +37 -8
  111. package/v2Containers/TagList/index.js +6 -0
  112. package/v2Containers/Templates/TemplatesActionBar.js +101 -0
  113. package/v2Containers/Templates/_templates.scss +166 -9
  114. package/v2Containers/Templates/actions.js +11 -0
  115. package/v2Containers/Templates/constants.js +2 -0
  116. package/v2Containers/Templates/index.js +122 -120
  117. package/v2Containers/Templates/sagas.js +56 -12
  118. package/v2Containers/Templates/tests/TemplatesActionBar.test.js +120 -0
  119. package/v2Containers/Templates/tests/__snapshots__/index.test.js.snap +1062 -1017
  120. package/v2Containers/Templates/tests/sagas.test.js +199 -16
  121. package/v2Containers/Templates/tests/smsTemplatesListApi.test.js +180 -0
  122. package/v2Containers/Templates/utils/smsTemplatesListApi.js +79 -0
  123. package/v2Containers/TemplatesV2/TemplatesV2.style.js +72 -1
  124. package/v2Containers/TemplatesV2/index.js +86 -23
  125. package/v2Containers/TemplatesV2/tests/TemplatesV2.localTemplates.test.js +131 -0
  126. package/v2Containers/WeChat/MapTemplates/test/saga.test.js +9 -9
  127. package/v2Containers/WebPush/Create/index.js +8 -91
  128. package/v2Containers/WebPush/Create/index.scss +0 -7
  129. package/v2Containers/Whatsapp/index.js +3 -20
  130. package/v2Containers/Whatsapp/tests/__snapshots__/index.test.js.snap +578 -34
  131. package/v2Components/CommonTestAndPreview/UnifiedPreview/WebPushPreviewContent.js +0 -169
  132. package/v2Components/CommonTestAndPreview/tests/UnifiedPreview/WebPushPreviewContent.test.js +0 -522
  133. package/v2Containers/App/tests/constants.test.js +0 -61
  134. package/v2Containers/Templates/tests/webpush.test.js +0 -375
  135. package/v2Containers/WebPush/Create/tests/getTemplateContent.test.js +0 -338
  136. package/v2Containers/WebPush/Create/tests/testAndPreviewIntegration.test.js +0 -325
@@ -0,0 +1,258 @@
1
+ import {
2
+ CAP_SPACE_32,
3
+ CAP_SPACE_56,
4
+ CAP_SPACE_64,
5
+ } from '@capillarytech/cap-ui-library/styled/variables';
6
+ import {
7
+ isDeepEmpty,
8
+ getSlideBoxWrapperMarginFromLiquidErrors,
9
+ computeLiquidFooterUpdateFromFormBuilder,
10
+ } from '../embeddedSlideboxUtils';
11
+ import { ANDROID, IOS, MOBILE_PUSH, LIQUID_ERROR_MSG, STANDARD_ERROR_MSG } from '../constants';
12
+
13
+ // ---------------------------------------------------------------------------
14
+ // isDeepEmpty
15
+ // ---------------------------------------------------------------------------
16
+
17
+ describe('isDeepEmpty', () => {
18
+ it('returns true for null', () => {
19
+ expect(isDeepEmpty(null)).toBe(true);
20
+ });
21
+
22
+ it('returns true for undefined', () => {
23
+ expect(isDeepEmpty(undefined)).toBe(true);
24
+ });
25
+
26
+ it('returns true for empty string', () => {
27
+ expect(isDeepEmpty('')).toBe(true);
28
+ });
29
+
30
+ it('returns false for non-empty string', () => {
31
+ expect(isDeepEmpty('hello')).toBe(false);
32
+ });
33
+
34
+ it('returns true for empty array', () => {
35
+ expect(isDeepEmpty([])).toBe(true);
36
+ });
37
+
38
+ it('returns false for non-empty array', () => {
39
+ expect(isDeepEmpty(['item'])).toBe(false);
40
+ });
41
+
42
+ it('returns true for empty object', () => {
43
+ expect(isDeepEmpty({})).toBe(true);
44
+ });
45
+
46
+ it('returns true for object where all values are deep-empty', () => {
47
+ expect(isDeepEmpty({ a: null, b: '', c: [] })).toBe(true);
48
+ });
49
+
50
+ it('returns false for object with at least one non-empty value', () => {
51
+ expect(isDeepEmpty({ a: null, b: 'something' })).toBe(false);
52
+ });
53
+
54
+ it('returns true for nested all-empty object', () => {
55
+ expect(isDeepEmpty({ a: { b: null, c: [] } })).toBe(true);
56
+ });
57
+
58
+ it('returns false for nested object with a non-empty leaf', () => {
59
+ expect(isDeepEmpty({ a: { b: 'value' } })).toBe(false);
60
+ });
61
+
62
+ it('returns false for a number (non-null, non-string, non-array, non-object)', () => {
63
+ expect(isDeepEmpty(0)).toBe(false);
64
+ expect(isDeepEmpty(42)).toBe(false);
65
+ });
66
+
67
+ it('returns false for a boolean false', () => {
68
+ expect(isDeepEmpty(false)).toBe(false);
69
+ });
70
+ });
71
+
72
+ // ---------------------------------------------------------------------------
73
+ // getSlideBoxWrapperMarginFromLiquidErrors
74
+ // ---------------------------------------------------------------------------
75
+
76
+ describe('getSlideBoxWrapperMarginFromLiquidErrors', () => {
77
+ it('returns 0 when liquidErrorMessage is null', () => {
78
+ expect(getSlideBoxWrapperMarginFromLiquidErrors(null)).toBe(0);
79
+ });
80
+
81
+ it('returns 0 when liquidErrorMessage is undefined', () => {
82
+ expect(getSlideBoxWrapperMarginFromLiquidErrors(undefined)).toBe(0);
83
+ });
84
+
85
+ it('returns 0 when both error arrays are empty', () => {
86
+ expect(getSlideBoxWrapperMarginFromLiquidErrors({
87
+ STANDARD_ERROR_MSG: [],
88
+ LIQUID_ERROR_MSG: [],
89
+ })).toBe(0);
90
+ });
91
+
92
+ it('returns 0 when neither error array is present', () => {
93
+ expect(getSlideBoxWrapperMarginFromLiquidErrors({})).toBe(0);
94
+ });
95
+
96
+ it('returns CAP_SPACE_64 when both STANDARD and LIQUID errors are present', () => {
97
+ expect(getSlideBoxWrapperMarginFromLiquidErrors({
98
+ STANDARD_ERROR_MSG: ['std error'],
99
+ LIQUID_ERROR_MSG: ['liquid error'],
100
+ })).toBe(CAP_SPACE_64);
101
+ });
102
+
103
+ it('returns CAP_SPACE_56 when only LIQUID errors are present', () => {
104
+ expect(getSlideBoxWrapperMarginFromLiquidErrors({
105
+ STANDARD_ERROR_MSG: [],
106
+ LIQUID_ERROR_MSG: ['liquid error'],
107
+ })).toBe(CAP_SPACE_56);
108
+ });
109
+
110
+ it('returns CAP_SPACE_32 when only STANDARD errors are present', () => {
111
+ expect(getSlideBoxWrapperMarginFromLiquidErrors({
112
+ STANDARD_ERROR_MSG: ['std error'],
113
+ LIQUID_ERROR_MSG: [],
114
+ })).toBe(CAP_SPACE_32);
115
+ });
116
+
117
+ it('returns CAP_SPACE_56 when LIQUID has multiple errors and STANDARD is absent', () => {
118
+ expect(getSlideBoxWrapperMarginFromLiquidErrors({
119
+ LIQUID_ERROR_MSG: ['err1', 'err2'],
120
+ })).toBe(CAP_SPACE_56);
121
+ });
122
+
123
+ it('returns CAP_SPACE_32 when STANDARD has multiple errors and LIQUID is absent', () => {
124
+ expect(getSlideBoxWrapperMarginFromLiquidErrors({
125
+ STANDARD_ERROR_MSG: ['err1', 'err2'],
126
+ })).toBe(CAP_SPACE_32);
127
+ });
128
+ });
129
+
130
+ // ---------------------------------------------------------------------------
131
+ // computeLiquidFooterUpdateFromFormBuilder
132
+ // ---------------------------------------------------------------------------
133
+
134
+ describe('computeLiquidFooterUpdateFromFormBuilder', () => {
135
+ const noErrors = { [LIQUID_ERROR_MSG]: [], [STANDARD_ERROR_MSG]: [] };
136
+ const liquidOnly = { [LIQUID_ERROR_MSG]: ['liquid err'], [STANDARD_ERROR_MSG]: [] };
137
+ const standardOnly = { [LIQUID_ERROR_MSG]: [], [STANDARD_ERROR_MSG]: ['std err'] };
138
+ const bothErrors = { [LIQUID_ERROR_MSG]: ['l'], [STANDARD_ERROR_MSG]: ['s'] };
139
+
140
+ describe('normal (non-null) returns', () => {
141
+ it('returns isLiquidValidationError=false when no errors', () => {
142
+ const result = computeLiquidFooterUpdateFromFormBuilder(noErrors, 1, {
143
+ previousIsLiquidValidationError: false,
144
+ currentChannelUpper: 'SMS',
145
+ });
146
+ expect(result).not.toBeNull();
147
+ expect(result.isLiquidValidationError).toBe(false);
148
+ expect(result.liquidErrorMessage).toEqual(noErrors);
149
+ });
150
+
151
+ it('sets isLiquidValidationError=true when liquid errors present', () => {
152
+ const result = computeLiquidFooterUpdateFromFormBuilder(liquidOnly, 1, {
153
+ previousIsLiquidValidationError: false,
154
+ currentChannelUpper: 'SMS',
155
+ });
156
+ expect(result.isLiquidValidationError).toBe(true);
157
+ });
158
+
159
+ it('sets isLiquidValidationError=true when standard errors present', () => {
160
+ const result = computeLiquidFooterUpdateFromFormBuilder(standardOnly, 1, {
161
+ previousIsLiquidValidationError: false,
162
+ currentChannelUpper: 'SMS',
163
+ });
164
+ expect(result.isLiquidValidationError).toBe(true);
165
+ });
166
+
167
+ it('sets isLiquidValidationError=true when both errors present', () => {
168
+ const result = computeLiquidFooterUpdateFromFormBuilder(bothErrors, 1, {
169
+ previousIsLiquidValidationError: false,
170
+ currentChannelUpper: 'SMS',
171
+ });
172
+ expect(result.isLiquidValidationError).toBe(true);
173
+ });
174
+
175
+ it('maps tab 1 to ANDROID', () => {
176
+ const result = computeLiquidFooterUpdateFromFormBuilder(noErrors, 1, {
177
+ previousIsLiquidValidationError: false,
178
+ currentChannelUpper: 'SMS',
179
+ });
180
+ expect(result.activeFormBuilderTab).toBe(ANDROID);
181
+ });
182
+
183
+ it('maps tab 2 to IOS', () => {
184
+ const result = computeLiquidFooterUpdateFromFormBuilder(noErrors, 2, {
185
+ previousIsLiquidValidationError: false,
186
+ currentChannelUpper: 'SMS',
187
+ });
188
+ expect(result.activeFormBuilderTab).toBe(IOS);
189
+ });
190
+
191
+ it('maps tab 3 (or any other tab) to null', () => {
192
+ const result = computeLiquidFooterUpdateFromFormBuilder(noErrors, 3, {
193
+ previousIsLiquidValidationError: false,
194
+ currentChannelUpper: 'SMS',
195
+ });
196
+ expect(result.activeFormBuilderTab).toBeNull();
197
+ });
198
+
199
+ it('maps undefined tab to null for activeFormBuilderTab', () => {
200
+ const result = computeLiquidFooterUpdateFromFormBuilder(noErrors, undefined, {
201
+ previousIsLiquidValidationError: false,
202
+ currentChannelUpper: 'SMS',
203
+ });
204
+ expect(result.activeFormBuilderTab).toBeNull();
205
+ });
206
+
207
+ it('does NOT return null for Mobile Push when errors ARE present', () => {
208
+ const result = computeLiquidFooterUpdateFromFormBuilder(liquidOnly, 1, {
209
+ previousIsLiquidValidationError: true,
210
+ currentChannelUpper: MOBILE_PUSH,
211
+ });
212
+ expect(result).not.toBeNull();
213
+ expect(result.isLiquidValidationError).toBe(true);
214
+ });
215
+
216
+ it('does NOT return null for non-Mobile Push channel even with previousIsLiquidValidationError', () => {
217
+ const result = computeLiquidFooterUpdateFromFormBuilder(noErrors, 1, {
218
+ previousIsLiquidValidationError: true,
219
+ currentChannelUpper: 'SMS',
220
+ });
221
+ expect(result).not.toBeNull();
222
+ expect(result.isLiquidValidationError).toBe(false);
223
+ });
224
+
225
+ it('works without options argument (uses defaults)', () => {
226
+ const result = computeLiquidFooterUpdateFromFormBuilder(noErrors, 1);
227
+ expect(result).not.toBeNull();
228
+ expect(result.isLiquidValidationError).toBe(false);
229
+ });
230
+ });
231
+
232
+ describe('Mobile Push OLD clear (returns null)', () => {
233
+ it('returns null when no errors and previousIsLiquidValidationError is true for MOBILEPUSH', () => {
234
+ const result = computeLiquidFooterUpdateFromFormBuilder(noErrors, 1, {
235
+ previousIsLiquidValidationError: true,
236
+ currentChannelUpper: MOBILE_PUSH,
237
+ });
238
+ expect(result).toBeNull();
239
+ });
240
+
241
+ it('does NOT return null when previousIsLiquidValidationError is false for MOBILEPUSH', () => {
242
+ const result = computeLiquidFooterUpdateFromFormBuilder(noErrors, 1, {
243
+ previousIsLiquidValidationError: false,
244
+ currentChannelUpper: MOBILE_PUSH,
245
+ });
246
+ expect(result).not.toBeNull();
247
+ });
248
+
249
+ it('does NOT return null when only standard errors cleared but liquid error remains for MOBILEPUSH', () => {
250
+ const result = computeLiquidFooterUpdateFromFormBuilder(liquidOnly, 1, {
251
+ previousIsLiquidValidationError: true,
252
+ currentChannelUpper: MOBILE_PUSH,
253
+ });
254
+ // hasLiquid = true → condition !hasLiquid is false → does not return null
255
+ expect(result).not.toBeNull();
256
+ });
257
+ });
258
+ });
@@ -173,19 +173,35 @@ describe('Test SlideBoxContent container', () => {
173
173
  expect(getCreativesData).toHaveBeenCalledWith({ channel: 'RCS' });
174
174
  });
175
175
 
176
- it('RCS getFormData does not attach smsFallBackContent to templateData', async () => {
177
- renderFunction('RCS', 'editTemplate', rcsTemplates, rcsEditTemplateData);
176
+ it('RCS getFormData mirrors smsFallBackContent onto templateData for library round-trip', async () => {
177
+ renderedComponent = shallowWithIntl(
178
+ <Creatives
179
+ loyaltyMetaData={loyaltyMetaData}
180
+ Templates={rcsTemplates}
181
+ channel="RCS"
182
+ slidBoxContent="editTemplate"
183
+ templateData={rcsEditTemplateData}
184
+ handleCloseCreatives={handleCloseCreatives}
185
+ getCreativesData={getCreativesData}
186
+ isFullMode={false}
187
+ templateActions={{
188
+ getCdnTransformationConfig,
189
+ }}
190
+ />,
191
+ );
178
192
  renderedComponent.instance().onEditTemplate();
179
193
 
180
194
  const mockValue = {
195
+ validity: true,
181
196
  value: {
182
197
  name: 'rcs_creative_name',
198
+ type: 'RCS',
183
199
  versions: {
184
200
  base: {
185
201
  content: {
186
202
  RCS: {
187
203
  rcsContent: { contentType: 'RICHCARD', cardType: 'STANDALONE', cardSettings: {}, cardContent: [{}] },
188
- smsFallBackContent: { message: 'should-not-be-copied' },
204
+ smsFallBackContent: { message: 'fallback-body' },
189
205
  },
190
206
  },
191
207
  },
@@ -193,16 +209,62 @@ describe('Test SlideBoxContent container', () => {
193
209
  },
194
210
  };
195
211
 
196
- renderedComponent
197
- .find('CapSlideBox')
198
- .props()
199
- .content.props.getFormData(mockValue);
212
+ // Call getFormData on the container instance — shallow CapSlideBox `content` shape can differ across wrappers
213
+ renderedComponent.instance().getFormData(mockValue);
200
214
  await tick();
201
215
 
202
216
  const instance = renderedComponent.instance();
203
217
  expect(instance.state.templateData).toBeDefined();
204
- // sms fallback content should not be set on templateData per current logic
205
- expect(instance.state.templateData.smsFallBackContent).toBeUndefined();
218
+ expect(instance.state.templateData.smsFallBackContent).toEqual({ message: 'fallback-body' });
219
+ });
220
+
221
+ it('RCS getFormData mirrors rcsCardVarMapped onto templateData for campaign reopen', async () => {
222
+ renderedComponent = shallowWithIntl(
223
+ <Creatives
224
+ loyaltyMetaData={loyaltyMetaData}
225
+ Templates={rcsTemplates}
226
+ channel="RCS"
227
+ slidBoxContent="editTemplate"
228
+ templateData={rcsEditTemplateData}
229
+ handleCloseCreatives={handleCloseCreatives}
230
+ getCreativesData={getCreativesData}
231
+ isFullMode={false}
232
+ templateActions={{
233
+ getCdnTransformationConfig,
234
+ }}
235
+ />,
236
+ );
237
+ renderedComponent.instance().onEditTemplate();
238
+
239
+ const cardVarMapped = { 1: '[Name]', user_name: '[Name]' };
240
+ const mockValue = {
241
+ validity: true,
242
+ value: {
243
+ name: 'rcs_creative_name',
244
+ type: 'RCS',
245
+ versions: {
246
+ base: {
247
+ content: {
248
+ RCS: {
249
+ rcsContent: {
250
+ contentType: 'RICHCARD',
251
+ cardType: 'STANDALONE',
252
+ cardSettings: {},
253
+ cardContent: [{ title: 'Hi {{user_name}}', cardVarMapped }],
254
+ },
255
+ smsFallBackContent: {},
256
+ },
257
+ },
258
+ },
259
+ },
260
+ },
261
+ };
262
+
263
+ renderedComponent.instance().getFormData(mockValue);
264
+ await tick();
265
+
266
+ const instance = renderedComponent.instance();
267
+ expect(instance.state.templateData.rcsCardVarMapped).toEqual(cardVarMapped);
206
268
  });
207
269
 
208
270
  it('Text getCreatives data for rcs, data from creatives done to campaigns', async () => {
@@ -0,0 +1,125 @@
1
+ /**
2
+ * Covers local-templates flag resolution: nested `localTemplatesConfig.useLocalTemplates`
3
+ * vs top-level `useLocalTemplates` (embedded consumers / SlideBoxContent).
4
+ */
5
+ import React from 'react';
6
+ import { shallowWithIntl } from '../../../helpers/intl-enzym-test-helpers';
7
+ import { Creatives } from '../index';
8
+ import mockdata from '../../mockdata';
9
+
10
+ const { smsTemplates, loyaltyMetaData } = mockdata;
11
+
12
+ jest.mock('../../../v2Components/FormBuilder', () => ({
13
+ __esModule: true,
14
+ default: (props) => (
15
+ <div className="FormBuilder-mock" {...props}>
16
+ FormBuilder
17
+ </div>
18
+ ),
19
+ }));
20
+
21
+ const baseProps = {
22
+ loyaltyMetaData,
23
+ Templates: smsTemplates,
24
+ channel: 'SMS',
25
+ creativesMode: 'create',
26
+ templateData: null,
27
+ isFullMode: true,
28
+ handleCloseCreatives: jest.fn(),
29
+ getCreativesData: jest.fn(),
30
+ templateActions: {
31
+ getCdnTransformationConfig: jest.fn(),
32
+ resetTemplateStoreData: jest.fn(),
33
+ },
34
+ globalActions: {
35
+ clearMetaEntities: jest.fn(),
36
+ },
37
+ };
38
+
39
+ describe('CreativesContainer useLocalTemplates prop resolution', () => {
40
+ beforeEach(() => {
41
+ jest.clearAllMocks();
42
+ });
43
+
44
+ it('initial slidebox is templates when only top-level useLocalTemplates is true', () => {
45
+ const wrapper = shallowWithIntl(
46
+ <Creatives
47
+ {...baseProps}
48
+ useLocalTemplates
49
+ />,
50
+ );
51
+ expect(wrapper.instance().state.slidBoxContent).toBe('templates');
52
+ });
53
+
54
+ it('initial slidebox is templates when localTemplatesConfig.useLocalTemplates is true', () => {
55
+ const wrapper = shallowWithIntl(
56
+ <Creatives
57
+ {...baseProps}
58
+ localTemplatesConfig={{ useLocalTemplates: true }}
59
+ />,
60
+ );
61
+ expect(wrapper.instance().state.slidBoxContent).toBe('templates');
62
+ });
63
+
64
+ it('does not reset template store on unmount when embedded and top-level useLocalTemplates is true', () => {
65
+ const wrapper = shallowWithIntl(
66
+ <Creatives
67
+ {...baseProps}
68
+ location={{ query: { type: 'embedded' } }}
69
+ useLocalTemplates
70
+ />,
71
+ );
72
+ wrapper.unmount();
73
+ expect(baseProps.templateActions.resetTemplateStoreData).not.toHaveBeenCalled();
74
+ expect(baseProps.globalActions.clearMetaEntities).toHaveBeenCalled();
75
+ });
76
+
77
+ it('resets template store on unmount when embedded and local templates are not used', () => {
78
+ const wrapper = shallowWithIntl(
79
+ <Creatives
80
+ {...baseProps}
81
+ location={{ query: { type: 'embedded' } }}
82
+ useLocalTemplates={false}
83
+ />,
84
+ );
85
+ wrapper.unmount();
86
+ expect(baseProps.templateActions.resetTemplateStoreData).toHaveBeenCalled();
87
+ expect(baseProps.globalActions.clearMetaEntities).toHaveBeenCalled();
88
+ });
89
+
90
+ it('prefers nested localTemplatesConfig.useLocalTemplates=false over top-level true (not local list mode)', () => {
91
+ const wrapper = shallowWithIntl(
92
+ <Creatives
93
+ {...baseProps}
94
+ localTemplatesConfig={{ useLocalTemplates: false }}
95
+ useLocalTemplates
96
+ />,
97
+ );
98
+ expect(wrapper.instance().state.slidBoxContent).toBe('createTemplate');
99
+ });
100
+
101
+ it('uses top-level useLocalTemplates when localTemplatesConfig omits the flag', () => {
102
+ const wrapper = shallowWithIntl(
103
+ <Creatives
104
+ {...baseProps}
105
+ localTemplatesConfig={{}}
106
+ useLocalTemplates
107
+ />,
108
+ );
109
+ expect(wrapper.instance().state.slidBoxContent).toBe('templates');
110
+ });
111
+
112
+ it('resets template store when nested useLocalTemplates is false even if top-level is true', () => {
113
+ const wrapper = shallowWithIntl(
114
+ <Creatives
115
+ {...baseProps}
116
+ location={{ query: { type: 'embedded' } }}
117
+ localTemplatesConfig={{ useLocalTemplates: false }}
118
+ useLocalTemplates
119
+ />,
120
+ );
121
+ wrapper.unmount();
122
+ expect(baseProps.templateActions.resetTemplateStoreData).toHaveBeenCalled();
123
+ expect(baseProps.globalActions.clearMetaEntities).toHaveBeenCalled();
124
+ });
125
+ });
@@ -7,12 +7,12 @@ import {
7
7
 
8
8
  describe("MPUSH Sagas", () => {
9
9
  describe("v2MobilePushCreateSagas Combined", () => {
10
- it.concurrent("should initialize all mpush-related watcher sagas without error", () => {
10
+ it("should initialize all mpush-related watcher sagas without error", () => {
11
11
  return expectSaga(v2MobilePushCreateSagas).run();
12
12
  });
13
13
  });
14
14
  describe("v2MobilePushWatchDuplicateTemplateSaga Combined", () => {
15
- it.concurrent("should initialize all mpush-related watcher sagas without error", () => {
15
+ it("should initialize all mpush-related watcher sagas without error", () => {
16
16
  return expectSaga(v2MobilePushWatchDuplicateTemplateSaga).run();
17
17
  });
18
18
  });
@@ -34,18 +34,32 @@ export const RCS_STATUSES = {
34
34
  awaitingApproval: 'awaitingApproval',
35
35
  };
36
36
 
37
+ export const HOST_ICS="rcsicsbulk";
38
+ export const HOST_INFOBIP="rcsinfobipbulk";
39
+
37
40
  export const contentType = {
38
41
  text_message: 'text_message',
39
42
  rich_card: 'rich_card',
40
43
  carousel: 'carousel',
41
44
  };
42
45
 
46
+ export const CHANNELS = [
47
+ 'email',
48
+ 'mPush',
49
+ 'viber',
50
+ 'whatsapp',
51
+ 'zalo',
52
+ 'facebook',
53
+ 'rcs',
54
+ 'inApp',
55
+ 'line',
56
+ 'weChat',
57
+ 'callTask',
58
+ 'FTP',
59
+ 'webpush',
60
+ ];
61
+
43
62
  export const STATUS_OPTIONS = [
44
- // {
45
- // key: 'created',
46
- // value: RCS_STATUSES.awaitingApproval,
47
- // label: <FormattedMessage {...messages.created} />,
48
- // },
49
63
  {
50
64
  key: 'approved',
51
65
  value: RCS_STATUSES.approved,
@@ -74,10 +88,31 @@ export const RCS_MEDIA_TYPES = {
74
88
  VIDEO: 'VIDEO',
75
89
  };
76
90
 
77
- export const rcsVarRegex = /\{\{\w+\}\}/g;
78
- export const rcsVarTestRegex = /\{\{\w+\}\}/;
91
+ /** Match `{{}}` placeholders including Liquid-style names (e.g. `tag.FORMAT_1`). Must align with split + `isAnyTemplateVarToken` / SMS fallback. */
92
+ export const rcsVarRegex = /\{\{[^}]+\}\}/g;
93
+ /** True when the whole string is a single RCS/mustache token (used when testing segment tokens). */
94
+ export const rcsVarTestRegex = /^\{\{[^}]+\}\}$/;
95
+
96
+ /** Matches all `{{N}}` numeric-index variable tokens in a template string (global). */
97
+ export const RCS_NUMERIC_VAR_TOKEN_REGEX = /\{\{(\d+)\}\}/g;
98
+ /** `cardVarMapped` slot keys that are numeric only (legacy ordering). */
99
+ export const RCS_NUMERIC_VAR_NAME_REGEX = /^\d+$/;
100
+ /** Semantic Liquid-style keys on RCS `cardVarMapped` (same class as `{{…}}` inner names in the editor). */
101
+ export const RCS_CARD_VAR_MAPPED_SEMANTIC_KEY_REGEX = /^[\w.]+$/;
102
+ /** Escape `RegExp` metacharacters when building a pattern from user/tag text. */
103
+ export const RCS_REGEX_META_CHARS_PATTERN = /[.*+?^${}()|[\]\\]/g;
104
+ /** Entire string is a single `{{tag}}` token (value still a placeholder). */
105
+ export const RCS_SINGLE_MUSTACHE_PLACEHOLDER_REGEX = /^\{\{[^}]+\}\}$/;
106
+ /** Strip leading `{{` and trailing `}}` from a token. */
107
+ export const RCS_STRIP_MUSTACHE_DELIMITERS_REGEX = /^\{\{|\}\}$/g;
108
+
109
+ /** Tag-popover target: title vs description field (matches `onTagSelect` `field` argument). */
110
+ export const RCS_TAG_AREA_FIELD_TITLE = 'title';
111
+ export const RCS_TAG_AREA_FIELD_DESC = 'desc';
112
+
79
113
  export const TEMPLATE_DESC_MAX_LENGTH = 2500;
80
- export const FALLBACK_MESSAGE_MAX_LENGTH = 160;
114
+ /** Resolved SMS fallback body cap. Must allow multi-segment DLT (>160); aligns with TRAI / `TEMPLATE_MESSAGE_MAX_LENGTH`. */
115
+ export const FALLBACK_MESSAGE_MAX_LENGTH = TEMPLATE_MESSAGE_MAX_LENGTH;
81
116
  export const TEMPLATE_TITLE_MAX_LENGTH = 200;
82
117
  export const TEMPLATE_BUTTON_TEXT_MAX_LENGTH = 25;
83
118
  export const CTA = 'CTA';
@@ -92,10 +127,12 @@ export const ALLOWED_IMAGE_EXTENSIONS_REGEX = (/\.(png|jpg|jpeg|gif)$/i);
92
127
  export const RCS_IMG_WIDTH = 1440;
93
128
  export const RCS_IMG_HEIGHT = 1024;
94
129
  export const RCS_IMG_SIZE = 2000000; // 2MB
130
+ export const RCS_CAROUSEL_IMG_SIZE = 1 * 1024 * 1024; // 1MB
95
131
  export const MAX_FILE_SIZE_MB = 10;
96
132
  export const MAX_FILE_SIZE_BYTES = MAX_FILE_SIZE_MB * 1024 * 1024;
97
133
  export const ALLOWED_EXTENSIONS_VIDEO_REGEX = (/\.(mp4)$/i);
98
134
  export const RCS_VIDEO_SIZE = 10 * 1024 * 1024;
135
+ export const RCS_CAROUSEL_VIDEO_SIZE = 5 * 1024 * 1024; // 5MB
99
136
  export const RCS_THUMBNAIL_MIN_SIZE = 40 * 1024;
100
137
  export const RCS_THUMBNAIL_MAX_SIZE = 100 * 1024;
101
138
 
@@ -142,13 +179,38 @@ export const RCS_IMAGE_DIMENSIONS = {
142
179
  },
143
180
  };
144
181
 
182
+ // Carousel image dimensions (pixel values shown in UI). Keys follow `${HEIGHT}_${WIDTH}`.
183
+ // HEIGHT: SHORT|MEDIUM, WIDTH: SMALL|MEDIUM
184
+ export const RCS_CAROUSEL_IMAGE_DIMENSIONS = {
185
+ SHORT_SMALL: {
186
+ width: 1160,
187
+ height: 720,
188
+ description: 'Image (1160 x 720)',
189
+ },
190
+ SHORT_MEDIUM: {
191
+ width: 1800,
192
+ height: 720,
193
+ description: 'Image (1800 x 720)',
194
+ },
195
+ MEDIUM_SMALL: {
196
+ width: 770,
197
+ height: 720,
198
+ description: 'Image (770 x 720)',
199
+ },
200
+ MEDIUM_MEDIUM: {
201
+ width: 1280,
202
+ height: 720,
203
+ description: 'Image (1280 x 720)',
204
+ },
205
+ };
206
+
145
207
  export const RCS_VIDEO_THUMBNAIL_DIMENSIONS = {
146
208
  SHORT_HEIGHT: {
147
- width: 770,
148
- height: 257,
149
209
  type: 'SHORT_HEIGHT',
150
210
  heightType: 'SHORT',
151
211
  label: 'Vertical Short',
212
+ width: 1140,
213
+ height: 448,
152
214
  description: 'Thumbnail (770 x 257)',
153
215
  orientation: 'VERTICAL',
154
216
  maxFileSize: 100 * 1024,
@@ -189,6 +251,29 @@ export const RCS_VIDEO_THUMBNAIL_DIMENSIONS = {
189
251
  },
190
252
  };
191
253
 
254
+ // Carousel thumbnail dimensions for video cards (pixel values shown in UI).
255
+ // Keys follow `${HEIGHT}_${WIDTH}`.
256
+ // Human-readable labels: formatMessage(messages.rcsCarouselVideoThumbnailLabel, { width, height })
257
+ // or formatRcsCarouselVideoThumbnailLabel(formatMessage, entry) from ./utils.
258
+ export const RCS_CAROUSEL_VIDEO_THUMBNAIL_DIMENSIONS = {
259
+ SHORT_SMALL: {
260
+ width: 718,
261
+ height: 448,
262
+ },
263
+ SHORT_MEDIUM: {
264
+ width: 1140,
265
+ height: 448,
266
+ },
267
+ MEDIUM_SMALL: {
268
+ width: 480,
269
+ height: 448,
270
+ },
271
+ MEDIUM_MEDIUM: {
272
+ width: 796,
273
+ height: 448,
274
+ },
275
+ };
276
+
192
277
  const prefix = 'app/v2Containers/Rcs';
193
278
  export const UPLOAD_RCS_ASSET_REQUEST = `${prefix}/UPLOAD_RCS_ASSET_REQUEST`;
194
279
  export const UPLOAD_RCS_ASSET_SUCCESS = `${prefix}/UPLOAD_RCS_ASSET_SUCCESS`;
@@ -215,6 +300,7 @@ export const CLEAR_EDIT_RESPONSE_REQUEST = `${prefix}/Edit/CLEAR_EDIT_RESPONSE_R
215
300
  export const AI_CONTENT_BOT_DISABLED = "AI_CONTENT_BOT_DISABLED";
216
301
 
217
302
  export const RCS_TEXT_MESSAGE_MAX_LENGTH = 160;
303
+ export const RCS_TEXT_MESSAGE_MAX_LENGTH_INFOBIP = 3072;
218
304
  export const RCS_RICH_CARD_MAX_LENGTH = 2000;
219
305
  export const MAX_BUTTONS = 4;
220
306
 
@@ -250,3 +336,26 @@ export const RCS_BUTTON_TYPES = {
250
336
  PHONE_NUMBER: 'PHONE_NUMBER',
251
337
  CTA: 'CTA',
252
338
  };
339
+
340
+ /** Default action row for carousel card 1 (mandatory button UX): phone CTA, unsaved until user completes + Save. */
341
+ export const RCS_CAROUSEL_FIRST_CARD_DEFAULT_SUGGESTIONS = [
342
+ {
343
+ index: 0,
344
+ type: RCS_BUTTON_TYPES.PHONE_NUMBER,
345
+ text: '',
346
+ phoneNumber: '',
347
+ url: null,
348
+ postback: '',
349
+ isSaved: false,
350
+ },
351
+ ];
352
+
353
+
354
+ export const CAROUSEL_HEIGHT_OPTIONS = [
355
+ { value: 'SHORT', label: 'Short' },
356
+ { value: 'MEDIUM', label: 'Medium' },
357
+ ];
358
+ export const CAROUSEL_WIDTH_OPTIONS = [
359
+ { value: 'SMALL', label: 'Small' },
360
+ { value: 'MEDIUM', label: 'Medium' },
361
+ ];