@capillarytech/creatives-library 8.0.359-alpha.0 → 8.0.359-alpha.1

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 (147) hide show
  1. package/constants/unified.js +29 -0
  2. package/index.html +1 -0
  3. package/package.json +1 -1
  4. package/services/tests/api.test.js +35 -20
  5. package/utils/cdnTransformation.js +75 -3
  6. package/utils/commonUtils.js +19 -1
  7. package/utils/rcsPayloadUtils.js +92 -0
  8. package/utils/templateVarUtils.js +201 -0
  9. package/utils/tests/cdnTransformation.test.js +127 -0
  10. package/utils/tests/rcsPayloadUtils.test.js +226 -0
  11. package/utils/tests/templateVarUtils.test.js +204 -0
  12. package/v2Components/CapActionButton/constants.js +7 -0
  13. package/v2Components/CapActionButton/index.js +166 -108
  14. package/v2Components/CapActionButton/index.scss +157 -6
  15. package/v2Components/CapActionButton/messages.js +19 -3
  16. package/v2Components/CapActionButton/tests/index.test.js +41 -17
  17. package/v2Components/CapImageUpload/index.js +2 -2
  18. package/v2Components/CapTagList/index.js +10 -0
  19. package/v2Components/CommonTestAndPreview/CustomValuesEditor.js +72 -49
  20. package/v2Components/CommonTestAndPreview/DeliverySettings/DeliverySettings.scss +8 -2
  21. package/v2Components/CommonTestAndPreview/DeliverySettings/ModifyDeliverySettings.js +214 -21
  22. package/v2Components/CommonTestAndPreview/DeliverySettings/constants.js +16 -0
  23. package/v2Components/CommonTestAndPreview/DeliverySettings/index.js +83 -9
  24. package/v2Components/CommonTestAndPreview/DeliverySettings/messages.js +30 -0
  25. package/v2Components/CommonTestAndPreview/DeliverySettings/utils/parseSenderDetailsResponse.js +79 -11
  26. package/v2Components/CommonTestAndPreview/SendTestMessage.js +10 -5
  27. package/v2Components/CommonTestAndPreview/UnifiedPreview/PreviewHeader.js +16 -0
  28. package/v2Components/CommonTestAndPreview/UnifiedPreview/RcsPreviewContent.js +157 -15
  29. package/v2Components/CommonTestAndPreview/UnifiedPreview/ViberPreviewContent.js +14 -132
  30. package/v2Components/CommonTestAndPreview/UnifiedPreview/WebPushPreviewContent.js +169 -0
  31. package/v2Components/CommonTestAndPreview/UnifiedPreview/_unifiedPreview.scss +400 -239
  32. package/v2Components/CommonTestAndPreview/UnifiedPreview/index.js +202 -10
  33. package/v2Components/CommonTestAndPreview/_commonTestAndPreview.scss +11 -0
  34. package/v2Components/CommonTestAndPreview/constants.js +40 -2
  35. package/v2Components/CommonTestAndPreview/index.js +887 -453
  36. package/v2Components/CommonTestAndPreview/messages.js +45 -3
  37. package/v2Components/CommonTestAndPreview/previewApiUtils.js +59 -0
  38. package/v2Components/CommonTestAndPreview/sagas.js +25 -6
  39. package/v2Components/CommonTestAndPreview/tests/CustomValuesEditor.test.js +308 -284
  40. package/v2Components/CommonTestAndPreview/tests/DeliverySettings/ModifyDeliverySettings.test.js +231 -65
  41. package/v2Components/CommonTestAndPreview/tests/DeliverySettings/index.test.js +118 -5
  42. package/v2Components/CommonTestAndPreview/tests/DeliverySettings/utils/parseSenderDetailsResponse.test.js +341 -0
  43. package/v2Components/CommonTestAndPreview/tests/PreviewSection.test.js +8 -1
  44. package/v2Components/CommonTestAndPreview/tests/SendTestMessage.test.js +34 -13
  45. package/v2Components/CommonTestAndPreview/tests/UnifiedPreview/PreviewHeader.test.js +163 -0
  46. package/v2Components/CommonTestAndPreview/tests/UnifiedPreview/RcsPreviewContent.test.js +281 -283
  47. package/v2Components/CommonTestAndPreview/tests/UnifiedPreview/ViberPreviewContent.test.js +0 -364
  48. package/v2Components/CommonTestAndPreview/tests/UnifiedPreview/WebPushPreviewContent.test.js +522 -0
  49. package/v2Components/CommonTestAndPreview/tests/UnifiedPreview/index.test.js +454 -1
  50. package/v2Components/CommonTestAndPreview/tests/constants.test.js +2 -1
  51. package/v2Components/CommonTestAndPreview/tests/index.test.js +327 -4
  52. package/v2Components/CommonTestAndPreview/tests/previewApiUtils.test.js +67 -0
  53. package/v2Components/CommonTestAndPreview/tests/sagas.test.js +31 -24
  54. package/v2Components/FormBuilder/index.js +167 -56
  55. package/v2Components/SmsFallback/SmsFallbackLocalSelector.js +91 -0
  56. package/v2Components/SmsFallback/constants.js +73 -0
  57. package/v2Components/SmsFallback/index.js +956 -0
  58. package/v2Components/SmsFallback/index.scss +265 -0
  59. package/v2Components/SmsFallback/messages.js +78 -0
  60. package/v2Components/SmsFallback/smsFallbackUtils.js +119 -0
  61. package/v2Components/SmsFallback/tests/SmsFallbackLocalSelector.test.js +50 -0
  62. package/v2Components/SmsFallback/tests/rcsSmsFallback.acceptance.test.js +147 -0
  63. package/v2Components/SmsFallback/tests/smsFallbackHandlers.test.js +304 -0
  64. package/v2Components/SmsFallback/tests/smsFallbackUi.test.js +223 -0
  65. package/v2Components/SmsFallback/tests/smsFallbackUtils.test.js +309 -0
  66. package/v2Components/SmsFallback/tests/useLocalTemplateList.test.js +422 -0
  67. package/v2Components/SmsFallback/useLocalTemplateList.js +92 -0
  68. package/v2Components/TemplatePreview/_templatePreview.scss +37 -22
  69. package/v2Components/TemplatePreview/constants.js +2 -0
  70. package/v2Components/TemplatePreview/index.js +143 -31
  71. package/v2Components/TemplatePreview/tests/index.test.js +142 -0
  72. package/v2Components/TestAndPreviewSlidebox/index.js +15 -3
  73. package/v2Components/TestAndPreviewSlidebox/sagas.js +11 -4
  74. package/v2Components/TestAndPreviewSlidebox/tests/saga.test.js +3 -1
  75. package/v2Components/VarSegmentMessageEditor/constants.js +2 -0
  76. package/v2Components/VarSegmentMessageEditor/index.js +125 -0
  77. package/v2Components/VarSegmentMessageEditor/index.scss +46 -0
  78. package/v2Containers/App/constants.js +3 -0
  79. package/v2Containers/App/tests/constants.test.js +61 -0
  80. package/v2Containers/CreativesContainer/CreativesSlideBoxWrapper.js +17 -0
  81. package/v2Containers/CreativesContainer/SlideBoxContent.js +36 -4
  82. package/v2Containers/CreativesContainer/SlideBoxFooter.js +14 -5
  83. package/v2Containers/CreativesContainer/SlideBoxHeader.js +36 -5
  84. package/v2Containers/CreativesContainer/constants.js +9 -0
  85. package/v2Containers/CreativesContainer/embeddedSlideboxUtils.js +79 -0
  86. package/v2Containers/CreativesContainer/index.js +382 -127
  87. package/v2Containers/CreativesContainer/index.scss +83 -1
  88. package/v2Containers/CreativesContainer/tests/SlideBoxContent.localTemplates.test.js +90 -0
  89. package/v2Containers/CreativesContainer/tests/SlideBoxFooter.test.js +79 -34
  90. package/v2Containers/CreativesContainer/tests/SlideBoxHeader.test.js +79 -16
  91. package/v2Containers/CreativesContainer/tests/__snapshots__/SlideBoxContent.test.js.snap +8 -0
  92. package/v2Containers/CreativesContainer/tests/__snapshots__/SlideBoxHeader.test.js.snap +357 -98
  93. package/v2Containers/CreativesContainer/tests/__snapshots__/index.test.js.snap +20 -15
  94. package/v2Containers/CreativesContainer/tests/embeddedSlideboxUtils.test.js +258 -0
  95. package/v2Containers/CreativesContainer/tests/index.test.js +71 -9
  96. package/v2Containers/CreativesContainer/tests/useLocalTemplatesProp.test.js +125 -0
  97. package/v2Containers/MobilePush/Create/test/saga.test.js +2 -2
  98. package/v2Containers/Rcs/constants.js +120 -11
  99. package/v2Containers/Rcs/index.js +2577 -812
  100. package/v2Containers/Rcs/index.scss +281 -8
  101. package/v2Containers/Rcs/messages.js +34 -3
  102. package/v2Containers/Rcs/rcsLibraryHydrationUtils.js +225 -0
  103. package/v2Containers/Rcs/tests/__snapshots__/index.test.js.snap +98036 -70145
  104. package/v2Containers/Rcs/tests/__snapshots__/utils.test.js.snap +0 -5
  105. package/v2Containers/Rcs/tests/index.test.js +152 -121
  106. package/v2Containers/Rcs/tests/mockData.js +38 -0
  107. package/v2Containers/Rcs/tests/rcsLibraryHydrationUtils.test.js +318 -0
  108. package/v2Containers/Rcs/tests/utils.test.js +646 -30
  109. package/v2Containers/Rcs/utils.js +478 -11
  110. package/v2Containers/Sms/Create/index.js +106 -40
  111. package/v2Containers/Sms/smsFormDataHelpers.js +67 -0
  112. package/v2Containers/Sms/tests/smsFormDataHelpers.test.js +253 -0
  113. package/v2Containers/SmsTrai/Create/index.js +9 -4
  114. package/v2Containers/SmsTrai/Edit/constants.js +2 -0
  115. package/v2Containers/SmsTrai/Edit/index.js +640 -130
  116. package/v2Containers/SmsTrai/Edit/index.scss +121 -0
  117. package/v2Containers/SmsTrai/Edit/messages.js +14 -4
  118. package/v2Containers/SmsTrai/Edit/tests/__snapshots__/index.test.js.snap +4328 -2375
  119. package/v2Containers/SmsWrapper/index.js +37 -8
  120. package/v2Containers/TagList/index.js +6 -0
  121. package/v2Containers/Templates/TemplatesActionBar.js +101 -0
  122. package/v2Containers/Templates/_templates.scss +166 -86
  123. package/v2Containers/Templates/actions.js +11 -0
  124. package/v2Containers/Templates/constants.js +2 -0
  125. package/v2Containers/Templates/index.js +203 -145
  126. package/v2Containers/Templates/sagas.js +62 -13
  127. package/v2Containers/Templates/tests/TemplatesActionBar.test.js +120 -0
  128. package/v2Containers/Templates/tests/__snapshots__/index.test.js.snap +1062 -1017
  129. package/v2Containers/Templates/tests/sagas.test.js +222 -22
  130. package/v2Containers/Templates/tests/smsTemplatesListApi.test.js +180 -0
  131. package/v2Containers/Templates/tests/webpush.test.js +375 -0
  132. package/v2Containers/Templates/utils/smsTemplatesListApi.js +79 -0
  133. package/v2Containers/TemplatesV2/TemplatesV2.style.js +72 -1
  134. package/v2Containers/TemplatesV2/index.js +86 -23
  135. package/v2Containers/TemplatesV2/tests/TemplatesV2.localTemplates.test.js +131 -0
  136. package/v2Containers/Viber/constants.js +0 -19
  137. package/v2Containers/Viber/index.js +47 -714
  138. package/v2Containers/Viber/index.scss +0 -148
  139. package/v2Containers/Viber/messages.js +0 -116
  140. package/v2Containers/Viber/tests/index.test.js +0 -80
  141. package/v2Containers/WeChat/MapTemplates/test/saga.test.js +9 -9
  142. package/v2Containers/WebPush/Create/index.js +91 -8
  143. package/v2Containers/WebPush/Create/index.scss +7 -0
  144. package/v2Containers/WebPush/Create/tests/getTemplateContent.test.js +348 -0
  145. package/v2Containers/WebPush/Create/tests/testAndPreviewIntegration.test.js +325 -0
  146. package/v2Containers/Whatsapp/index.js +3 -20
  147. package/v2Containers/Whatsapp/tests/__snapshots__/index.test.js.snap +578 -34
