@capillarytech/creatives-library 8.0.353-alpha.3 → 8.0.353-alpha.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/v2Components/CommonTestAndPreview/UnifiedPreview/PreviewHeader.js +17 -0
- package/v2Components/CommonTestAndPreview/UnifiedPreview/ViberPreviewContent.js +14 -132
- package/v2Components/CommonTestAndPreview/UnifiedPreview/WebPushPreviewContent.js +169 -0
- package/v2Components/CommonTestAndPreview/UnifiedPreview/_unifiedPreview.scss +70 -163
- package/v2Components/CommonTestAndPreview/UnifiedPreview/index.js +44 -5
- package/v2Components/CommonTestAndPreview/constants.js +2 -0
- package/v2Components/CommonTestAndPreview/index.js +58 -49
- package/v2Components/CommonTestAndPreview/tests/UnifiedPreview/PreviewHeader.test.js +159 -0
- package/v2Components/CommonTestAndPreview/tests/UnifiedPreview/ViberPreviewContent.test.js +0 -364
- package/v2Components/CommonTestAndPreview/tests/UnifiedPreview/WebPushPreviewContent.test.js +522 -0
- package/v2Components/CommonTestAndPreview/tests/UnifiedPreview/index.test.js +255 -0
- package/v2Components/CommonTestAndPreview/tests/constants.test.js +2 -1
- package/v2Components/CommonTestAndPreview/tests/index.test.js +194 -0
- package/v2Components/TestAndPreviewSlidebox/index.js +2 -2
- package/v2Containers/App/constants.js +3 -0
- package/v2Containers/App/tests/constants.test.js +61 -0
- package/v2Containers/Templates/_templates.scss +0 -77
- package/v2Containers/Templates/index.js +70 -77
- package/v2Containers/Templates/tests/webpush.test.js +375 -0
- package/v2Containers/Viber/constants.js +0 -19
- package/v2Containers/Viber/index.js +47 -714
- package/v2Containers/Viber/index.scss +0 -148
- package/v2Containers/Viber/messages.js +0 -116
- package/v2Containers/Viber/tests/index.test.js +0 -80
- package/v2Containers/WebPush/Create/index.js +91 -8
- package/v2Containers/WebPush/Create/index.scss +7 -0
- package/v2Containers/WebPush/Create/tests/getTemplateContent.test.js +338 -0
- package/v2Containers/WebPush/Create/tests/testAndPreviewIntegration.test.js +325 -0
|
@@ -72,7 +72,6 @@ import * as ebillActions from '../Ebill/actions';
|
|
|
72
72
|
import * as emailActions from '../Email/actions';
|
|
73
73
|
import * as lineActions from '../Line/Container/actions';
|
|
74
74
|
import * as viberActions from '../Viber/actions';
|
|
75
|
-
import { VIBER_MEDIA_TYPES } from '../Viber/constants';
|
|
76
75
|
import * as facebookActions from '../Facebook/actions';
|
|
77
76
|
import * as whatsappActions from '../Whatsapp/actions';
|
|
78
77
|
import * as rcsActions from '../Rcs/actions';
|
|
@@ -105,6 +104,9 @@ import {
|
|
|
105
104
|
VIBER as VIBER_CHANNEL,
|
|
106
105
|
FACEBOOK as FACEBOOK_CHANNEL,
|
|
107
106
|
CREATE,
|
|
107
|
+
EXTERNAL_URL,
|
|
108
|
+
URL,
|
|
109
|
+
SITE_URL,
|
|
108
110
|
} from '../App/constants';
|
|
109
111
|
import {MAX_WHATSAPP_TEMPLATES, WARNING_WHATSAPP_TEMPLATES , ACCOUNT_MAPPING_ON_CHANNEL, noFilteredWhatsappZaloTemplatesTitle, noFilteredWhatsappZaloTemplatesDesc, noApprovedWhatsappZaloTemplatesTitle, noApprovedWhatsappTemplatesDesc, zaloDescIllustration, noApprovedRcsTemplatesTitle, noApprovedRcsTemplatesDesc, ARCHIVE_STATUS_ACTIVE, ARCHIVE_STATUS_ARCHIVED, ARCHIVE_REFRESH_TYPE_ARCHIVE, ARCHIVE_REFRESH_TYPE_UNARCHIVE} from './constants';
|
|
110
112
|
import { COPY_OF, EMBEDDED } from '../../constants/unified';
|
|
@@ -1372,11 +1374,6 @@ export class Templates extends React.Component { // eslint-disable-line react/pr
|
|
|
1372
1374
|
const image = viberContent.image || {};
|
|
1373
1375
|
const video = viberContent.video || {};
|
|
1374
1376
|
const button = viberContent.button || {};
|
|
1375
|
-
const messageType = viberContent.type || '';
|
|
1376
|
-
const cardsRaw = viberContent.cards;
|
|
1377
|
-
const cards = Array.isArray(cardsRaw) ? cardsRaw : [];
|
|
1378
|
-
const isCarousel =
|
|
1379
|
-
messageType === VIBER_MEDIA_TYPES.CAROUSEL || cards.length > 0;
|
|
1380
1377
|
|
|
1381
1378
|
const viberPreview = {
|
|
1382
1379
|
messageContent: text,
|
|
@@ -1394,39 +1391,77 @@ export class Templates extends React.Component { // eslint-disable-line react/pr
|
|
|
1394
1391
|
};
|
|
1395
1392
|
}
|
|
1396
1393
|
|
|
1397
|
-
if (isCarousel) {
|
|
1398
|
-
viberPreview.type = VIBER_MEDIA_TYPES.CAROUSEL;
|
|
1399
|
-
viberPreview.cards = cards.map((card) => ({
|
|
1400
|
-
text: card?.text || '',
|
|
1401
|
-
mediaUrl: card?.mediaUrl || '',
|
|
1402
|
-
buttons: (card?.buttons || []).map((btn) => ({
|
|
1403
|
-
title: btn?.title || '',
|
|
1404
|
-
action: btn?.action || '',
|
|
1405
|
-
})),
|
|
1406
|
-
}));
|
|
1407
|
-
viberPreview.showCarouselEditorPreview = true;
|
|
1408
|
-
}
|
|
1409
|
-
|
|
1410
1394
|
// Extract account name
|
|
1411
1395
|
const accountName = get(template, 'definition.accountName', '');
|
|
1412
1396
|
|
|
1413
1397
|
// Return Viber content object (same as Viber/index.js getTemplateContent)
|
|
1414
1398
|
return {
|
|
1415
1399
|
viberPreviewContent: viberPreview,
|
|
1416
|
-
accountName: accountName
|
|
1417
|
-
brandName: accountName
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1400
|
+
accountName: accountName ? [accountName] : [],
|
|
1401
|
+
brandName: accountName ? [accountName] : [],
|
|
1402
|
+
};
|
|
1403
|
+
}
|
|
1404
|
+
|
|
1405
|
+
case WEBPUSH: {
|
|
1406
|
+
// WebPush content is stored in creatives format (brandIcon, onClickAction, ctas)
|
|
1407
|
+
// Must be transformed to campaign/test-message format matching getTemplateContent() in WebPush/Create/index.js
|
|
1408
|
+
const webpushContent = get(baseContent, 'content.webpush', {});
|
|
1409
|
+
const title = webpushContent?.title || '';
|
|
1410
|
+
const message = webpushContent?.message || '';
|
|
1411
|
+
const accountId = get(template, 'definition.accountId', null);
|
|
1412
|
+
const templateName = template?.name || '';
|
|
1413
|
+
|
|
1414
|
+
// iconImageUrl stored as brandIcon in creatives format
|
|
1415
|
+
const iconImageUrl = webpushContent?.brandIcon || webpushContent?.iconImageUrl || undefined;
|
|
1416
|
+
|
|
1417
|
+
// cta stored as onClickAction in creatives format (type: URL|SITE_URL, url)
|
|
1418
|
+
// or already as cta in campaign format (type: EXTERNAL_URL|SITE_URL, actionLink)
|
|
1419
|
+
let cta = null;
|
|
1420
|
+
const onClickAction = webpushContent?.onClickAction;
|
|
1421
|
+
const existingCta = webpushContent?.cta;
|
|
1422
|
+
if (onClickAction) {
|
|
1423
|
+
const ctaType = onClickAction.type === URL ? EXTERNAL_URL : (onClickAction.type || SITE_URL);
|
|
1424
|
+
cta = { type: ctaType, actionLink: onClickAction.url || '' };
|
|
1425
|
+
} else if (existingCta) {
|
|
1426
|
+
cta = { type: existingCta.type || EXTERNAL_URL, actionLink: existingCta.actionLink || '' };
|
|
1427
|
+
}
|
|
1428
|
+
|
|
1429
|
+
// expandableDetails: image → media[], ctas[] → mapped ctas
|
|
1430
|
+
const image = webpushContent?.image;
|
|
1431
|
+
const rawCtas = webpushContent?.ctas;
|
|
1432
|
+
const existingExpandable = webpushContent?.expandableDetails;
|
|
1433
|
+
let expandableDetails = null;
|
|
1434
|
+
const hasImage = !!image;
|
|
1435
|
+
const hasCtas = Array.isArray(rawCtas) && rawCtas.length > 0;
|
|
1436
|
+
if (hasImage || hasCtas) {
|
|
1437
|
+
expandableDetails = {
|
|
1438
|
+
media: hasImage ? [{ url: image, type: IMAGE }] : [],
|
|
1439
|
+
ctas: hasCtas ? rawCtas.map((ctaItem) => ({
|
|
1440
|
+
type: ctaItem?.type === URL ? EXTERNAL_URL : (ctaItem?.type || EXTERNAL_URL),
|
|
1441
|
+
action: ctaItem?.action || '',
|
|
1442
|
+
title: ctaItem?.actionText || ctaItem?.title || '',
|
|
1443
|
+
actionLink: ctaItem?.actionLink || '',
|
|
1444
|
+
})) : [],
|
|
1445
|
+
};
|
|
1446
|
+
} else if (existingExpandable) {
|
|
1447
|
+
expandableDetails = {
|
|
1448
|
+
media: existingExpandable?.media || [],
|
|
1449
|
+
ctas: existingExpandable?.ctas || [],
|
|
1450
|
+
};
|
|
1451
|
+
}
|
|
1452
|
+
|
|
1453
|
+
return {
|
|
1454
|
+
channel: WEBPUSH,
|
|
1455
|
+
accountId,
|
|
1456
|
+
content: {
|
|
1457
|
+
title,
|
|
1458
|
+
message,
|
|
1459
|
+
...(iconImageUrl ? { iconImageUrl } : {}),
|
|
1460
|
+
...(cta ? { cta } : {}),
|
|
1461
|
+
...(expandableDetails ? { expandableDetails } : {}),
|
|
1462
|
+
},
|
|
1463
|
+
messageSubject: templateName || title,
|
|
1464
|
+
offers: [],
|
|
1430
1465
|
};
|
|
1431
1466
|
}
|
|
1432
1467
|
|
|
@@ -1442,7 +1477,7 @@ export class Templates extends React.Component { // eslint-disable-line react/pr
|
|
|
1442
1477
|
* @returns {Boolean} - True if channel supports Test and Preview
|
|
1443
1478
|
*/
|
|
1444
1479
|
isTestAndPreviewSupported = () => {
|
|
1445
|
-
const supportedChannels = [EMAIL, SMS, INAPP, MOBILE_PUSH, VIBER, ZALO];
|
|
1480
|
+
const supportedChannels = [EMAIL, SMS, INAPP, MOBILE_PUSH, VIBER, ZALO, WEBPUSH];
|
|
1446
1481
|
return supportedChannels.includes(this.state.channel.toUpperCase());
|
|
1447
1482
|
}
|
|
1448
1483
|
|
|
@@ -2002,7 +2037,7 @@ export class Templates extends React.Component { // eslint-disable-line react/pr
|
|
|
2002
2037
|
// Show preview icon only for channels that don't support Test and Preview
|
|
2003
2038
|
(() => {
|
|
2004
2039
|
// Channels that have Test and Preview integrated
|
|
2005
|
-
const testAndPreviewChannels = [EMAIL, SMS, WHATSAPP, RCS, INAPP, MOBILE_PUSH, VIBER, ZALO];
|
|
2040
|
+
const testAndPreviewChannels = [EMAIL, SMS, WHATSAPP, RCS, INAPP, MOBILE_PUSH, VIBER, ZALO, WEBPUSH];
|
|
2006
2041
|
const isTestAndPreviewSupported = testAndPreviewChannels.includes(currentChannel.toUpperCase());
|
|
2007
2042
|
|
|
2008
2043
|
// Don't show preview icon if channel supports Test and Preview
|
|
@@ -2372,52 +2407,10 @@ export class Templates extends React.Component { // eslint-disable-line react/pr
|
|
|
2372
2407
|
image = {},
|
|
2373
2408
|
button = {},
|
|
2374
2409
|
video = {},
|
|
2375
|
-
type = '',
|
|
2376
|
-
cards = [],
|
|
2377
2410
|
} = {},
|
|
2378
2411
|
} = {},
|
|
2379
2412
|
} = template.versions.base;
|
|
2380
|
-
const isViberCarousel = type === VIBER_MEDIA_TYPES.CAROUSEL;
|
|
2381
2413
|
templateData.content = text;
|
|
2382
|
-
if (isViberCarousel) {
|
|
2383
|
-
const previewCards = Array.isArray(cards) ? cards.slice(0, 1) : [];
|
|
2384
|
-
templateData.className = 'viber-carousel-static';
|
|
2385
|
-
templateData.content = (
|
|
2386
|
-
<CapRow className="viber-carousel-static-content">
|
|
2387
|
-
<CapRow className="viber-carousel-static-message-box">
|
|
2388
|
-
<CapLabel type="label1" className="viber-carousel-static-message">
|
|
2389
|
-
{text}
|
|
2390
|
-
</CapLabel>
|
|
2391
|
-
</CapRow>
|
|
2392
|
-
<CapRow className="viber-carousel-static-cards">
|
|
2393
|
-
{previewCards.map((card, cardIdx) => (
|
|
2394
|
-
<CapRow className="viber-carousel-static-card" key={`viber-static-card-${cardIdx}`}>
|
|
2395
|
-
{card?.mediaUrl ? (
|
|
2396
|
-
<CapImage src={card.mediaUrl} className="viber-carousel-static-image" />
|
|
2397
|
-
) : (
|
|
2398
|
-
<CapRow className="viber-carousel-static-image-placeholder" />
|
|
2399
|
-
)}
|
|
2400
|
-
<CapLabel type="label1" className="viber-carousel-static-text">
|
|
2401
|
-
{card?.text}
|
|
2402
|
-
</CapLabel>
|
|
2403
|
-
<CapRow className="viber-carousel-static-buttons">
|
|
2404
|
-
{(Array.isArray(card?.buttons) && card.buttons.length ? card.buttons : [{}, {}]).slice(0, 2).map((carouselButton, btnIdx) => (
|
|
2405
|
-
<CapLabel
|
|
2406
|
-
key={`viber-static-btn-${cardIdx}-${btnIdx}`}
|
|
2407
|
-
type="label1"
|
|
2408
|
-
className={`viber-carousel-static-button ${btnIdx === 1 ? 'viber-carousel-static-button-secondary' : ''}`}
|
|
2409
|
-
>
|
|
2410
|
-
{(carouselButton?.title || '').trim()}
|
|
2411
|
-
</CapLabel>
|
|
2412
|
-
))}
|
|
2413
|
-
</CapRow>
|
|
2414
|
-
</CapRow>
|
|
2415
|
-
))}
|
|
2416
|
-
</CapRow>
|
|
2417
|
-
</CapRow>
|
|
2418
|
-
);
|
|
2419
|
-
break;
|
|
2420
|
-
}
|
|
2421
2414
|
if (!isEmpty(image)) {
|
|
2422
2415
|
const { url = '' } = image;
|
|
2423
2416
|
templateData.url = url;
|
|
@@ -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
|
+
});
|
|
@@ -47,22 +47,7 @@ export const VIBER_MEDIA_TYPES = {
|
|
|
47
47
|
TEXT: 'TEXT',
|
|
48
48
|
IMAGE: 'IMAGE',
|
|
49
49
|
VIDEO: 'VIDEO',
|
|
50
|
-
CAROUSEL: 'CAROUSEL',
|
|
51
50
|
};
|
|
52
|
-
export const VIBER_CAROUSEL_MIN_CARDS = 2;
|
|
53
|
-
export const VIBER_CAROUSEL_MAX_CARDS = 5;
|
|
54
|
-
export const VIBER_CAROUSEL_MAX_BUTTONS = 2;
|
|
55
|
-
export const VIBER_CAROUSEL_CARD_TITLE_MIN_LENGTH = 2;
|
|
56
|
-
export const VIBER_CAROUSEL_CARD_TITLE_MAX_LENGTH = 38;
|
|
57
|
-
export const VIBER_CAROUSEL_FIRST_BUTTON_TITLE_MAX_LENGTH = 10;
|
|
58
|
-
export const VIBER_CAROUSEL_SECOND_BUTTON_TITLE_MAX_LENGTH = 12;
|
|
59
|
-
export const VIBER_CAROUSEL_BUTTON_URL_MAX_LENGTH = 1000;
|
|
60
|
-
/** Recommended / validated carousel image height in pixels (passed to image upload). */
|
|
61
|
-
export const VIBER_CAROUSEL_IMG_HEIGHT = 600;
|
|
62
|
-
/** Recommended / validated carousel image width in pixels (passed to image upload). */
|
|
63
|
-
export const VIBER_CAROUSEL_IMG_WIDTH = 696;
|
|
64
|
-
/** Maximum carousel image file size (bytes). 10_000_000 ≈ 10 MB (decimal). */
|
|
65
|
-
export const VIBER_CAROUSEL_IMG_SIZE = 10000000;
|
|
66
51
|
export const ALLOWED_IMAGE_EXTENSIONS_REGEX_VIBER = /\.(jpe?g|png)$/i;
|
|
67
52
|
export const ALLOWED_EXTENSIONS_VIDEO_REGEX_VIBER = /\.(mp4|3gp|m4v|mov)$/i;
|
|
68
53
|
export const NONE = 'NONE';
|
|
@@ -84,10 +69,6 @@ export const mediaRadioOptions = [
|
|
|
84
69
|
value: VIBER_MEDIA_TYPES.VIDEO,
|
|
85
70
|
label: <FormattedMessage {...messages.mediaVideo} />,
|
|
86
71
|
},
|
|
87
|
-
{
|
|
88
|
-
value: VIBER_MEDIA_TYPES.CAROUSEL,
|
|
89
|
-
label: <FormattedMessage {...messages.mediaCarousel} />,
|
|
90
|
-
},
|
|
91
72
|
];
|
|
92
73
|
|
|
93
74
|
export const buttonRadioOptions = [
|