@capillarytech/creatives-library 9.0.29 → 9.0.31
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/constants/unified.js +29 -0
- package/global-styles.js +1 -1
- package/package.json +1 -1
- package/services/tests/api.test.js +35 -20
- package/utils/commonUtils.js +22 -1
- package/utils/rcsPayloadUtils.js +102 -0
- package/utils/templateVarUtils.js +198 -0
- package/utils/tests/rcsPayloadUtils.test.js +295 -0
- package/utils/tests/templateVarUtils.test.js +204 -0
- package/v2Components/CapActionButton/constants.js +21 -1
- package/v2Components/CapActionButton/index.js +261 -144
- package/v2Components/CapActionButton/index.scss +245 -14
- package/v2Components/CapActionButton/messages.js +32 -3
- package/v2Components/CapActionButton/tests/index.test.js +74 -19
- package/v2Components/CapImageUpload/index.js +2 -2
- package/v2Components/CapTagList/index.js +9 -0
- package/v2Components/CommonTestAndPreview/CustomValuesEditor.js +70 -48
- package/v2Components/CommonTestAndPreview/DeliverySettings/DeliverySettings.scss +8 -2
- package/v2Components/CommonTestAndPreview/DeliverySettings/ModifyDeliverySettings.js +214 -21
- package/v2Components/CommonTestAndPreview/DeliverySettings/constants.js +16 -0
- package/v2Components/CommonTestAndPreview/DeliverySettings/index.js +83 -9
- package/v2Components/CommonTestAndPreview/DeliverySettings/messages.js +30 -0
- package/v2Components/CommonTestAndPreview/DeliverySettings/utils/parseSenderDetailsResponse.js +58 -11
- package/v2Components/CommonTestAndPreview/SendTestMessage.js +10 -5
- package/v2Components/CommonTestAndPreview/UnifiedPreview/RcsPreviewContent.js +186 -22
- package/v2Components/CommonTestAndPreview/UnifiedPreview/_unifiedPreview.scss +381 -80
- package/v2Components/CommonTestAndPreview/UnifiedPreview/index.js +155 -8
- package/v2Components/CommonTestAndPreview/_commonTestAndPreview.scss +11 -0
- package/v2Components/CommonTestAndPreview/constants.js +35 -2
- package/v2Components/CommonTestAndPreview/index.js +774 -231
- package/v2Components/CommonTestAndPreview/messages.js +45 -3
- package/v2Components/CommonTestAndPreview/previewApiUtils.js +59 -0
- package/v2Components/CommonTestAndPreview/sagas.js +25 -6
- package/v2Components/CommonTestAndPreview/tests/CommonTestAndPreview.addTestCustomer.test.js +1 -0
- package/v2Components/CommonTestAndPreview/tests/CustomValuesEditor.test.js +412 -257
- package/v2Components/CommonTestAndPreview/tests/DeliverySettings/ModifyDeliverySettings.test.js +337 -63
- package/v2Components/CommonTestAndPreview/tests/DeliverySettings/index.test.js +118 -5
- package/v2Components/CommonTestAndPreview/tests/DeliverySettings/utils/parseSenderDetailsResponse.test.js +341 -0
- package/v2Components/CommonTestAndPreview/tests/PreviewSection.test.js +8 -1
- package/v2Components/CommonTestAndPreview/tests/SendTestMessage.test.js +108 -15
- package/v2Components/CommonTestAndPreview/tests/UnifiedPreview/RcsPreviewContent.test.js +281 -283
- package/v2Components/CommonTestAndPreview/tests/UnifiedPreview/index.test.js +199 -1
- package/v2Components/CommonTestAndPreview/tests/index.test.js +133 -4
- package/v2Components/CommonTestAndPreview/tests/previewApiUtils.test.js +67 -0
- package/v2Components/CommonTestAndPreview/tests/sagas.test.js +31 -24
- package/v2Components/CommonTestAndPreview/tests/utils.test.js +151 -0
- package/v2Components/CommonTestAndPreview/utils.js +84 -0
- package/v2Components/SmsFallback/SmsFallbackLocalSelector.js +91 -0
- package/v2Components/SmsFallback/constants.js +94 -0
- package/v2Components/SmsFallback/index.js +958 -0
- package/v2Components/SmsFallback/index.scss +266 -0
- package/v2Components/SmsFallback/messages.js +78 -0
- package/v2Components/SmsFallback/smsFallbackUtils.js +120 -0
- package/v2Components/SmsFallback/tests/SmsFallbackLocalSelector.test.js +50 -0
- package/v2Components/SmsFallback/tests/rcsSmsFallback.acceptance.test.js +147 -0
- package/v2Components/SmsFallback/tests/smsFallbackHandlers.test.js +304 -0
- package/v2Components/SmsFallback/tests/smsFallbackUi.test.js +208 -0
- package/v2Components/SmsFallback/tests/smsFallbackUtils.test.js +309 -0
- package/v2Components/SmsFallback/tests/useLocalTemplateList.test.js +471 -0
- package/v2Components/SmsFallback/useLocalTemplateList.js +92 -0
- package/v2Components/TemplatePreview/_templatePreview.scss +45 -25
- package/v2Components/TemplatePreview/constants.js +2 -0
- package/v2Components/TemplatePreview/index.js +147 -32
- package/v2Components/TemplatePreview/tests/index.test.js +142 -0
- package/v2Components/TestAndPreviewSlidebox/CustomValuesEditor.js +6 -6
- package/v2Components/TestAndPreviewSlidebox/index.js +13 -1
- package/v2Components/TestAndPreviewSlidebox/sagas.js +11 -4
- package/v2Components/TestAndPreviewSlidebox/tests/saga.test.js +3 -1
- package/v2Components/VarSegmentMessageEditor/constants.js +2 -0
- package/v2Components/VarSegmentMessageEditor/index.js +125 -0
- package/v2Components/VarSegmentMessageEditor/index.scss +46 -0
- package/v2Containers/CommunicationFlow/Tests/CommunicationFlow.test.js +4 -1
- package/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/Tests/DeliverySettingsSection.test.js +4 -0
- package/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/Tests/SenderDetails.test.js +20 -20
- package/v2Containers/CreativesContainer/CreativesSlideBoxWrapper.js +27 -0
- package/v2Containers/CreativesContainer/SlideBoxContent.js +37 -4
- package/v2Containers/CreativesContainer/SlideBoxFooter.js +14 -5
- package/v2Containers/CreativesContainer/SlideBoxHeader.js +36 -5
- package/v2Containers/CreativesContainer/constants.js +11 -0
- package/v2Containers/CreativesContainer/embeddedSlideboxUtils.js +77 -0
- package/v2Containers/CreativesContainer/index.js +322 -106
- package/v2Containers/CreativesContainer/index.scss +102 -1
- package/v2Containers/CreativesContainer/tests/CreativesSlideBoxWrapper.test.js +58 -0
- package/v2Containers/CreativesContainer/tests/SlideBoxContent.localTemplates.test.js +90 -0
- package/v2Containers/CreativesContainer/tests/SlideBoxContent.smsDltPreview.test.js +73 -0
- package/v2Containers/CreativesContainer/tests/SlideBoxContent.test.js +37 -0
- package/v2Containers/CreativesContainer/tests/SlideBoxFooter.test.js +103 -34
- package/v2Containers/CreativesContainer/tests/SlideBoxHeader.test.js +193 -15
- package/v2Containers/CreativesContainer/tests/__snapshots__/SlideBoxContent.test.js.snap +88 -0
- package/v2Containers/CreativesContainer/tests/__snapshots__/SlideBoxHeader.test.js.snap +549 -57
- package/v2Containers/CreativesContainer/tests/__snapshots__/index.test.js.snap +20 -15
- package/v2Containers/CreativesContainer/tests/embeddedSlideboxUtils.test.js +258 -0
- package/v2Containers/CreativesContainer/tests/index.test.js +71 -9
- package/v2Containers/CreativesContainer/tests/useLocalTemplatesProp.test.js +125 -0
- package/v2Containers/MobilePush/Create/test/saga.test.js +2 -2
- package/v2Containers/Rcs/carouselUtils.js +224 -0
- package/v2Containers/Rcs/components/CarouselCard.js +317 -0
- package/v2Containers/Rcs/components/CarouselCardButtons.js +113 -0
- package/v2Containers/Rcs/components/CarouselCardMedia.js +136 -0
- package/v2Containers/Rcs/components/CarouselCharacterCount.js +31 -0
- package/v2Containers/Rcs/components/CarouselDimensionSelection.js +80 -0
- package/v2Containers/Rcs/constants.js +132 -16
- package/v2Containers/Rcs/index.js +1822 -946
- package/v2Containers/Rcs/index.scss +443 -8
- package/v2Containers/Rcs/messages.js +45 -22
- package/v2Containers/Rcs/rcsLibraryHydrationUtils.js +494 -0
- package/v2Containers/Rcs/tests/CarouselCard.test.js +464 -0
- package/v2Containers/Rcs/tests/CarouselCardButtons.test.js +211 -0
- package/v2Containers/Rcs/tests/CarouselCardMedia.test.js +160 -0
- package/v2Containers/Rcs/tests/CarouselCharacterCount.test.js +50 -0
- package/v2Containers/Rcs/tests/CarouselDimensionSelection.test.js +119 -0
- package/v2Containers/Rcs/tests/__snapshots__/index.test.js.snap +74440 -39387
- package/v2Containers/Rcs/tests/__snapshots__/utils.test.js.snap +10 -74
- package/v2Containers/Rcs/tests/carouselUtils.test.js +916 -0
- package/v2Containers/Rcs/tests/index.test.js +275 -40
- package/v2Containers/Rcs/tests/mockData.js +38 -0
- package/v2Containers/Rcs/tests/rcsLibraryHydrationUtils.test.js +912 -0
- package/v2Containers/Rcs/tests/utils.test.js +682 -30
- package/v2Containers/Rcs/utils.js +514 -12
- package/v2Containers/Sms/Create/index.js +115 -48
- package/v2Containers/Sms/smsFormDataHelpers.js +67 -0
- package/v2Containers/Sms/tests/smsFormDataHelpers.test.js +253 -0
- package/v2Containers/SmsTrai/Create/index.js +9 -4
- package/v2Containers/SmsTrai/Edit/constants.js +2 -0
- package/v2Containers/SmsTrai/Edit/index.js +678 -169
- package/v2Containers/SmsTrai/Edit/index.scss +126 -0
- package/v2Containers/SmsTrai/Edit/messages.js +14 -4
- package/v2Containers/SmsTrai/Edit/tests/__snapshots__/index.test.js.snap +5615 -3014
- package/v2Containers/SmsWrapper/index.js +37 -8
- package/v2Containers/TagList/index.js +5 -0
- package/v2Containers/Templates/TemplatesActionBar.js +101 -0
- package/v2Containers/Templates/_templates.scss +196 -12
- package/v2Containers/Templates/actions.js +11 -0
- package/v2Containers/Templates/constants.js +2 -0
- package/v2Containers/Templates/index.js +131 -59
- package/v2Containers/Templates/sagas.js +57 -13
- package/v2Containers/Templates/tests/TemplatesActionBar.test.js +129 -0
- package/v2Containers/Templates/tests/__snapshots__/index.test.js.snap +1060 -1015
- package/v2Containers/Templates/tests/sagas.test.js +199 -16
- package/v2Containers/Templates/tests/smsTemplatesListApi.test.js +180 -0
- package/v2Containers/Templates/utils/smsTemplatesListApi.js +79 -0
- package/v2Containers/TemplatesV2/TemplatesV2.style.js +72 -1
- package/v2Containers/TemplatesV2/index.js +88 -25
- package/v2Containers/TemplatesV2/tests/TemplatesV2.localTemplates.test.js +131 -0
- package/v2Containers/WeChat/MapTemplates/test/saga.test.js +9 -9
- package/v2Containers/Whatsapp/index.js +3 -20
- package/v2Containers/Whatsapp/tests/__snapshots__/index.test.js.snap +578 -34
|
@@ -0,0 +1,916 @@
|
|
|
1
|
+
import {
|
|
2
|
+
getCarouselImageAssetIndex,
|
|
3
|
+
getCarouselVideoAssetIndex,
|
|
4
|
+
getCarouselThumbnailAssetIndex,
|
|
5
|
+
isThumbnailAssetIndex,
|
|
6
|
+
getCarouselDimensionKey,
|
|
7
|
+
ensureFirstCardDefaultPhoneSuggestions,
|
|
8
|
+
carouselButtonTextHasForbiddenChars,
|
|
9
|
+
isCompleteSavedCarouselSuggestion,
|
|
10
|
+
isCarouselCardValid,
|
|
11
|
+
checkDisableAddCarouselButton,
|
|
12
|
+
getNextCarouselVarToken,
|
|
13
|
+
getCarouselTitleCharacterCount,
|
|
14
|
+
getCarouselDescriptionCharacterCount,
|
|
15
|
+
buildCarouselCardsForPreview,
|
|
16
|
+
buildCarouselCardContentForPayload,
|
|
17
|
+
} from '../carouselUtils';
|
|
18
|
+
import { RCS_CTA_URL_TYPE } from '../../../v2Components/CapActionButton/constants';
|
|
19
|
+
import {
|
|
20
|
+
RCS_MEDIA_TYPES,
|
|
21
|
+
RCS_BUTTON_TYPES,
|
|
22
|
+
RCS_CAROUSEL_ASSET_INDEX_BASE,
|
|
23
|
+
RCS_CAROUSEL_FIRST_CARD_DEFAULT_SUGGESTIONS,
|
|
24
|
+
TEMPLATE_TITLE_MAX_LENGTH,
|
|
25
|
+
RCS_RICH_CARD_MAX_LENGTH,
|
|
26
|
+
rcsVarRegex,
|
|
27
|
+
MEDIUM,
|
|
28
|
+
} from '../constants';
|
|
29
|
+
|
|
30
|
+
describe('getCarouselImageAssetIndex', () => {
|
|
31
|
+
it('returns base index for card 0', () => {
|
|
32
|
+
expect(getCarouselImageAssetIndex(0)).toBe(RCS_CAROUSEL_ASSET_INDEX_BASE);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it('returns base + 3*cardIndex for later cards', () => {
|
|
36
|
+
expect(getCarouselImageAssetIndex(1)).toBe(RCS_CAROUSEL_ASSET_INDEX_BASE + 3);
|
|
37
|
+
expect(getCarouselImageAssetIndex(3)).toBe(RCS_CAROUSEL_ASSET_INDEX_BASE + 9);
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
describe('getCarouselVideoAssetIndex', () => {
|
|
42
|
+
it('returns base + 3*cardIndex + 1', () => {
|
|
43
|
+
expect(getCarouselVideoAssetIndex(0)).toBe(RCS_CAROUSEL_ASSET_INDEX_BASE + 1);
|
|
44
|
+
expect(getCarouselVideoAssetIndex(2)).toBe(RCS_CAROUSEL_ASSET_INDEX_BASE + 7);
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
describe('getCarouselThumbnailAssetIndex', () => {
|
|
49
|
+
it('returns base + 3*cardIndex + 2', () => {
|
|
50
|
+
expect(getCarouselThumbnailAssetIndex(0)).toBe(RCS_CAROUSEL_ASSET_INDEX_BASE + 2);
|
|
51
|
+
expect(getCarouselThumbnailAssetIndex(2)).toBe(RCS_CAROUSEL_ASSET_INDEX_BASE + 8);
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
describe('isThumbnailAssetIndex', () => {
|
|
56
|
+
it('returns true for the standalone thumbnail index 1', () => {
|
|
57
|
+
expect(isThumbnailAssetIndex(1)).toBe(true);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it('returns true for carousel thumbnail slots (base + 2, base + 5, ...)', () => {
|
|
61
|
+
expect(isThumbnailAssetIndex(RCS_CAROUSEL_ASSET_INDEX_BASE + 2)).toBe(true);
|
|
62
|
+
expect(isThumbnailAssetIndex(RCS_CAROUSEL_ASSET_INDEX_BASE + 5)).toBe(true);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it('returns false for carousel image/video slots', () => {
|
|
66
|
+
expect(isThumbnailAssetIndex(RCS_CAROUSEL_ASSET_INDEX_BASE)).toBe(false);
|
|
67
|
+
expect(isThumbnailAssetIndex(RCS_CAROUSEL_ASSET_INDEX_BASE + 1)).toBe(false);
|
|
68
|
+
expect(isThumbnailAssetIndex(RCS_CAROUSEL_ASSET_INDEX_BASE + 3)).toBe(false);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it('returns false for standalone indices below the carousel base other than 1', () => {
|
|
72
|
+
expect(isThumbnailAssetIndex(0)).toBe(false);
|
|
73
|
+
expect(isThumbnailAssetIndex(2)).toBe(false);
|
|
74
|
+
expect(isThumbnailAssetIndex(9)).toBe(false);
|
|
75
|
+
});
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
describe('getCarouselDimensionKey', () => {
|
|
79
|
+
it('joins height and width with underscore', () => {
|
|
80
|
+
expect(getCarouselDimensionKey('SHORT', 'SMALL')).toBe('SHORT_SMALL');
|
|
81
|
+
expect(getCarouselDimensionKey('MEDIUM', 'MEDIUM')).toBe('MEDIUM_MEDIUM');
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
describe('ensureFirstCardDefaultPhoneSuggestions', () => {
|
|
86
|
+
it('returns empty array when cards is null, undefined, or empty', () => {
|
|
87
|
+
expect(ensureFirstCardDefaultPhoneSuggestions(null)).toEqual([]);
|
|
88
|
+
expect(ensureFirstCardDefaultPhoneSuggestions(undefined)).toEqual([]);
|
|
89
|
+
expect(ensureFirstCardDefaultPhoneSuggestions([])).toEqual([]);
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it('adds default suggestions to first card when suggestions is missing', () => {
|
|
93
|
+
const out = ensureFirstCardDefaultPhoneSuggestions([{ title: 'A' }]);
|
|
94
|
+
expect(out[0].suggestions).toEqual(RCS_CAROUSEL_FIRST_CARD_DEFAULT_SUGGESTIONS);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it('adds default suggestions to first card when suggestions is an empty array', () => {
|
|
98
|
+
const out = ensureFirstCardDefaultPhoneSuggestions([{ title: 'A', suggestions: [] }]);
|
|
99
|
+
expect(out[0].suggestions).toEqual(RCS_CAROUSEL_FIRST_CARD_DEFAULT_SUGGESTIONS);
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
it('adds default suggestions to first card when suggestions is not an array', () => {
|
|
103
|
+
const out = ensureFirstCardDefaultPhoneSuggestions([{ title: 'A', suggestions: 'nope' }]);
|
|
104
|
+
expect(out[0].suggestions).toEqual(RCS_CAROUSEL_FIRST_CARD_DEFAULT_SUGGESTIONS);
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it('keeps existing first-card suggestions when already present', () => {
|
|
108
|
+
const existing = [{ type: RCS_BUTTON_TYPES.QUICK_REPLY, text: 'Hi' }];
|
|
109
|
+
const out = ensureFirstCardDefaultPhoneSuggestions([{ title: 'A', suggestions: existing }]);
|
|
110
|
+
expect(out[0].suggestions).toEqual(existing);
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it('does not touch cards after the first', () => {
|
|
114
|
+
const out = ensureFirstCardDefaultPhoneSuggestions([
|
|
115
|
+
{ title: 'A' },
|
|
116
|
+
{ title: 'B' },
|
|
117
|
+
]);
|
|
118
|
+
expect(out[1].suggestions).toBeUndefined();
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
it('does not mutate the original input array', () => {
|
|
122
|
+
const original = [{ title: 'A', suggestions: [{ type: RCS_BUTTON_TYPES.QUICK_REPLY }] }];
|
|
123
|
+
const out = ensureFirstCardDefaultPhoneSuggestions(original);
|
|
124
|
+
out[0].suggestions.push({ type: RCS_BUTTON_TYPES.CTA });
|
|
125
|
+
expect(original[0].suggestions.length).toBe(1);
|
|
126
|
+
});
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
describe('carouselButtonTextHasForbiddenChars', () => {
|
|
130
|
+
it('returns false for falsy values', () => {
|
|
131
|
+
expect(carouselButtonTextHasForbiddenChars('')).toBe(false);
|
|
132
|
+
expect(carouselButtonTextHasForbiddenChars(null)).toBe(false);
|
|
133
|
+
expect(carouselButtonTextHasForbiddenChars(undefined)).toBe(false);
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
it('returns true when value contains [ or ]', () => {
|
|
137
|
+
expect(carouselButtonTextHasForbiddenChars('Hello [there]')).toBe(true);
|
|
138
|
+
expect(carouselButtonTextHasForbiddenChars('Only [')).toBe(true);
|
|
139
|
+
expect(carouselButtonTextHasForbiddenChars('Only ]')).toBe(true);
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
it('returns false when the only braces are valid {{var}} tokens', () => {
|
|
143
|
+
expect(carouselButtonTextHasForbiddenChars('Hi {{1}}')).toBe(false);
|
|
144
|
+
expect(carouselButtonTextHasForbiddenChars('Plain text')).toBe(false);
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
it('returns true when a stray { or } remains after stripping valid variables', () => {
|
|
148
|
+
expect(carouselButtonTextHasForbiddenChars('Hi {{1}} extra {')).toBe(true);
|
|
149
|
+
expect(carouselButtonTextHasForbiddenChars('Hi {{1}} extra }')).toBe(true);
|
|
150
|
+
expect(carouselButtonTextHasForbiddenChars('just {')).toBe(true);
|
|
151
|
+
expect(carouselButtonTextHasForbiddenChars('just }')).toBe(true);
|
|
152
|
+
});
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
describe('isCompleteSavedCarouselSuggestion', () => {
|
|
156
|
+
it('returns false when suggestion is missing or not saved', () => {
|
|
157
|
+
expect(isCompleteSavedCarouselSuggestion(null)).toBe(false);
|
|
158
|
+
expect(isCompleteSavedCarouselSuggestion(undefined)).toBe(false);
|
|
159
|
+
expect(isCompleteSavedCarouselSuggestion({ isSaved: false })).toBe(false);
|
|
160
|
+
expect(isCompleteSavedCarouselSuggestion({})).toBe(false);
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
it('returns false when text is empty, whitespace-only, or invalid', () => {
|
|
164
|
+
expect(isCompleteSavedCarouselSuggestion({ isSaved: true, text: '' })).toBe(false);
|
|
165
|
+
expect(isCompleteSavedCarouselSuggestion({ isSaved: true, text: ' ' })).toBe(false);
|
|
166
|
+
expect(isCompleteSavedCarouselSuggestion({ isSaved: true, text: 'Bad!' })).toBe(false);
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
describe('PHONE_NUMBER suggestions', () => {
|
|
170
|
+
it('returns true when phoneNumber length is at least 5', () => {
|
|
171
|
+
expect(
|
|
172
|
+
isCompleteSavedCarouselSuggestion({
|
|
173
|
+
isSaved: true,
|
|
174
|
+
text: 'Call',
|
|
175
|
+
type: RCS_BUTTON_TYPES.PHONE_NUMBER,
|
|
176
|
+
phoneNumber: '12345',
|
|
177
|
+
}),
|
|
178
|
+
).toBe(true);
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
it('returns false when phoneNumber is too short or missing', () => {
|
|
182
|
+
expect(
|
|
183
|
+
isCompleteSavedCarouselSuggestion({
|
|
184
|
+
isSaved: true,
|
|
185
|
+
text: 'Call',
|
|
186
|
+
type: RCS_BUTTON_TYPES.PHONE_NUMBER,
|
|
187
|
+
phoneNumber: '123',
|
|
188
|
+
}),
|
|
189
|
+
).toBe(false);
|
|
190
|
+
expect(
|
|
191
|
+
isCompleteSavedCarouselSuggestion({
|
|
192
|
+
isSaved: true,
|
|
193
|
+
text: 'Call',
|
|
194
|
+
type: RCS_BUTTON_TYPES.PHONE_NUMBER,
|
|
195
|
+
}),
|
|
196
|
+
).toBe(false);
|
|
197
|
+
});
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
describe('CTA suggestions', () => {
|
|
201
|
+
it('returns false when url is empty or whitespace-only', () => {
|
|
202
|
+
expect(
|
|
203
|
+
isCompleteSavedCarouselSuggestion({
|
|
204
|
+
isSaved: true,
|
|
205
|
+
text: 'Go',
|
|
206
|
+
type: RCS_BUTTON_TYPES.CTA,
|
|
207
|
+
url: ' ',
|
|
208
|
+
}),
|
|
209
|
+
).toBe(false);
|
|
210
|
+
expect(
|
|
211
|
+
isCompleteSavedCarouselSuggestion({
|
|
212
|
+
isSaved: true,
|
|
213
|
+
text: 'Go',
|
|
214
|
+
type: RCS_BUTTON_TYPES.CTA,
|
|
215
|
+
}),
|
|
216
|
+
).toBe(false);
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
it('returns false when url exceeds max length', () => {
|
|
220
|
+
const longUrl = `https://example.com/${'a'.repeat(2000)}`;
|
|
221
|
+
expect(
|
|
222
|
+
isCompleteSavedCarouselSuggestion({
|
|
223
|
+
isSaved: true,
|
|
224
|
+
text: 'Go',
|
|
225
|
+
type: RCS_BUTTON_TYPES.CTA,
|
|
226
|
+
url: longUrl,
|
|
227
|
+
}),
|
|
228
|
+
).toBe(false);
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
it('returns true for DYNAMIC urlType regardless of url shape', () => {
|
|
232
|
+
expect(
|
|
233
|
+
isCompleteSavedCarouselSuggestion({
|
|
234
|
+
isSaved: true,
|
|
235
|
+
text: 'Go',
|
|
236
|
+
type: RCS_BUTTON_TYPES.CTA,
|
|
237
|
+
url: 'not-a-real-url',
|
|
238
|
+
urlType: RCS_CTA_URL_TYPE.DYNAMIC,
|
|
239
|
+
}),
|
|
240
|
+
).toBe(true);
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
it('returns false for STATIC (default) urlType when url is not a valid url', () => {
|
|
244
|
+
expect(
|
|
245
|
+
isCompleteSavedCarouselSuggestion({
|
|
246
|
+
isSaved: true,
|
|
247
|
+
text: 'Go',
|
|
248
|
+
type: RCS_BUTTON_TYPES.CTA,
|
|
249
|
+
url: 'not-a-real-url',
|
|
250
|
+
}),
|
|
251
|
+
).toBe(false);
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
it('returns false for STATIC urlType when the url contains a var token', () => {
|
|
255
|
+
expect(
|
|
256
|
+
isCompleteSavedCarouselSuggestion({
|
|
257
|
+
isSaved: true,
|
|
258
|
+
text: 'Go',
|
|
259
|
+
type: RCS_BUTTON_TYPES.CTA,
|
|
260
|
+
url: 'https://example.com/{{promo}}',
|
|
261
|
+
urlType: RCS_CTA_URL_TYPE.STATIC,
|
|
262
|
+
}),
|
|
263
|
+
).toBe(false);
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
it('returns true for STATIC urlType with a clean valid url', () => {
|
|
267
|
+
expect(
|
|
268
|
+
isCompleteSavedCarouselSuggestion({
|
|
269
|
+
isSaved: true,
|
|
270
|
+
text: 'Go',
|
|
271
|
+
type: RCS_BUTTON_TYPES.CTA,
|
|
272
|
+
url: 'https://example.com/page',
|
|
273
|
+
urlType: RCS_CTA_URL_TYPE.STATIC,
|
|
274
|
+
}),
|
|
275
|
+
).toBe(true);
|
|
276
|
+
});
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
it('returns true for QUICK_REPLY suggestions with valid text', () => {
|
|
280
|
+
expect(
|
|
281
|
+
isCompleteSavedCarouselSuggestion({
|
|
282
|
+
isSaved: true,
|
|
283
|
+
text: 'Yes please',
|
|
284
|
+
type: RCS_BUTTON_TYPES.QUICK_REPLY,
|
|
285
|
+
}),
|
|
286
|
+
).toBe(true);
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
it('returns false for unrecognized or NONE suggestion types', () => {
|
|
290
|
+
expect(
|
|
291
|
+
isCompleteSavedCarouselSuggestion({
|
|
292
|
+
isSaved: true,
|
|
293
|
+
text: 'Hi',
|
|
294
|
+
type: RCS_BUTTON_TYPES.NONE,
|
|
295
|
+
}),
|
|
296
|
+
).toBe(false);
|
|
297
|
+
expect(
|
|
298
|
+
isCompleteSavedCarouselSuggestion({
|
|
299
|
+
isSaved: true,
|
|
300
|
+
text: 'Hi',
|
|
301
|
+
type: 'SOMETHING_ELSE',
|
|
302
|
+
}),
|
|
303
|
+
).toBe(false);
|
|
304
|
+
});
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
describe('isCarouselCardValid', () => {
|
|
308
|
+
const validImageCard = {
|
|
309
|
+
title: 'Title',
|
|
310
|
+
description: 'Description',
|
|
311
|
+
mediaType: RCS_MEDIA_TYPES.IMAGE,
|
|
312
|
+
imageSrc: 'https://example.com/img.png',
|
|
313
|
+
};
|
|
314
|
+
|
|
315
|
+
it('returns false when card is missing', () => {
|
|
316
|
+
expect(isCarouselCardValid(null, 0)).toBe(false);
|
|
317
|
+
expect(isCarouselCardValid(undefined, 0)).toBe(false);
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
it('returns false when title is missing, empty, or whitespace-only', () => {
|
|
321
|
+
expect(isCarouselCardValid({ ...validImageCard, title: undefined }, 1)).toBe(false);
|
|
322
|
+
expect(isCarouselCardValid({ ...validImageCard, title: '' }, 1)).toBe(false);
|
|
323
|
+
expect(isCarouselCardValid({ ...validImageCard, title: ' ' }, 1)).toBe(false);
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
it('returns false when title exceeds TEMPLATE_TITLE_MAX_LENGTH', () => {
|
|
327
|
+
const longTitle = 'a'.repeat(TEMPLATE_TITLE_MAX_LENGTH + 1);
|
|
328
|
+
expect(isCarouselCardValid({ ...validImageCard, title: longTitle }, 1)).toBe(false);
|
|
329
|
+
});
|
|
330
|
+
|
|
331
|
+
it('accepts a title exactly at TEMPLATE_TITLE_MAX_LENGTH', () => {
|
|
332
|
+
const maxTitle = 'a'.repeat(TEMPLATE_TITLE_MAX_LENGTH);
|
|
333
|
+
expect(isCarouselCardValid({ ...validImageCard, title: maxTitle }, 1)).toBe(true);
|
|
334
|
+
});
|
|
335
|
+
|
|
336
|
+
it('returns false when description is missing, empty, or whitespace-only', () => {
|
|
337
|
+
expect(isCarouselCardValid({ ...validImageCard, description: undefined }, 1)).toBe(false);
|
|
338
|
+
expect(isCarouselCardValid({ ...validImageCard, description: '' }, 1)).toBe(false);
|
|
339
|
+
expect(isCarouselCardValid({ ...validImageCard, description: ' ' }, 1)).toBe(false);
|
|
340
|
+
});
|
|
341
|
+
|
|
342
|
+
it('returns false when description exceeds RCS_RICH_CARD_MAX_LENGTH', () => {
|
|
343
|
+
const longDesc = 'a'.repeat(RCS_RICH_CARD_MAX_LENGTH + 1);
|
|
344
|
+
expect(isCarouselCardValid({ ...validImageCard, description: longDesc }, 1)).toBe(false);
|
|
345
|
+
});
|
|
346
|
+
|
|
347
|
+
it('accepts a description exactly at RCS_RICH_CARD_MAX_LENGTH', () => {
|
|
348
|
+
const maxDesc = 'a'.repeat(RCS_RICH_CARD_MAX_LENGTH);
|
|
349
|
+
expect(isCarouselCardValid({ ...validImageCard, description: maxDesc }, 1)).toBe(true);
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
it('returns false for an unrecognized/NONE media type', () => {
|
|
353
|
+
expect(isCarouselCardValid({ ...validImageCard, mediaType: RCS_MEDIA_TYPES.NONE }, 1)).toBe(false);
|
|
354
|
+
expect(isCarouselCardValid({ ...validImageCard, mediaType: undefined }, 1)).toBe(false);
|
|
355
|
+
});
|
|
356
|
+
|
|
357
|
+
describe('IMAGE media', () => {
|
|
358
|
+
it('returns false when imageSrc is missing or blank', () => {
|
|
359
|
+
expect(isCarouselCardValid({ ...validImageCard, imageSrc: '' }, 1)).toBe(false);
|
|
360
|
+
expect(isCarouselCardValid({ ...validImageCard, imageSrc: ' ' }, 1)).toBe(false);
|
|
361
|
+
expect(isCarouselCardValid({ ...validImageCard, imageSrc: undefined }, 1)).toBe(false);
|
|
362
|
+
});
|
|
363
|
+
|
|
364
|
+
it('returns true when imageSrc is present', () => {
|
|
365
|
+
expect(isCarouselCardValid(validImageCard, 1)).toBe(true);
|
|
366
|
+
});
|
|
367
|
+
});
|
|
368
|
+
|
|
369
|
+
describe('VIDEO media', () => {
|
|
370
|
+
const baseVideoCard = {
|
|
371
|
+
title: 'Title',
|
|
372
|
+
description: 'Description',
|
|
373
|
+
mediaType: RCS_MEDIA_TYPES.VIDEO,
|
|
374
|
+
videoAsset: { videoSrc: 'https://example.com/v.mp4' },
|
|
375
|
+
thumbnailSrc: 'https://example.com/thumb.png',
|
|
376
|
+
};
|
|
377
|
+
|
|
378
|
+
it('returns true when both videoSrc and thumbnailSrc are present', () => {
|
|
379
|
+
expect(isCarouselCardValid(baseVideoCard, 1)).toBe(true);
|
|
380
|
+
});
|
|
381
|
+
|
|
382
|
+
it('returns false when videoAsset.videoSrc is missing', () => {
|
|
383
|
+
expect(isCarouselCardValid({ ...baseVideoCard, videoAsset: {} }, 1)).toBe(false);
|
|
384
|
+
expect(isCarouselCardValid({ ...baseVideoCard, videoAsset: undefined }, 1)).toBe(false);
|
|
385
|
+
});
|
|
386
|
+
|
|
387
|
+
it('returns false when thumbnailSrc is missing or blank', () => {
|
|
388
|
+
expect(isCarouselCardValid({ ...baseVideoCard, thumbnailSrc: '' }, 1)).toBe(false);
|
|
389
|
+
expect(isCarouselCardValid({ ...baseVideoCard, thumbnailSrc: ' ' }, 1)).toBe(false);
|
|
390
|
+
});
|
|
391
|
+
});
|
|
392
|
+
|
|
393
|
+
describe('first card suggestion requirement (cardIndex === 0)', () => {
|
|
394
|
+
it('returns false when there are no complete saved suggestions', () => {
|
|
395
|
+
expect(isCarouselCardValid({ ...validImageCard, suggestions: [] }, 0)).toBe(false);
|
|
396
|
+
expect(isCarouselCardValid({ ...validImageCard, suggestions: undefined }, 0)).toBe(false);
|
|
397
|
+
expect(
|
|
398
|
+
isCarouselCardValid(
|
|
399
|
+
{ ...validImageCard, suggestions: [{ isSaved: false }] },
|
|
400
|
+
0,
|
|
401
|
+
),
|
|
402
|
+
).toBe(false);
|
|
403
|
+
});
|
|
404
|
+
|
|
405
|
+
it('returns true when at least one suggestion is complete and saved', () => {
|
|
406
|
+
expect(
|
|
407
|
+
isCarouselCardValid(
|
|
408
|
+
{
|
|
409
|
+
...validImageCard,
|
|
410
|
+
suggestions: [
|
|
411
|
+
{
|
|
412
|
+
isSaved: true,
|
|
413
|
+
text: 'Yes',
|
|
414
|
+
type: RCS_BUTTON_TYPES.QUICK_REPLY,
|
|
415
|
+
},
|
|
416
|
+
],
|
|
417
|
+
},
|
|
418
|
+
0,
|
|
419
|
+
),
|
|
420
|
+
).toBe(true);
|
|
421
|
+
});
|
|
422
|
+
|
|
423
|
+
it('treats a non-array suggestions field as empty', () => {
|
|
424
|
+
expect(isCarouselCardValid({ ...validImageCard, suggestions: 'nope' }, 0)).toBe(false);
|
|
425
|
+
});
|
|
426
|
+
});
|
|
427
|
+
|
|
428
|
+
it('does not require suggestions for cards after the first', () => {
|
|
429
|
+
expect(isCarouselCardValid({ ...validImageCard, suggestions: [] }, 1)).toBe(true);
|
|
430
|
+
expect(isCarouselCardValid({ ...validImageCard, suggestions: undefined }, 2)).toBe(true);
|
|
431
|
+
});
|
|
432
|
+
});
|
|
433
|
+
|
|
434
|
+
describe('checkDisableAddCarouselButton', () => {
|
|
435
|
+
const validCard = {
|
|
436
|
+
title: 'Title',
|
|
437
|
+
description: 'Description',
|
|
438
|
+
mediaType: RCS_MEDIA_TYPES.IMAGE,
|
|
439
|
+
imageSrc: 'https://example.com/img.png',
|
|
440
|
+
suggestions: [{ isSaved: true, text: 'Yes', type: RCS_BUTTON_TYPES.QUICK_REPLY }],
|
|
441
|
+
};
|
|
442
|
+
|
|
443
|
+
it('returns false when the active card is valid', () => {
|
|
444
|
+
expect(checkDisableAddCarouselButton([validCard], 0)).toBe(false);
|
|
445
|
+
});
|
|
446
|
+
|
|
447
|
+
it('returns true when the active card is invalid', () => {
|
|
448
|
+
expect(checkDisableAddCarouselButton([{ title: '' }], 0)).toBe(true);
|
|
449
|
+
});
|
|
450
|
+
|
|
451
|
+
it('returns true when the active card is out of bounds', () => {
|
|
452
|
+
expect(checkDisableAddCarouselButton([validCard], 5)).toBe(true);
|
|
453
|
+
});
|
|
454
|
+
|
|
455
|
+
it('returns true when carouselData is null or undefined', () => {
|
|
456
|
+
expect(checkDisableAddCarouselButton(null, 0)).toBe(true);
|
|
457
|
+
expect(checkDisableAddCarouselButton(undefined, 0)).toBe(true);
|
|
458
|
+
});
|
|
459
|
+
|
|
460
|
+
it('parses a string activeCarouselIndex', () => {
|
|
461
|
+
expect(checkDisableAddCarouselButton([{}, validCard], '1')).toBe(false);
|
|
462
|
+
});
|
|
463
|
+
|
|
464
|
+
it('handles a non-numeric activeCarouselIndex as out of bounds', () => {
|
|
465
|
+
expect(checkDisableAddCarouselButton([validCard], 'abc')).toBe(true);
|
|
466
|
+
});
|
|
467
|
+
});
|
|
468
|
+
|
|
469
|
+
describe('getNextCarouselVarToken', () => {
|
|
470
|
+
it('returns {{1}} when carouselData is null, undefined, or empty', () => {
|
|
471
|
+
expect(getNextCarouselVarToken(null)).toBe('{{1}}');
|
|
472
|
+
expect(getNextCarouselVarToken(undefined)).toBe('{{1}}');
|
|
473
|
+
expect(getNextCarouselVarToken([])).toBe('{{1}}');
|
|
474
|
+
});
|
|
475
|
+
|
|
476
|
+
it('returns {{1}} when no cards contain numeric var tokens', () => {
|
|
477
|
+
expect(
|
|
478
|
+
getNextCarouselVarToken([{ title: 'Hello', description: 'World' }]),
|
|
479
|
+
).toBe('{{1}}');
|
|
480
|
+
});
|
|
481
|
+
|
|
482
|
+
it('handles cards with missing title/description gracefully', () => {
|
|
483
|
+
expect(getNextCarouselVarToken([{}, undefined])).toBe('{{1}}');
|
|
484
|
+
});
|
|
485
|
+
|
|
486
|
+
it('returns the next number after the highest used token', () => {
|
|
487
|
+
expect(
|
|
488
|
+
getNextCarouselVarToken([{ title: 'Hi {{1}}', description: '' }]),
|
|
489
|
+
).toBe('{{2}}');
|
|
490
|
+
});
|
|
491
|
+
|
|
492
|
+
it('fills a gap in used numbers', () => {
|
|
493
|
+
expect(
|
|
494
|
+
getNextCarouselVarToken([
|
|
495
|
+
{ title: 'Hi {{1}}', description: 'Bye {{3}}' },
|
|
496
|
+
]),
|
|
497
|
+
).toBe('{{2}}');
|
|
498
|
+
});
|
|
499
|
+
|
|
500
|
+
it('collects numeric tokens from title and description across multiple cards', () => {
|
|
501
|
+
expect(
|
|
502
|
+
getNextCarouselVarToken([
|
|
503
|
+
{ title: '{{1}}', description: '{{2}}' },
|
|
504
|
+
{ title: '{{4}}', description: '' },
|
|
505
|
+
]),
|
|
506
|
+
).toBe('{{3}}');
|
|
507
|
+
});
|
|
508
|
+
|
|
509
|
+
it('returns an empty string once all slots 1-19 are used', () => {
|
|
510
|
+
const allTokens = Array.from({ length: 19 }, (_, i) => `{{${i + 1}}}`).join(' ');
|
|
511
|
+
expect(
|
|
512
|
+
getNextCarouselVarToken([{ title: allTokens, description: '' }]),
|
|
513
|
+
).toBe('');
|
|
514
|
+
});
|
|
515
|
+
});
|
|
516
|
+
|
|
517
|
+
describe('getCarouselTitleCharacterCount', () => {
|
|
518
|
+
it('returns 0 when carouselData, card, or title is missing', () => {
|
|
519
|
+
expect(getCarouselTitleCharacterCount(undefined, 0)).toBe(0);
|
|
520
|
+
expect(getCarouselTitleCharacterCount([], 0)).toBe(0);
|
|
521
|
+
expect(getCarouselTitleCharacterCount([{}], 0)).toBe(0);
|
|
522
|
+
expect(getCarouselTitleCharacterCount([{ title: '' }], 0)).toBe(0);
|
|
523
|
+
});
|
|
524
|
+
|
|
525
|
+
it('returns the title length for the given card index', () => {
|
|
526
|
+
expect(getCarouselTitleCharacterCount([{ title: 'Hello' }], 0)).toBe(5);
|
|
527
|
+
expect(
|
|
528
|
+
getCarouselTitleCharacterCount([{ title: 'A' }, { title: 'Longer title' }], 1),
|
|
529
|
+
).toBe(12);
|
|
530
|
+
});
|
|
531
|
+
});
|
|
532
|
+
|
|
533
|
+
describe('getCarouselDescriptionCharacterCount', () => {
|
|
534
|
+
it('returns 0 when carouselData, card, or description is missing', () => {
|
|
535
|
+
expect(getCarouselDescriptionCharacterCount(undefined, 0)).toBe(0);
|
|
536
|
+
expect(getCarouselDescriptionCharacterCount([], 0)).toBe(0);
|
|
537
|
+
expect(getCarouselDescriptionCharacterCount([{}], 0)).toBe(0);
|
|
538
|
+
expect(getCarouselDescriptionCharacterCount([{ description: '' }], 0)).toBe(0);
|
|
539
|
+
});
|
|
540
|
+
|
|
541
|
+
it('returns the description length for the given card index', () => {
|
|
542
|
+
expect(getCarouselDescriptionCharacterCount([{ description: 'Hello' }], 0)).toBe(5);
|
|
543
|
+
expect(
|
|
544
|
+
getCarouselDescriptionCharacterCount(
|
|
545
|
+
[{ description: 'A' }, { description: 'Longer desc' }],
|
|
546
|
+
1,
|
|
547
|
+
),
|
|
548
|
+
).toBe(11);
|
|
549
|
+
});
|
|
550
|
+
});
|
|
551
|
+
|
|
552
|
+
describe('buildCarouselCardsForPreview', () => {
|
|
553
|
+
it('returns an empty array when cards is missing', () => {
|
|
554
|
+
expect(
|
|
555
|
+
buildCarouselCardsForPreview(undefined, {
|
|
556
|
+
isFullMode: false,
|
|
557
|
+
rcsVarRegex,
|
|
558
|
+
resolveTemplateWithMap: jest.fn(),
|
|
559
|
+
}),
|
|
560
|
+
).toEqual([]);
|
|
561
|
+
expect(
|
|
562
|
+
buildCarouselCardsForPreview([], {
|
|
563
|
+
isFullMode: false,
|
|
564
|
+
rcsVarRegex,
|
|
565
|
+
resolveTemplateWithMap: jest.fn(),
|
|
566
|
+
}),
|
|
567
|
+
).toEqual([]);
|
|
568
|
+
});
|
|
569
|
+
|
|
570
|
+
it('returns an empty array when cards is explicitly null', () => {
|
|
571
|
+
expect(
|
|
572
|
+
buildCarouselCardsForPreview(null, {
|
|
573
|
+
isFullMode: false,
|
|
574
|
+
rcsVarRegex,
|
|
575
|
+
resolveTemplateWithMap: jest.fn(),
|
|
576
|
+
}),
|
|
577
|
+
).toEqual([]);
|
|
578
|
+
});
|
|
579
|
+
|
|
580
|
+
it('passes title/description through unresolved in full mode without calling resolveTemplateWithMap', () => {
|
|
581
|
+
const resolveTemplateWithMap = jest.fn();
|
|
582
|
+
const out = buildCarouselCardsForPreview(
|
|
583
|
+
[{ title: 'Hi {{a}}', description: 'Bye {{b}}' }],
|
|
584
|
+
{ isFullMode: true, rcsVarRegex, resolveTemplateWithMap },
|
|
585
|
+
);
|
|
586
|
+
expect(resolveTemplateWithMap).not.toHaveBeenCalled();
|
|
587
|
+
expect(out[0].title).toBe('Hi {{a}}');
|
|
588
|
+
expect(out[0].bodyText).toBe('Bye {{b}}');
|
|
589
|
+
});
|
|
590
|
+
|
|
591
|
+
it('resolves title/description through resolveTemplateWithMap and accumulates slot offset across cards', () => {
|
|
592
|
+
const resolveTemplateWithMap = jest.fn((str, offset) => `R(${str}|${offset})`);
|
|
593
|
+
const out = buildCarouselCardsForPreview(
|
|
594
|
+
[
|
|
595
|
+
{ title: 'Hi {{a}}', description: 'Bye {{b}} {{c}}' },
|
|
596
|
+
{ title: 'Solo {{d}}', description: 'none' },
|
|
597
|
+
],
|
|
598
|
+
{ isFullMode: false, rcsVarRegex, resolveTemplateWithMap },
|
|
599
|
+
);
|
|
600
|
+
expect(resolveTemplateWithMap).toHaveBeenNthCalledWith(1, 'Hi {{a}}', 0);
|
|
601
|
+
expect(resolveTemplateWithMap).toHaveBeenNthCalledWith(2, 'Bye {{b}} {{c}}', 1);
|
|
602
|
+
expect(resolveTemplateWithMap).toHaveBeenNthCalledWith(3, 'Solo {{d}}', 3);
|
|
603
|
+
expect(resolveTemplateWithMap).toHaveBeenNthCalledWith(4, 'none', 4);
|
|
604
|
+
expect(out[0].title).toBe('R(Hi {{a}}|0)');
|
|
605
|
+
expect(out[0].bodyText).toBe('R(Bye {{b}} {{c}}|1)');
|
|
606
|
+
expect(out[1].title).toBe('R(Solo {{d}}|3)');
|
|
607
|
+
expect(out[1].bodyText).toBe('R(none|4)');
|
|
608
|
+
});
|
|
609
|
+
|
|
610
|
+
it('defaults missing card fields, including a default {} for a missing array item', () => {
|
|
611
|
+
const out = buildCarouselCardsForPreview([undefined], {
|
|
612
|
+
isFullMode: true,
|
|
613
|
+
rcsVarRegex,
|
|
614
|
+
resolveTemplateWithMap: jest.fn(),
|
|
615
|
+
});
|
|
616
|
+
expect(out[0]).toEqual({
|
|
617
|
+
mediaType: '',
|
|
618
|
+
imageSrc: '',
|
|
619
|
+
videoSrc: '',
|
|
620
|
+
videoPreviewImg: '',
|
|
621
|
+
title: '',
|
|
622
|
+
bodyText: '',
|
|
623
|
+
suggestions: [],
|
|
624
|
+
});
|
|
625
|
+
});
|
|
626
|
+
|
|
627
|
+
it('lowercases mediaType and passes through imageSrc/suggestions', () => {
|
|
628
|
+
const out = buildCarouselCardsForPreview(
|
|
629
|
+
[
|
|
630
|
+
{
|
|
631
|
+
mediaType: RCS_MEDIA_TYPES.IMAGE,
|
|
632
|
+
imageSrc: 'img.png',
|
|
633
|
+
suggestions: [{ text: 'Hi' }],
|
|
634
|
+
title: '',
|
|
635
|
+
description: '',
|
|
636
|
+
},
|
|
637
|
+
],
|
|
638
|
+
{ isFullMode: true, rcsVarRegex, resolveTemplateWithMap: jest.fn() },
|
|
639
|
+
);
|
|
640
|
+
expect(out[0].mediaType).toBe('image');
|
|
641
|
+
expect(out[0].imageSrc).toBe('img.png');
|
|
642
|
+
expect(out[0].suggestions).toEqual([{ text: 'Hi' }]);
|
|
643
|
+
});
|
|
644
|
+
|
|
645
|
+
it('prefers thumbnailSrc over videoAsset.videoThumbnail for videoPreviewImg', () => {
|
|
646
|
+
const out = buildCarouselCardsForPreview(
|
|
647
|
+
[
|
|
648
|
+
{
|
|
649
|
+
mediaType: RCS_MEDIA_TYPES.VIDEO,
|
|
650
|
+
thumbnailSrc: 'thumb.png',
|
|
651
|
+
videoAsset: { videoSrc: 'v.mp4', videoThumbnail: 'other-thumb.png' },
|
|
652
|
+
},
|
|
653
|
+
],
|
|
654
|
+
{ isFullMode: true, rcsVarRegex, resolveTemplateWithMap: jest.fn() },
|
|
655
|
+
);
|
|
656
|
+
expect(out[0].videoPreviewImg).toBe('thumb.png');
|
|
657
|
+
expect(out[0].videoSrc).toBe('v.mp4');
|
|
658
|
+
});
|
|
659
|
+
|
|
660
|
+
it('falls back to videoAsset.videoThumbnail when thumbnailSrc is missing', () => {
|
|
661
|
+
const out = buildCarouselCardsForPreview(
|
|
662
|
+
[
|
|
663
|
+
{
|
|
664
|
+
mediaType: RCS_MEDIA_TYPES.VIDEO,
|
|
665
|
+
videoAsset: { videoSrc: 'v.mp4', videoThumbnail: 'other-thumb.png' },
|
|
666
|
+
},
|
|
667
|
+
],
|
|
668
|
+
{ isFullMode: true, rcsVarRegex, resolveTemplateWithMap: jest.fn() },
|
|
669
|
+
);
|
|
670
|
+
expect(out[0].videoPreviewImg).toBe('other-thumb.png');
|
|
671
|
+
});
|
|
672
|
+
|
|
673
|
+
it('defaults videoPreviewImg and videoSrc to empty string when nothing is set', () => {
|
|
674
|
+
const out = buildCarouselCardsForPreview(
|
|
675
|
+
[{ mediaType: RCS_MEDIA_TYPES.IMAGE }],
|
|
676
|
+
{ isFullMode: true, rcsVarRegex, resolveTemplateWithMap: jest.fn() },
|
|
677
|
+
);
|
|
678
|
+
expect(out[0].videoPreviewImg).toBe('');
|
|
679
|
+
expect(out[0].videoSrc).toBe('');
|
|
680
|
+
});
|
|
681
|
+
});
|
|
682
|
+
|
|
683
|
+
describe('buildCarouselCardContentForPayload', () => {
|
|
684
|
+
it('returns an empty array when carouselData is missing', () => {
|
|
685
|
+
expect(
|
|
686
|
+
buildCarouselCardContentForPayload(undefined, {
|
|
687
|
+
isSlotMappingMode: false,
|
|
688
|
+
cardVarMapped: {},
|
|
689
|
+
selectedCarouselHeight: 'SHORT',
|
|
690
|
+
rcsVarRegex,
|
|
691
|
+
}),
|
|
692
|
+
).toEqual([]);
|
|
693
|
+
});
|
|
694
|
+
|
|
695
|
+
it('returns an empty array when carouselData is explicitly null', () => {
|
|
696
|
+
expect(
|
|
697
|
+
buildCarouselCardContentForPayload(null, {
|
|
698
|
+
isSlotMappingMode: false,
|
|
699
|
+
cardVarMapped: {},
|
|
700
|
+
selectedCarouselHeight: 'SHORT',
|
|
701
|
+
rcsVarRegex,
|
|
702
|
+
}),
|
|
703
|
+
).toEqual([]);
|
|
704
|
+
});
|
|
705
|
+
|
|
706
|
+
it('defaults a missing array item to {}', () => {
|
|
707
|
+
const out = buildCarouselCardContentForPayload([undefined], {
|
|
708
|
+
isSlotMappingMode: false,
|
|
709
|
+
cardVarMapped: {},
|
|
710
|
+
selectedCarouselHeight: 'SHORT',
|
|
711
|
+
rcsVarRegex,
|
|
712
|
+
});
|
|
713
|
+
expect(out[0]).toEqual({ title: '', description: '' });
|
|
714
|
+
});
|
|
715
|
+
|
|
716
|
+
it('builds title/description/mediaType and omits media for non image/video cards', () => {
|
|
717
|
+
const out = buildCarouselCardContentForPayload(
|
|
718
|
+
[{ title: 'T', description: 'D', mediaType: RCS_MEDIA_TYPES.NONE }],
|
|
719
|
+
{ isSlotMappingMode: false, cardVarMapped: {}, selectedCarouselHeight: 'SHORT', rcsVarRegex },
|
|
720
|
+
);
|
|
721
|
+
expect(out[0]).toEqual({ title: 'T', description: 'D', mediaType: RCS_MEDIA_TYPES.NONE });
|
|
722
|
+
});
|
|
723
|
+
|
|
724
|
+
describe('IMAGE media', () => {
|
|
725
|
+
it('uses imageSrc as mediaUrl and an empty thumbnailUrl', () => {
|
|
726
|
+
const out = buildCarouselCardContentForPayload(
|
|
727
|
+
[{ title: 'T', description: 'D', mediaType: RCS_MEDIA_TYPES.IMAGE, imageSrc: 'img.png' }],
|
|
728
|
+
{ isSlotMappingMode: false, cardVarMapped: {}, selectedCarouselHeight: 'SHORT', rcsVarRegex },
|
|
729
|
+
);
|
|
730
|
+
expect(out[0].media).toEqual({ mediaUrl: 'img.png', thumbnailUrl: '', height: 'SHORT' });
|
|
731
|
+
});
|
|
732
|
+
|
|
733
|
+
it('defaults height to MEDIUM when selectedCarouselHeight is missing', () => {
|
|
734
|
+
const out = buildCarouselCardContentForPayload(
|
|
735
|
+
[{ title: 'T', description: 'D', mediaType: RCS_MEDIA_TYPES.IMAGE, imageSrc: 'img.png' }],
|
|
736
|
+
{ isSlotMappingMode: false, cardVarMapped: {}, selectedCarouselHeight: undefined, rcsVarRegex },
|
|
737
|
+
);
|
|
738
|
+
expect(out[0].media.height).toBe(MEDIUM);
|
|
739
|
+
});
|
|
740
|
+
});
|
|
741
|
+
|
|
742
|
+
describe('VIDEO media', () => {
|
|
743
|
+
it('uses videoAsset.videoSrc as mediaUrl and thumbnailSrc as thumbnailUrl', () => {
|
|
744
|
+
const out = buildCarouselCardContentForPayload(
|
|
745
|
+
[
|
|
746
|
+
{
|
|
747
|
+
title: 'T',
|
|
748
|
+
description: 'D',
|
|
749
|
+
mediaType: RCS_MEDIA_TYPES.VIDEO,
|
|
750
|
+
videoAsset: { videoSrc: 'v.mp4' },
|
|
751
|
+
thumbnailSrc: 'thumb.png',
|
|
752
|
+
},
|
|
753
|
+
],
|
|
754
|
+
{ isSlotMappingMode: false, cardVarMapped: {}, selectedCarouselHeight: 'SHORT', rcsVarRegex },
|
|
755
|
+
);
|
|
756
|
+
expect(out[0].media.mediaUrl).toBe('v.mp4');
|
|
757
|
+
expect(out[0].media.thumbnailUrl).toBe('thumb.png');
|
|
758
|
+
});
|
|
759
|
+
|
|
760
|
+
it('falls back to videoAsset.videoThumbnail when thumbnailSrc missing', () => {
|
|
761
|
+
const out = buildCarouselCardContentForPayload(
|
|
762
|
+
[
|
|
763
|
+
{
|
|
764
|
+
title: 'T',
|
|
765
|
+
description: 'D',
|
|
766
|
+
mediaType: RCS_MEDIA_TYPES.VIDEO,
|
|
767
|
+
videoAsset: { videoSrc: 'v.mp4', videoThumbnail: 'fallback-thumb.png' },
|
|
768
|
+
},
|
|
769
|
+
],
|
|
770
|
+
{ isSlotMappingMode: false, cardVarMapped: {}, selectedCarouselHeight: 'SHORT', rcsVarRegex },
|
|
771
|
+
);
|
|
772
|
+
expect(out[0].media.thumbnailUrl).toBe('fallback-thumb.png');
|
|
773
|
+
});
|
|
774
|
+
|
|
775
|
+
it('includes videoName only when videoAsset.videoName is present', () => {
|
|
776
|
+
const withName = buildCarouselCardContentForPayload(
|
|
777
|
+
[
|
|
778
|
+
{
|
|
779
|
+
title: 'T',
|
|
780
|
+
description: 'D',
|
|
781
|
+
mediaType: RCS_MEDIA_TYPES.VIDEO,
|
|
782
|
+
videoAsset: { videoSrc: 'v.mp4', videoName: 'clip.mp4' },
|
|
783
|
+
},
|
|
784
|
+
],
|
|
785
|
+
{ isSlotMappingMode: false, cardVarMapped: {}, selectedCarouselHeight: 'SHORT', rcsVarRegex },
|
|
786
|
+
);
|
|
787
|
+
expect(withName[0].media.videoName).toBe('clip.mp4');
|
|
788
|
+
|
|
789
|
+
const withoutName = buildCarouselCardContentForPayload(
|
|
790
|
+
[
|
|
791
|
+
{
|
|
792
|
+
title: 'T',
|
|
793
|
+
description: 'D',
|
|
794
|
+
mediaType: RCS_MEDIA_TYPES.VIDEO,
|
|
795
|
+
videoAsset: { videoSrc: 'v.mp4' },
|
|
796
|
+
},
|
|
797
|
+
],
|
|
798
|
+
{ isSlotMappingMode: false, cardVarMapped: {}, selectedCarouselHeight: 'SHORT', rcsVarRegex },
|
|
799
|
+
);
|
|
800
|
+
expect(withoutName[0].media.videoName).toBeUndefined();
|
|
801
|
+
});
|
|
802
|
+
|
|
803
|
+
it('defaults mediaUrl to empty string when videoAsset.videoSrc is missing', () => {
|
|
804
|
+
const out = buildCarouselCardContentForPayload(
|
|
805
|
+
[{ title: 'T', description: 'D', mediaType: RCS_MEDIA_TYPES.VIDEO, videoAsset: {} }],
|
|
806
|
+
{ isSlotMappingMode: false, cardVarMapped: {}, selectedCarouselHeight: 'SHORT', rcsVarRegex },
|
|
807
|
+
);
|
|
808
|
+
expect(out[0].media.mediaUrl).toBe('');
|
|
809
|
+
});
|
|
810
|
+
});
|
|
811
|
+
|
|
812
|
+
describe('suggestions', () => {
|
|
813
|
+
it('includes suggestions when the array is non-empty', () => {
|
|
814
|
+
const suggestions = [{ type: RCS_BUTTON_TYPES.QUICK_REPLY, text: 'Hi' }];
|
|
815
|
+
const out = buildCarouselCardContentForPayload(
|
|
816
|
+
[{ title: 'T', description: 'D', mediaType: RCS_MEDIA_TYPES.NONE, suggestions }],
|
|
817
|
+
{ isSlotMappingMode: false, cardVarMapped: {}, selectedCarouselHeight: 'SHORT', rcsVarRegex },
|
|
818
|
+
);
|
|
819
|
+
expect(out[0].suggestions).toEqual(suggestions);
|
|
820
|
+
});
|
|
821
|
+
|
|
822
|
+
it('omits suggestions when array is empty, missing, or not an array', () => {
|
|
823
|
+
const outEmpty = buildCarouselCardContentForPayload(
|
|
824
|
+
[{ title: 'T', description: 'D', mediaType: RCS_MEDIA_TYPES.NONE, suggestions: [] }],
|
|
825
|
+
{ isSlotMappingMode: false, cardVarMapped: {}, selectedCarouselHeight: 'SHORT', rcsVarRegex },
|
|
826
|
+
);
|
|
827
|
+
expect(outEmpty[0].suggestions).toBeUndefined();
|
|
828
|
+
|
|
829
|
+
const outMissing = buildCarouselCardContentForPayload(
|
|
830
|
+
[{ title: 'T', description: 'D', mediaType: RCS_MEDIA_TYPES.NONE }],
|
|
831
|
+
{ isSlotMappingMode: false, cardVarMapped: {}, selectedCarouselHeight: 'SHORT', rcsVarRegex },
|
|
832
|
+
);
|
|
833
|
+
expect(outMissing[0].suggestions).toBeUndefined();
|
|
834
|
+
|
|
835
|
+
const outNotArray = buildCarouselCardContentForPayload(
|
|
836
|
+
[{ title: 'T', description: 'D', mediaType: RCS_MEDIA_TYPES.NONE, suggestions: 'nope' }],
|
|
837
|
+
{ isSlotMappingMode: false, cardVarMapped: {}, selectedCarouselHeight: 'SHORT', rcsVarRegex },
|
|
838
|
+
);
|
|
839
|
+
expect(outNotArray[0].suggestions).toBeUndefined();
|
|
840
|
+
});
|
|
841
|
+
});
|
|
842
|
+
|
|
843
|
+
describe('cardVarMapped', () => {
|
|
844
|
+
it('omits cardVarMapped when isSlotMappingMode is false, even if there are var tokens', () => {
|
|
845
|
+
const out = buildCarouselCardContentForPayload(
|
|
846
|
+
[{ title: 'Hi {{name}}', description: 'D', mediaType: RCS_MEDIA_TYPES.NONE }],
|
|
847
|
+
{ isSlotMappingMode: false, cardVarMapped: { name: 'Bob' }, selectedCarouselHeight: 'SHORT', rcsVarRegex },
|
|
848
|
+
);
|
|
849
|
+
expect(out[0].cardVarMapped).toBeUndefined();
|
|
850
|
+
});
|
|
851
|
+
|
|
852
|
+
it('omits cardVarMapped when isSlotMappingMode is true but there are no var tokens', () => {
|
|
853
|
+
const out = buildCarouselCardContentForPayload(
|
|
854
|
+
[{ title: 'Hi', description: 'D', mediaType: RCS_MEDIA_TYPES.NONE }],
|
|
855
|
+
{ isSlotMappingMode: true, cardVarMapped: {}, selectedCarouselHeight: 'SHORT', rcsVarRegex },
|
|
856
|
+
);
|
|
857
|
+
expect(out[0].cardVarMapped).toBeUndefined();
|
|
858
|
+
});
|
|
859
|
+
|
|
860
|
+
it('builds cardVarMapped from title and description tokens, sanitizing values', () => {
|
|
861
|
+
const out = buildCarouselCardContentForPayload(
|
|
862
|
+
[{ title: 'Hi {{name}}', description: 'Pts {{points}}', mediaType: RCS_MEDIA_TYPES.NONE }],
|
|
863
|
+
{
|
|
864
|
+
isSlotMappingMode: true,
|
|
865
|
+
cardVarMapped: { name: 'Bob', points: '{{2}}' },
|
|
866
|
+
selectedCarouselHeight: 'SHORT',
|
|
867
|
+
rcsVarRegex,
|
|
868
|
+
},
|
|
869
|
+
);
|
|
870
|
+
expect(out[0].cardVarMapped).toEqual({ name: 'Bob', points: '' });
|
|
871
|
+
});
|
|
872
|
+
|
|
873
|
+
it('defaults an unmapped token value to empty string via sanitizeCardVarMappedValue', () => {
|
|
874
|
+
const out = buildCarouselCardContentForPayload(
|
|
875
|
+
[{ title: 'Hi {{missing}}', description: 'D', mediaType: RCS_MEDIA_TYPES.NONE }],
|
|
876
|
+
{ isSlotMappingMode: true, cardVarMapped: {}, selectedCarouselHeight: 'SHORT', rcsVarRegex },
|
|
877
|
+
);
|
|
878
|
+
expect(out[0].cardVarMapped).toEqual({ missing: '' });
|
|
879
|
+
});
|
|
880
|
+
|
|
881
|
+
it('handles a missing cardVarMapped object entirely', () => {
|
|
882
|
+
const out = buildCarouselCardContentForPayload(
|
|
883
|
+
[{ title: 'Hi {{name}}', description: 'D', mediaType: RCS_MEDIA_TYPES.NONE }],
|
|
884
|
+
{ isSlotMappingMode: true, cardVarMapped: undefined, selectedCarouselHeight: 'SHORT', rcsVarRegex },
|
|
885
|
+
);
|
|
886
|
+
expect(out[0].cardVarMapped).toEqual({ name: '' });
|
|
887
|
+
});
|
|
888
|
+
|
|
889
|
+
it('skips a matched token that strips down to an empty variable name', () => {
|
|
890
|
+
const emptyTokenRegex = /\{\{\}\}/g;
|
|
891
|
+
const out = buildCarouselCardContentForPayload(
|
|
892
|
+
[{ title: 'Hi {{}}', description: 'D', mediaType: RCS_MEDIA_TYPES.NONE }],
|
|
893
|
+
{
|
|
894
|
+
isSlotMappingMode: true,
|
|
895
|
+
cardVarMapped: {},
|
|
896
|
+
selectedCarouselHeight: 'SHORT',
|
|
897
|
+
rcsVarRegex: emptyTokenRegex,
|
|
898
|
+
},
|
|
899
|
+
);
|
|
900
|
+
expect(out[0].cardVarMapped).toBeUndefined();
|
|
901
|
+
});
|
|
902
|
+
});
|
|
903
|
+
|
|
904
|
+
it('builds independent content for multiple cards in one call', () => {
|
|
905
|
+
const out = buildCarouselCardContentForPayload(
|
|
906
|
+
[
|
|
907
|
+
{ title: 'A', description: 'B', mediaType: RCS_MEDIA_TYPES.IMAGE, imageSrc: 'a.png' },
|
|
908
|
+
{ title: 'C', description: 'D', mediaType: RCS_MEDIA_TYPES.NONE },
|
|
909
|
+
],
|
|
910
|
+
{ isSlotMappingMode: false, cardVarMapped: {}, selectedCarouselHeight: 'SHORT', rcsVarRegex },
|
|
911
|
+
);
|
|
912
|
+
expect(out).toHaveLength(2);
|
|
913
|
+
expect(out[0].media).toBeDefined();
|
|
914
|
+
expect(out[1].media).toBeUndefined();
|
|
915
|
+
});
|
|
916
|
+
});
|