@@ -0,0 +1,375 @@
1
+ /**
2
+ * Tests for Templates container - WEBPUSH channel additions
3
+ *
4
+ * Covers:
5
+ * - extractTemplateContentForPreview for WEBPUSH channel
6
+ * - isTestAndPreviewSupported with WEBPUSH channel
7
+ */
8
+
9
+ import React from 'react';
10
+ import { shallowWithIntl } from '../../../helpers/intl-enzym-test-helpers';
11
+ import { Templates } from '../index';
12
+
13
+ jest.mock('../../CreativesContainer', () => ({
14
+ __esModule: true,
15
+ default: (props) => (
16
+ <div className="creatives-container-mock" {...props}>
17
+ CreativesContainer
18
+ </div>
19
+ ),
20
+ }));
21
+
22
+ // Helper: build a valid template for WEBPUSH
23
+ // extractTemplateContentForPreview reads:
24
+ // - template.versions.base.content.webpush → webpushContent
25
+ // - template.definition.accountId → accountId
26
+ // - template.name → templateName
27
+ const makeTemplate = (webpush = {}, extra = {}) => ({
28
+ name: 'My Template',
29
+ definition: { accountId: 'acc-123' },
30
+ versions: {
31
+ base: {
32
+ content: { webpush },
33
+ },
34
+ },
35
+ ...extra,
36
+ });
37
+
38
+ describe('Templates - WEBPUSH channel', () => {
39
+ const mockActions = {
40
+ getWeCrmAccounts: jest.fn(),
41
+ setChannelAccount: jest.fn(),
42
+ getAllTemplates: jest.fn(),
43
+ getUserList: jest.fn(),
44
+ getSenderDetails: jest.fn(),
45
+ resetTemplate: jest.fn(),
46
+ setArchivedMode: jest.fn(),
47
+ clearTemplateSelection: jest.fn(),
48
+ toggleTemplateSelection: jest.fn(),
49
+ };
50
+
51
+ const baseProps = {
52
+ route: { name: 'webpush' },
53
+ Templates: { templates: [] },
54
+ actions: mockActions,
55
+ location: { pathname: '/webpush', query: {}, search: '' },
56
+ EmailCreate: { duplicateTemplateInProgress: false },
57
+ isFullMode: false,
58
+ intl: { formatMessage: jest.fn((msg) => msg.defaultMessage || '') },
59
+ };
60
+
61
+ beforeEach(() => {
62
+ jest.clearAllMocks();
63
+ });
64
+
65
+ const renderComponent = (channel = 'webpush') => {
66
+ const props = { ...baseProps, route: { name: channel } };
67
+ return shallowWithIntl(<Templates {...props} />);
68
+ };
69
+
70
+ // ─────────────────────────────────────────────────────────────────────────
71
+ describe('isTestAndPreviewSupported', () => {
72
+ it('should return true for WEBPUSH channel', () => {
73
+ const component = renderComponent('webpush');
74
+ component.setState({ channel: 'webpush' });
75
+ expect(component.instance().isTestAndPreviewSupported()).toBe(true);
76
+ });
77
+
78
+ it('should return true for WEBPUSH in uppercase state', () => {
79
+ const component = renderComponent('webpush');
80
+ component.setState({ channel: 'WEBPUSH' });
81
+ expect(component.instance().isTestAndPreviewSupported()).toBe(true);
82
+ });
83
+
84
+ it('should return true for EMAIL channel', () => {
85
+ const component = renderComponent('webpush');
86
+ component.setState({ channel: 'email' });
87
+ expect(component.instance().isTestAndPreviewSupported()).toBe(true);
88
+ });
89
+
90
+ it('should return true for SMS channel', () => {
91
+ const component = renderComponent('webpush');
92
+ component.setState({ channel: 'sms' });
93
+ expect(component.instance().isTestAndPreviewSupported()).toBe(true);
94
+ });
95
+
96
+ it('should return true for INAPP channel', () => {
97
+ const component = renderComponent('webpush');
98
+ component.setState({ channel: 'inapp' });
99
+ expect(component.instance().isTestAndPreviewSupported()).toBe(true);
100
+ });
101
+
102
+ it('should return true for VIBER channel', () => {
103
+ const component = renderComponent('webpush');
104
+ component.setState({ channel: 'viber' });
105
+ expect(component.instance().isTestAndPreviewSupported()).toBe(true);
106
+ });
107
+
108
+ it('should return true for ZALO channel', () => {
109
+ const component = renderComponent('webpush');
110
+ component.setState({ channel: 'zalo' });
111
+ expect(component.instance().isTestAndPreviewSupported()).toBe(true);
112
+ });
113
+
114
+ it('should return false for WHATSAPP channel', () => {
115
+ const component = renderComponent('webpush');
116
+ component.setState({ channel: 'whatsapp' });
117
+ expect(component.instance().isTestAndPreviewSupported()).toBe(false);
118
+ });
119
+
120
+ it('should return false for RCS channel', () => {
121
+ const component = renderComponent('webpush');
122
+ component.setState({ channel: 'rcs' });
123
+ expect(component.instance().isTestAndPreviewSupported()).toBe(false);
124
+ });
125
+ });
126
+
127
+ // ─────────────────────────────────────────────────────────────────────────
128
+ describe('extractTemplateContentForPreview - WEBPUSH channel', () => {
129
+ it('should return null when template has no versions.base', () => {
130
+ const consoleSpy = jest.spyOn(console, 'warn').mockImplementation(() => {});
131
+ const component = renderComponent('webpush');
132
+ const result = component.instance().extractTemplateContentForPreview(
133
+ { name: 'T', definition: {} },
134
+ 'WEBPUSH'
135
+ );
136
+ expect(result).toBeNull();
137
+ consoleSpy.mockRestore();
138
+ });
139
+
140
+ it('should extract title and message from versions.base.content.webpush', () => {
141
+ const component = renderComponent('webpush');
142
+ const template = makeTemplate({ title: 'Push Title', message: 'Push body' });
143
+
144
+ const result = component.instance().extractTemplateContentForPreview(template, 'WEBPUSH');
145
+ expect(result).toBeDefined();
146
+ expect(result.channel).toBe('WEBPUSH');
147
+ expect(result.accountId).toBe('acc-123');
148
+ expect(result.content.title).toBe('Push Title');
149
+ expect(result.content.message).toBe('Push body');
150
+ expect(result.messageSubject).toBe('My Template');
151
+ expect(result.offers).toEqual([]);
152
+ });
153
+
154
+ it('should fall back to title for messageSubject when template name is empty', () => {
155
+ const component = renderComponent('webpush');
156
+ const template = makeTemplate({ title: 'Title Only', message: 'M' }, { name: '' });
157
+
158
+ const result = component.instance().extractTemplateContentForPreview(template, 'WEBPUSH');
159
+ expect(result.messageSubject).toBe('Title Only');
160
+ });
161
+
162
+ it('should extract brandIcon as iconImageUrl', () => {
163
+ const component = renderComponent('webpush');
164
+ const template = makeTemplate({
165
+ title: 'T',
166
+ message: 'M',
167
+ brandIcon: 'https://example.com/brand.png',
168
+ });
169
+
170
+ const result = component.instance().extractTemplateContentForPreview(template, 'WEBPUSH');
171
+ expect(result.content.iconImageUrl).toBe('https://example.com/brand.png');
172
+ });
173
+
174
+ it('should use iconImageUrl field when brandIcon is absent', () => {
175
+ const component = renderComponent('webpush');
176
+ const template = makeTemplate({
177
+ title: 'T',
178
+ message: 'M',
179
+ iconImageUrl: 'https://example.com/icon.png',
180
+ });
181
+
182
+ const result = component.instance().extractTemplateContentForPreview(template, 'WEBPUSH');
183
+ expect(result.content.iconImageUrl).toBe('https://example.com/icon.png');
184
+ });
185
+
186
+ it('should NOT include iconImageUrl when neither brandIcon nor iconImageUrl present', () => {
187
+ const component = renderComponent('webpush');
188
+ const template = makeTemplate({ title: 'T', message: 'M' });
189
+
190
+ const result = component.instance().extractTemplateContentForPreview(template, 'WEBPUSH');
191
+ expect(result.content.iconImageUrl).toBeUndefined();
192
+ });
193
+
194
+ it('should convert onClickAction type URL to EXTERNAL_URL cta', () => {
195
+ const component = renderComponent('webpush');
196
+ const template = makeTemplate({
197
+ title: 'T',
198
+ message: 'M',
199
+ onClickAction: { type: 'URL', url: 'https://example.com' },
200
+ });
201
+
202
+ const result = component.instance().extractTemplateContentForPreview(template, 'WEBPUSH');
203
+ expect(result.content.cta).toEqual({ type: 'EXTERNAL_URL', actionLink: 'https://example.com' });
204
+ });
205
+
206
+ it('should preserve onClickAction type SITE_URL as-is', () => {
207
+ const component = renderComponent('webpush');
208
+ const template = makeTemplate({
209
+ title: 'T',
210
+ message: 'M',
211
+ onClickAction: { type: 'SITE_URL', url: 'https://site.com' },
212
+ });
213
+
214
+ const result = component.instance().extractTemplateContentForPreview(template, 'WEBPUSH');
215
+ expect(result.content.cta).toEqual({ type: 'SITE_URL', actionLink: 'https://site.com' });
216
+ });
217
+
218
+ it('should use existingCta when onClickAction is absent', () => {
219
+ const component = renderComponent('webpush');
220
+ const template = makeTemplate({
221
+ title: 'T',
222
+ message: 'M',
223
+ cta: { type: 'EXTERNAL_URL', actionLink: 'https://existing.com' },
224
+ });
225
+
226
+ const result = component.instance().extractTemplateContentForPreview(template, 'WEBPUSH');
227
+ expect(result.content.cta).toEqual({ type: 'EXTERNAL_URL', actionLink: 'https://existing.com' });
228
+ });
229
+
230
+ it('should default existingCta type to EXTERNAL_URL when type is missing', () => {
231
+ const component = renderComponent('webpush');
232
+ const template = makeTemplate({
233
+ title: 'T',
234
+ message: 'M',
235
+ cta: { actionLink: 'https://example.com' },
236
+ });
237
+
238
+ const result = component.instance().extractTemplateContentForPreview(template, 'WEBPUSH');
239
+ expect(result.content.cta.type).toBe('EXTERNAL_URL');
240
+ });
241
+
242
+ it('should NOT include cta when neither onClickAction nor cta present', () => {
243
+ const component = renderComponent('webpush');
244
+ const template = makeTemplate({ title: 'T', message: 'M' });
245
+
246
+ const result = component.instance().extractTemplateContentForPreview(template, 'WEBPUSH');
247
+ expect(result.content.cta).toBeUndefined();
248
+ });
249
+
250
+ it('should build expandableDetails from image and ctas', () => {
251
+ const component = renderComponent('webpush');
252
+ const template = makeTemplate({
253
+ title: 'T',
254
+ message: 'M',
255
+ image: 'https://example.com/image.jpg',
256
+ ctas: [{ type: 'URL', actionText: 'Click', actionLink: 'https://a.com', action: '' }],
257
+ });
258
+
259
+ const result = component.instance().extractTemplateContentForPreview(template, 'WEBPUSH');
260
+ expect(result.content.expandableDetails).toBeDefined();
261
+ expect(result.content.expandableDetails.media).toEqual([
262
+ { url: 'https://example.com/image.jpg', type: 'IMAGE' },
263
+ ]);
264
+ expect(result.content.expandableDetails.ctas[0].title).toBe('Click');
265
+ expect(result.content.expandableDetails.ctas[0].type).toBe('EXTERNAL_URL');
266
+ });
267
+
268
+ it('should convert CTA type URL → EXTERNAL_URL in expandableDetails.ctas', () => {
269
+ const component = renderComponent('webpush');
270
+ const template = makeTemplate({
271
+ title: 'T',
272
+ message: 'M',
273
+ ctas: [{ type: 'URL', actionText: 'Go', actionLink: 'https://x.com' }],
274
+ });
275
+
276
+ const result = component.instance().extractTemplateContentForPreview(template, 'WEBPUSH');
277
+ expect(result.content.expandableDetails.ctas[0].type).toBe('EXTERNAL_URL');
278
+ });
279
+
280
+ it('should keep EXTERNAL_URL type unchanged in expandableDetails.ctas', () => {
281
+ const component = renderComponent('webpush');
282
+ const template = makeTemplate({
283
+ title: 'T',
284
+ message: 'M',
285
+ ctas: [{ type: 'EXTERNAL_URL', title: 'Go', actionLink: 'https://x.com' }],
286
+ });
287
+
288
+ const result = component.instance().extractTemplateContentForPreview(template, 'WEBPUSH');
289
+ expect(result.content.expandableDetails.ctas[0].type).toBe('EXTERNAL_URL');
290
+ });
291
+
292
+ it('should build image-only expandableDetails when no ctas', () => {
293
+ const component = renderComponent('webpush');
294
+ const template = makeTemplate({
295
+ title: 'T',
296
+ message: 'M',
297
+ image: 'https://example.com/img.jpg',
298
+ });
299
+
300
+ const result = component.instance().extractTemplateContentForPreview(template, 'WEBPUSH');
301
+ expect(result.content.expandableDetails.media).toHaveLength(1);
302
+ expect(result.content.expandableDetails.ctas).toEqual([]);
303
+ });
304
+
305
+ it('should build ctas-only expandableDetails when no image', () => {
306
+ const component = renderComponent('webpush');
307
+ const template = makeTemplate({
308
+ title: 'T',
309
+ message: 'M',
310
+ ctas: [{ type: 'EXTERNAL_URL', actionText: 'Btn', actionLink: 'https://b.com' }],
311
+ });
312
+
313
+ const result = component.instance().extractTemplateContentForPreview(template, 'WEBPUSH');
314
+ expect(result.content.expandableDetails.media).toEqual([]);
315
+ expect(result.content.expandableDetails.ctas).toHaveLength(1);
316
+ });
317
+
318
+ it('should use existingExpandable when no image and no ctas', () => {
319
+ const component = renderComponent('webpush');
320
+ const existingExpandable = {
321
+ media: [{ url: 'https://example.com/existing.jpg', type: 'IMAGE' }],
322
+ ctas: [{ title: 'Existing', actionLink: 'https://existing.com', type: 'EXTERNAL_URL' }],
323
+ };
324
+ const template = makeTemplate({
325
+ title: 'T',
326
+ message: 'M',
327
+ expandableDetails: existingExpandable,
328
+ });
329
+
330
+ const result = component.instance().extractTemplateContentForPreview(template, 'WEBPUSH');
331
+ expect(result.content.expandableDetails).toEqual(existingExpandable);
332
+ });
333
+
334
+ it('should NOT include expandableDetails when none present', () => {
335
+ const component = renderComponent('webpush');
336
+ const template = makeTemplate({ title: 'T', message: 'M' });
337
+
338
+ const result = component.instance().extractTemplateContentForPreview(template, 'WEBPUSH');
339
+ expect(result.content.expandableDetails).toBeUndefined();
340
+ });
341
+
342
+ it('should handle empty webpush content gracefully', () => {
343
+ const component = renderComponent('webpush');
344
+ const template = makeTemplate({}, { name: '' });
345
+
346
+ const result = component.instance().extractTemplateContentForPreview(template, 'WEBPUSH');
347
+ expect(result).toBeDefined();
348
+ expect(result.content.title).toBe('');
349
+ expect(result.content.message).toBe('');
350
+ });
351
+
352
+ it('should handle missing definition.accountId gracefully (returns null)', () => {
353
+ const component = renderComponent('webpush');
354
+ const template = {
355
+ name: 'T',
356
+ versions: { base: { content: { webpush: { title: 'T', message: 'M' } } } },
357
+ // no definition
358
+ };
359
+
360
+ const result = component.instance().extractTemplateContentForPreview(template, 'WEBPUSH');
361
+ expect(result).toBeDefined();
362
+ expect(result.accountId).toBeNull();
363
+ });
364
+
365
+ it('should return null for unsupported channel', () => {
366
+ const consoleSpy = jest.spyOn(console, 'warn').mockImplementation(() => {});
367
+ const component = renderComponent('webpush');
368
+ const template = makeTemplate({ title: 'T', message: 'M' });
369
+
370
+ const result = component.instance().extractTemplateContentForPreview(template, 'UNSUPPORTED_CHANNEL');
371
+ expect(result).toBeNull();
372
+ consoleSpy.mockRestore();
373
+ });
374
+ });
375
+ });
@@ -0,0 +1,79 @@
1
+ import get from 'lodash/get';
2
+ import * as Api from '../../../services/api';
3
+ import { COPY_OF } from '../../../constants/unified';
4
+ import { isTraiDLTEnable } from '../../../utils/common';
5
+
6
+ /** Matches Templates `getAllTemplates` default for SMS. */
7
+ export const SMS_TEMPLATES_LIST_SORT_MOST_RECENT = 'Most Recent';
8
+
9
+ /**
10
+ * Same query shape as Redux `GET_ALL_TEMPLATES` for channel Sms (DLT vs non-DLT via traiEnable).
11
+ */
12
+ export function buildSmsTemplatesListQueryParams({
13
+ page,
14
+ perPage,
15
+ name = '',
16
+ sortBy = SMS_TEMPLATES_LIST_SORT_MOST_RECENT,
17
+ isFullMode,
18
+ smsRegister,
19
+ }) {
20
+ const traiDlt = isTraiDLTEnable(isFullMode, smsRegister);
21
+ return {
22
+ page,
23
+ perPage,
24
+ sortBy,
25
+ name: name || '',
26
+ ...(traiDlt ? { traiEnable: true } : {}),
27
+ };
28
+ }
29
+
30
+ /**
31
+ * SMS list for Redux saga: uses queryParams already built by Templates (incl. traiEnable).
32
+ * Applies the same "Copy of" → intl label as the former inline saga logic.
33
+ */
34
+ export async function fetchSmsTemplatesFromQuery(queryParams, intlCopyOf = '') {
35
+ const raw = await Api.getAllTemplates({ channel: 'Sms', queryParams });
36
+ const response = raw.response || {};
37
+ let templates = get(response, 'templates', []) || [];
38
+ if (intlCopyOf && templates.length) {
39
+ templates = templates.map((template) => ({
40
+ ...template,
41
+ name: (template.name || '').replace(new RegExp(COPY_OF, 'g'), intlCopyOf),
42
+ }));
43
+ }
44
+ const channelTemplates = { ...response, templates };
45
+ return {
46
+ channelTemplates,
47
+ weCRMTemplate: response.unMapped,
48
+ raw,
49
+ };
50
+ }
51
+
52
+ /**
53
+ * SMS list for the RCS SMS fallback picker only.
54
+ * Called from `useLocalTemplateList` in SmsFallback — keeps data in component state and pairs with
55
+ * `localTemplatesConfig` / `useLocalTemplates` in TemplatesV2. Does **not** use GET_ALL_TEMPLATES saga.
56
+ * Same HTTP call shape as `fetchSmsTemplatesFromQuery` (used by the main SMS GET_ALL_TEMPLATES saga).
57
+ */
58
+ export async function fetchSmsTemplatesListPage({
59
+ page,
60
+ perPage,
61
+ name,
62
+ sortBy,
63
+ isFullMode,
64
+ smsRegister,
65
+ intlCopyOf = '',
66
+ }) {
67
+ const queryParams = buildSmsTemplatesListQueryParams({
68
+ page,
69
+ perPage,
70
+ name,
71
+ sortBy,
72
+ isFullMode,
73
+ smsRegister,
74
+ });
75
+ const { channelTemplates } = await fetchSmsTemplatesFromQuery(queryParams, intlCopyOf);
76
+ const templates = channelTemplates.templates || [];
77
+ const totalCount = get(channelTemplates, 'totalCount', get(channelTemplates, 'total', 0)) || 0;
78
+ return { templates, totalCount };
79
+ }
@@ -10,7 +10,7 @@ export default css`
10
10
 
11
11
  .component-wrapper {
12
12
  ${(props) => props.isFullMode ? `
13
- max-width: 1140px;
13
+ max-width: 71.25rem;
14
14
  margin: 0 auto;
15
15
  width: 100%;
16
16
  padding: 0.714rem 0;
@@ -23,6 +23,77 @@ export default css`
23
23
  height: calc(100vh - 11.25rem);
24
24
  } `}
25
25
  }
26
+
27
+ /* SMS fallback / local list: single pane skips .cap-tab-v2, so flex-fill the grid instead of viewport-fixed pagination height */
28
+ .creatives-templates-container--local-sms.library-mode {
29
+ display: flex;
30
+ flex-direction: column;
31
+ flex: 1;
32
+ min-height: 0;
33
+ height: 100%;
34
+
35
+ .component-wrapper {
36
+ display: flex;
37
+ flex-direction: column;
38
+ flex: 1;
39
+ min-height: 0;
40
+ height: 100%;
41
+ }
42
+
43
+ .templates-v2-local-sms-pane {
44
+ display: flex;
45
+ flex-direction: column;
46
+ flex: 1;
47
+ min-height: 0;
48
+ overflow: hidden;
49
+ height: 100%;
50
+ }
51
+
52
+ .templates-v2-local-sms-pane .creatives-templates-list.library-mode {
53
+ display: flex;
54
+ flex-direction: column;
55
+ flex: 1;
56
+ min-height: 0;
57
+ overflow: hidden;
58
+ height: 100%;
59
+ }
60
+
61
+ .templates-v2-local-sms-pane .creatives-templates-list.library-mode > .cap-row:first-of-type {
62
+ flex: 1;
63
+ min-height: 0;
64
+ display: flex;
65
+ flex-direction: column;
66
+ overflow: hidden;
67
+ }
68
+
69
+ .templates-v2-local-sms-pane .creatives-templates-list.library-mode > .cap-row:first-of-type > div {
70
+ flex: 1;
71
+ min-height: 0;
72
+ display: flex;
73
+ flex-direction: column;
74
+ overflow: hidden;
75
+ }
76
+
77
+ .templates-v2-local-sms-pane .creatives-templates-list.library-mode > .cap-row:first-of-type > div > div:first-child {
78
+ flex: 1;
79
+ min-height: 0;
80
+ display: flex;
81
+ flex-direction: column;
82
+ overflow: hidden;
83
+ }
84
+
85
+ .templates-v2-local-sms-pane .v2-pagination-container,
86
+ .templates-v2-local-sms-pane .v2-pagination-container-half {
87
+ /* Match _templates local-SMS: bounded height so overflow-y scroll works (flex+100% alone often doesn’t) */
88
+ flex: 0 1 auto;
89
+ min-height: 0;
90
+ height: calc(100vh - 12rem);
91
+ max-height: calc(100vh - 12rem);
92
+ overflow-y: auto;
93
+ overflow-x: hidden;
94
+ -webkit-overflow-scrolling: touch;
95
+ }
96
+ }
26
97
  `;
27
98
 
28
99
  export const CapTabStyle = css`