cap-creatives-ui 8.0.280 → 8.0.321

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 (247) hide show
  1. package/.github/workflows/pr-title-check.yml +88 -0
  2. package/app/constants/unified.js +21 -1
  3. package/app/containers/App/constants.js +0 -1
  4. package/app/containers/Login/test/index.test.js +123 -0
  5. package/app/containers/Login/test/selectors.test.js +165 -0
  6. package/app/initialState.js +0 -2
  7. package/app/services/api.js +6 -0
  8. package/app/services/tests/api.test.js +7 -0
  9. package/app/services/tests/getSchema.test.js +95 -0
  10. package/app/utils/common.js +23 -9
  11. package/app/utils/commonUtils.js +64 -93
  12. package/app/utils/tagValidations.js +83 -219
  13. package/app/utils/templateVarUtils.js +172 -0
  14. package/app/utils/tests/common.test.js +265 -323
  15. package/app/utils/tests/commonUtil.test.js +461 -118
  16. package/app/utils/tests/commonUtils.test.js +581 -0
  17. package/app/utils/tests/messageUtils.test.js +95 -0
  18. package/app/utils/tests/smsCharCount.test.js +304 -0
  19. package/app/utils/tests/smsCharCountV2.test.js +213 -10
  20. package/app/utils/tests/tagValidations.test.js +474 -357
  21. package/app/utils/tests/templateVarUtils.test.js +160 -0
  22. package/app/v2Components/CapDeviceContent/index.js +10 -7
  23. package/app/v2Components/CapTagList/index.js +32 -24
  24. package/app/v2Components/CapTagList/style.scss +48 -0
  25. package/app/v2Components/CapTagListWithInput/__tests__/CapTagListWithInput.test.js +63 -0
  26. package/app/v2Components/CapTagListWithInput/index.js +8 -0
  27. package/app/v2Components/CapWhatsappCTA/index.js +2 -0
  28. package/app/v2Components/CapWhatsappCarouselButton/index.js +32 -14
  29. package/app/v2Components/CapWhatsappCarouselButton/tests/index.test.js +120 -2
  30. package/app/v2Components/CommonTestAndPreview/CustomValuesEditor.js +70 -49
  31. package/app/v2Components/CommonTestAndPreview/DeliverySettings/DeliverySettings.scss +39 -0
  32. package/app/v2Components/CommonTestAndPreview/DeliverySettings/ModifyDeliverySettings.js +606 -0
  33. package/app/v2Components/CommonTestAndPreview/DeliverySettings/ModifyDeliverySettings.scss +36 -0
  34. package/app/v2Components/CommonTestAndPreview/DeliverySettings/constants.js +79 -0
  35. package/app/v2Components/CommonTestAndPreview/DeliverySettings/index.js +314 -0
  36. package/app/v2Components/CommonTestAndPreview/DeliverySettings/messages.js +141 -0
  37. package/app/v2Components/CommonTestAndPreview/DeliverySettings/utils/parseSenderDetailsResponse.js +156 -0
  38. package/app/v2Components/CommonTestAndPreview/SendTestMessage.js +57 -1
  39. package/app/v2Components/CommonTestAndPreview/UnifiedPreview/_unifiedPreview.scss +20 -1
  40. package/app/v2Components/CommonTestAndPreview/UnifiedPreview/index.js +133 -4
  41. package/app/v2Components/CommonTestAndPreview/_commonTestAndPreview.scss +210 -4
  42. package/app/v2Components/CommonTestAndPreview/actions.js +20 -0
  43. package/app/v2Components/CommonTestAndPreview/constants.js +57 -1
  44. package/app/v2Components/CommonTestAndPreview/index.js +878 -156
  45. package/app/v2Components/CommonTestAndPreview/messages.js +41 -3
  46. package/app/v2Components/CommonTestAndPreview/previewApiUtils.js +59 -0
  47. package/app/v2Components/CommonTestAndPreview/reducer.js +47 -0
  48. package/app/v2Components/CommonTestAndPreview/sagas.js +75 -5
  49. package/app/v2Components/CommonTestAndPreview/selectors.js +51 -0
  50. package/app/v2Components/CommonTestAndPreview/tests/CustomValuesEditor.test.js +352 -0
  51. package/app/v2Components/CommonTestAndPreview/tests/DeliverySettings/ModifyDeliverySettings.test.js +1156 -0
  52. package/app/v2Components/CommonTestAndPreview/tests/DeliverySettings/index.test.js +334 -0
  53. package/app/v2Components/CommonTestAndPreview/tests/DeliverySettings/utils/parseSenderDetailsResponse.test.js +576 -0
  54. package/app/v2Components/CommonTestAndPreview/tests/SendTestMessage.test.js +156 -0
  55. package/app/v2Components/CommonTestAndPreview/tests/UnifiedPreview/index.test.js +199 -1
  56. package/app/v2Components/CommonTestAndPreview/tests/actions.test.js +50 -0
  57. package/app/v2Components/CommonTestAndPreview/tests/constants.test.js +18 -7
  58. package/app/v2Components/CommonTestAndPreview/tests/index.test.js +914 -5
  59. package/app/v2Components/CommonTestAndPreview/tests/previewApiUtils.test.js +67 -0
  60. package/app/v2Components/CommonTestAndPreview/tests/reducer.test.js +118 -0
  61. package/app/v2Components/CommonTestAndPreview/tests/sagas.test.js +146 -378
  62. package/app/v2Components/CommonTestAndPreview/tests/selectors.test.js +146 -0
  63. package/app/v2Components/ErrorInfoNote/index.js +24 -26
  64. package/app/v2Components/FormBuilder/index.js +182 -204
  65. package/app/v2Components/FormBuilder/messages.js +4 -8
  66. package/app/v2Components/HtmlEditor/HTMLEditor.js +7 -6
  67. package/app/v2Components/HtmlEditor/__tests__/HTMLEditor.apiErrors.test.js +1 -1
  68. package/app/v2Components/HtmlEditor/__tests__/HTMLEditor.test.js +928 -17
  69. package/app/v2Components/HtmlEditor/components/CodeEditorPane/index.js +4 -2
  70. package/app/v2Components/HtmlEditor/hooks/__tests__/useValidation.test.js +452 -3
  71. package/app/v2Components/HtmlEditor/hooks/useValidation.js +12 -9
  72. package/app/v2Components/HtmlEditor/utils/__tests__/htmlValidator.enhanced.test.js +132 -0
  73. package/app/v2Components/HtmlEditor/utils/htmlValidator.js +4 -2
  74. package/app/v2Components/SmsFallback/SmsFallbackLocalSelector.js +87 -0
  75. package/app/v2Components/SmsFallback/constants.js +73 -0
  76. package/app/v2Components/SmsFallback/index.js +956 -0
  77. package/app/v2Components/SmsFallback/index.scss +265 -0
  78. package/app/v2Components/SmsFallback/messages.js +78 -0
  79. package/app/v2Components/SmsFallback/smsFallbackUtils.js +107 -0
  80. package/app/v2Components/SmsFallback/tests/SmsFallbackLocalSelector.test.js +50 -0
  81. package/app/v2Components/SmsFallback/tests/rcsSmsFallback.acceptance.test.js +147 -0
  82. package/app/v2Components/SmsFallback/tests/smsFallbackHandlers.test.js +304 -0
  83. package/app/v2Components/SmsFallback/tests/smsFallbackUi.test.js +197 -0
  84. package/app/v2Components/SmsFallback/tests/smsFallbackUtils.test.js +261 -0
  85. package/app/v2Components/SmsFallback/tests/useLocalTemplateList.test.js +422 -0
  86. package/app/v2Components/SmsFallback/useLocalTemplateList.js +92 -0
  87. package/app/v2Components/TestAndPreviewSlidebox/index.js +22 -1
  88. package/app/v2Components/TestAndPreviewSlidebox/sagas.js +11 -4
  89. package/app/v2Components/TestAndPreviewSlidebox/tests/saga.test.js +3 -1
  90. package/app/v2Components/VarSegmentMessageEditor/constants.js +2 -0
  91. package/app/v2Components/VarSegmentMessageEditor/index.js +125 -0
  92. package/app/v2Components/VarSegmentMessageEditor/index.scss +46 -0
  93. package/app/v2Containers/BeeEditor/index.js +3 -0
  94. package/app/v2Containers/BeePopupEditor/index.js +9 -2
  95. package/app/v2Containers/Cap/mockData.js +0 -14
  96. package/app/v2Containers/Cap/reducer.js +3 -55
  97. package/app/v2Containers/Cap/tests/reducer.test.js +0 -102
  98. package/app/v2Containers/CommunicationFlow/CommunicationFlow.js +291 -0
  99. package/app/v2Containers/CommunicationFlow/CommunicationFlow.scss +25 -0
  100. package/app/v2Containers/CommunicationFlow/Tests/CommunicationFlow.test.js +255 -0
  101. package/app/v2Containers/CommunicationFlow/constants.js +200 -0
  102. package/app/v2Containers/CommunicationFlow/index.js +102 -0
  103. package/app/v2Containers/CommunicationFlow/messages.js +346 -0
  104. package/app/v2Containers/CommunicationFlow/steps/ChannelSelectionStep/ChannelSelectionStep.js +522 -0
  105. package/app/v2Containers/CommunicationFlow/steps/ChannelSelectionStep/ChannelSelectionStep.scss +170 -0
  106. package/app/v2Containers/CommunicationFlow/steps/ChannelSelectionStep/Tests/ChannelSelectionStep.test.js +796 -0
  107. package/app/v2Containers/CommunicationFlow/steps/ChannelSelectionStep/index.js +5 -0
  108. package/app/v2Containers/CommunicationFlow/steps/CommunicationStrategyStep/CommunicationStrategyStep.js +95 -0
  109. package/app/v2Containers/CommunicationFlow/steps/CommunicationStrategyStep/Tests/CommunicationStrategyStep.test.js +133 -0
  110. package/app/v2Containers/CommunicationFlow/steps/CommunicationStrategyStep/index.js +5 -0
  111. package/app/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/DeliverySettingsSection.js +289 -0
  112. package/app/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/DeliverySettingsSection.scss +70 -0
  113. package/app/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/SenderDetails.js +319 -0
  114. package/app/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/SenderDetails.scss +69 -0
  115. package/app/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/Tests/DeliverySettingsSection.test.js +616 -0
  116. package/app/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/Tests/SenderDetails.test.js +577 -0
  117. package/app/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/Tests/deliverySettingsConfig.test.js +1111 -0
  118. package/app/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/deliverySettingsConfig.js +696 -0
  119. package/app/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/index.js +7 -0
  120. package/app/v2Containers/CommunicationFlow/steps/DynamicControlsStep/DynamicControlsStep.js +102 -0
  121. package/app/v2Containers/CommunicationFlow/steps/DynamicControlsStep/DynamicControlsStep.scss +36 -0
  122. package/app/v2Containers/CommunicationFlow/steps/DynamicControlsStep/Tests/DynamicControlsStep.test.js +91 -0
  123. package/app/v2Containers/CommunicationFlow/steps/DynamicControlsStep/index.js +5 -0
  124. package/app/v2Containers/CommunicationFlow/steps/MessageTypeStep/MessageTypeStep.js +86 -0
  125. package/app/v2Containers/CommunicationFlow/steps/MessageTypeStep/Tests/MessageTypeStep.test.js +100 -0
  126. package/app/v2Containers/CommunicationFlow/steps/MessageTypeStep/index.js +5 -0
  127. package/app/v2Containers/CommunicationFlow/utils/getEnabledSteps.js +30 -0
  128. package/app/v2Containers/CreativesContainer/CreativesSlideBoxWrapper.js +43 -0
  129. package/app/v2Containers/CreativesContainer/SlideBoxContent.js +127 -11
  130. package/app/v2Containers/CreativesContainer/SlideBoxFooter.js +62 -9
  131. package/app/v2Containers/CreativesContainer/SlideBoxHeader.js +29 -4
  132. package/app/v2Containers/CreativesContainer/constants.js +24 -0
  133. package/app/v2Containers/CreativesContainer/embeddedSlideboxUtils.js +67 -0
  134. package/app/v2Containers/CreativesContainer/index.js +346 -71
  135. package/app/v2Containers/CreativesContainer/index.scss +51 -1
  136. package/app/v2Containers/CreativesContainer/messages.js +12 -0
  137. package/app/v2Containers/CreativesContainer/tests/SlideBoxContent.localTemplates.test.js +90 -0
  138. package/app/v2Containers/CreativesContainer/tests/SlideBoxContent.test.js +69 -1
  139. package/app/v2Containers/CreativesContainer/tests/SlideBoxFooter.test.js +443 -0
  140. package/app/v2Containers/CreativesContainer/tests/SlideBoxHeader.test.js +110 -0
  141. package/app/v2Containers/CreativesContainer/tests/__snapshots__/SlideBoxContent.test.js.snap +147 -4
  142. package/app/v2Containers/CreativesContainer/tests/__snapshots__/SlideBoxHeader.test.js.snap +363 -0
  143. package/app/v2Containers/CreativesContainer/tests/__snapshots__/index.test.js.snap +57 -10
  144. package/app/v2Containers/CreativesContainer/tests/embeddedSlideboxUtils.test.js +258 -0
  145. package/app/v2Containers/CreativesContainer/tests/index.test.js +71 -9
  146. package/app/v2Containers/CreativesContainer/tests/useLocalTemplatesProp.test.js +125 -0
  147. package/app/v2Containers/Email/index.js +2 -5
  148. package/app/v2Containers/EmailWrapper/components/EmailHTMLEditor.js +58 -77
  149. package/app/v2Containers/EmailWrapper/components/EmailWrapperView.js +3 -0
  150. package/app/v2Containers/EmailWrapper/components/__tests__/EmailHTMLEditor.test.js +158 -89
  151. package/app/v2Containers/EmailWrapper/components/__tests__/EmailWrapperView.test.js +16 -1
  152. package/app/v2Containers/EmailWrapper/hooks/useEmailWrapper.js +17 -12
  153. package/app/v2Containers/EmailWrapper/index.js +4 -0
  154. package/app/v2Containers/EmailWrapper/tests/useEmailWrapper.edgeCases.test.js +1 -0
  155. package/app/v2Containers/EmailWrapper/tests/useEmailWrapper.test.js +133 -0
  156. package/app/v2Containers/FTP/index.js +2 -51
  157. package/app/v2Containers/FTP/messages.js +0 -4
  158. package/app/v2Containers/InApp/__tests__/InAppHTMLEditor.test.js +110 -155
  159. package/app/v2Containers/InApp/index.js +297 -118
  160. package/app/v2Containers/InApp/tests/index.test.js +17 -6
  161. package/app/v2Containers/InApp/tests/mockData.js +1 -1
  162. package/app/v2Containers/InAppWrapper/hooks/__tests__/useInAppWrapper.test.js +19 -0
  163. package/app/v2Containers/InAppWrapper/hooks/useInAppWrapper.js +3 -0
  164. package/app/v2Containers/InAppWrapper/index.js +3 -0
  165. package/app/v2Containers/InappAdvance/index.js +5 -104
  166. package/app/v2Containers/InappAdvance/tests/index.test.js +2 -0
  167. package/app/v2Containers/Line/Container/ImageCarousel/tests/__snapshots__/content.test.js.snap +24 -3
  168. package/app/v2Containers/Line/Container/Text/index.js +0 -1
  169. package/app/v2Containers/MobilePush/Create/index.js +105 -28
  170. package/app/v2Containers/MobilePush/Create/messages.js +4 -0
  171. package/app/v2Containers/MobilePush/Edit/index.js +250 -68
  172. package/app/v2Containers/MobilePush/Edit/messages.js +4 -0
  173. package/app/v2Containers/MobilePushNew/components/PlatformContentFields.js +36 -12
  174. package/app/v2Containers/MobilePushNew/components/tests/PlatformContentFields.test.js +68 -27
  175. package/app/v2Containers/MobilePushNew/index.js +78 -35
  176. package/app/v2Containers/MobilePushNew/messages.js +8 -0
  177. package/app/v2Containers/MobilepushWrapper/index.js +11 -1
  178. package/app/v2Containers/Rcs/constants.js +32 -1
  179. package/app/v2Containers/Rcs/index.js +963 -916
  180. package/app/v2Containers/Rcs/index.scss +85 -6
  181. package/app/v2Containers/Rcs/messages.js +10 -1
  182. package/app/v2Containers/Rcs/rcsLibraryHydrationUtils.js +205 -0
  183. package/app/v2Containers/Rcs/tests/__snapshots__/index.test.js.snap +41136 -1566
  184. package/app/v2Containers/Rcs/tests/__snapshots__/utils.test.js.snap +0 -5
  185. package/app/v2Containers/Rcs/tests/index.test.js +41 -38
  186. package/app/v2Containers/Rcs/tests/mockData.js +38 -0
  187. package/app/v2Containers/Rcs/tests/rcsLibraryHydrationUtils.test.js +251 -0
  188. package/app/v2Containers/Rcs/tests/utils.test.js +379 -1
  189. package/app/v2Containers/Rcs/utils.js +358 -10
  190. package/app/v2Containers/Sms/Create/index.js +122 -39
  191. package/app/v2Containers/Sms/Create/messages.js +4 -0
  192. package/app/v2Containers/Sms/Edit/index.js +37 -3
  193. package/app/v2Containers/Sms/commonMethods.js +3 -6
  194. package/app/v2Containers/Sms/smsFormDataHelpers.js +67 -0
  195. package/app/v2Containers/Sms/tests/commonMethods.test.js +122 -0
  196. package/app/v2Containers/Sms/tests/smsFormDataHelpers.test.js +253 -0
  197. package/app/v2Containers/SmsTrai/Create/index.js +9 -4
  198. package/app/v2Containers/SmsTrai/Create/index.scss +1 -1
  199. package/app/v2Containers/SmsTrai/Edit/constants.js +2 -0
  200. package/app/v2Containers/SmsTrai/Edit/index.js +667 -160
  201. package/app/v2Containers/SmsTrai/Edit/index.scss +121 -0
  202. package/app/v2Containers/SmsTrai/Edit/messages.js +9 -4
  203. package/app/v2Containers/SmsTrai/Edit/tests/__snapshots__/index.test.js.snap +4590 -2436
  204. package/app/v2Containers/SmsWrapper/index.js +41 -8
  205. package/app/v2Containers/TagList/index.js +63 -2
  206. package/app/v2Containers/TagList/messages.js +8 -0
  207. package/app/v2Containers/TagList/tests/TagList.test.js +122 -20
  208. package/app/v2Containers/TagList/tests/mockdata.js +17 -0
  209. package/app/v2Containers/Templates/TemplatesActionBar.js +101 -0
  210. package/app/v2Containers/Templates/_templates.scss +61 -2
  211. package/app/v2Containers/Templates/actions.js +11 -0
  212. package/app/v2Containers/Templates/constants.js +2 -0
  213. package/app/v2Containers/Templates/index.js +90 -40
  214. package/app/v2Containers/Templates/reducer.js +3 -1
  215. package/app/v2Containers/Templates/sagas.js +57 -12
  216. package/app/v2Containers/Templates/tests/TemplatesActionBar.test.js +120 -0
  217. package/app/v2Containers/Templates/tests/__snapshots__/index.test.js.snap +1043 -1079
  218. package/app/v2Containers/Templates/tests/reducer.test.js +12 -0
  219. package/app/v2Containers/Templates/tests/sagas.test.js +193 -12
  220. package/app/v2Containers/Templates/tests/smsTemplatesListApi.test.js +180 -0
  221. package/app/v2Containers/Templates/utils/smsTemplatesListApi.js +79 -0
  222. package/app/v2Containers/TemplatesV2/TemplatesV2.style.js +72 -1
  223. package/app/v2Containers/TemplatesV2/index.js +147 -49
  224. package/app/v2Containers/TemplatesV2/tests/TemplatesV2.localTemplates.test.js +131 -0
  225. package/app/v2Containers/Viber/index.js +9 -10
  226. package/app/v2Containers/Viber/index.scss +1 -1
  227. package/app/v2Containers/WebPush/Create/components/BrandIconSection.test.js +264 -0
  228. package/app/v2Containers/WebPush/Create/components/MessageSection.js +78 -19
  229. package/app/v2Containers/WebPush/Create/components/MessageSection.test.js +82 -0
  230. package/app/v2Containers/WebPush/Create/components/__snapshots__/BrandIconSection.test.js.snap +187 -0
  231. package/app/v2Containers/WebPush/Create/components/__snapshots__/MessageSection.test.js.snap +25 -17
  232. package/app/v2Containers/WebPush/Create/hooks/useAiraTriggerPosition.js +80 -0
  233. package/app/v2Containers/WebPush/Create/hooks/useAiraTriggerPosition.test.js +210 -0
  234. package/app/v2Containers/WebPush/Create/hooks/useTagManagement.js +1 -5
  235. package/app/v2Containers/WebPush/Create/hooks/useTagManagement.test.js +0 -7
  236. package/app/v2Containers/WebPush/Create/index.js +36 -6
  237. package/app/v2Containers/WebPush/Create/index.scss +5 -0
  238. package/app/v2Containers/WebPush/Create/messages.js +8 -1
  239. package/app/v2Containers/WebPush/Create/preview/tests/NotificationContainer.test.js +269 -0
  240. package/app/v2Containers/WebPush/Create/utils/validation.js +31 -15
  241. package/app/v2Containers/WebPush/Create/utils/validation.test.js +72 -24
  242. package/app/v2Containers/Whatsapp/index.js +28 -53
  243. package/app/v2Containers/Whatsapp/tests/__snapshots__/index.test.js.snap +26939 -3982
  244. package/app/v2Containers/Whatsapp/tests/index.test.js +172 -0
  245. package/app/v2Containers/Zalo/index.js +5 -11
  246. package/package.json +2 -2
  247. package/version +9 -0
@@ -1,7 +1,7 @@
1
1
  import '@testing-library/jest-dom';
2
2
  import {
3
- checkSupport,
4
- extractNames,
3
+ hasUnsubscribeTag,
4
+ validateTagsCore,
5
5
  getTagMapValue,
6
6
  getLoyaltyTagsMapValue,
7
7
  getForwardedMapValues,
@@ -9,10 +9,160 @@ import {
9
9
  validateIfTagClosed,
10
10
  validateTags,
11
11
  skipTags,
12
- isInsideLiquidBlock,
12
+ checkIfSupportedTag,
13
+ transformInjectedTags,
13
14
  } from '../tagValidations';
14
15
  import { containsBase64Images } from '../content';
15
- import { eventContextTags } from '../../v2Containers/TagList/tests/mockdata';
16
+
17
+ describe('hasUnsubscribeTag', () => {
18
+ it('should return false when content is not a string', () => {
19
+ expect(hasUnsubscribeTag(null)).toBe(false);
20
+ expect(hasUnsubscribeTag(undefined)).toBe(false);
21
+ expect(hasUnsubscribeTag(123)).toBe(false);
22
+ expect(hasUnsubscribeTag({})).toBe(false);
23
+ });
24
+
25
+ it('should return false when content has no unsubscribe tag', () => {
26
+ expect(hasUnsubscribeTag('Hello world')).toBe(false);
27
+ expect(hasUnsubscribeTag('{{ name }}')).toBe(false);
28
+ });
29
+
30
+ it('should return true when content has {{ unsubscribe }}', () => {
31
+ expect(hasUnsubscribeTag('Click {{ unsubscribe }} here')).toBe(true);
32
+ expect(hasUnsubscribeTag('{{ unsubscribe }}')).toBe(true);
33
+ });
34
+ });
35
+
36
+ describe('validateTagsCore', () => {
37
+ it('should include isContentEmpty: false when includeIsContentEmpty is true', () => {
38
+ const result = validateTagsCore({
39
+ contentForBraceCheck: '{{a}}',
40
+ contentForUnsubscribeScan: '{{a}}',
41
+ tags: null,
42
+ currentModule: 'default',
43
+ isFullMode: true,
44
+ includeIsContentEmpty: true,
45
+ });
46
+ expect(result.isContentEmpty).toBe(false);
47
+ expect(result.valid).toBe(true);
48
+ });
49
+
50
+ it('should use initialMissingTags when provided', () => {
51
+ const result = validateTagsCore({
52
+ contentForBraceCheck: '{{a}}',
53
+ contentForUnsubscribeScan: '{{a}}',
54
+ tags: [{ definition: { supportedModules: [], value: 'x' } }],
55
+ currentModule: 'default',
56
+ isFullMode: false,
57
+ initialMissingTags: ['requiredTag'],
58
+ });
59
+ expect(result.missingTags).toEqual(['requiredTag']);
60
+ expect(result.valid).toBe(false);
61
+ });
62
+
63
+ it('should use custom skipTagsFn when provided', () => {
64
+ const customSkip = jest.fn(() => true);
65
+ const result = validateTagsCore({
66
+ contentForBraceCheck: '{{ unsubscribe }}',
67
+ contentForUnsubscribeScan: '{{ unsubscribe }}',
68
+ tags: [
69
+ {
70
+ definition: {
71
+ supportedModules: [{ context: 'DEFAULT', mandatory: true }],
72
+ value: 'unsubscribe',
73
+ },
74
+ },
75
+ ],
76
+ currentModule: 'DEFAULT',
77
+ isFullMode: false,
78
+ skipTagsFn: customSkip,
79
+ });
80
+ expect(customSkip).toHaveBeenCalled();
81
+ expect(result.valid).toBe(true);
82
+ });
83
+ });
84
+
85
+ describe('checkIfSupportedTag', () => {
86
+ it('should return false for empty or no injected tags', () => {
87
+ expect(checkIfSupportedTag('someTag', {})).toBe(false);
88
+ expect(checkIfSupportedTag('someTag', undefined)).toBe(false);
89
+ });
90
+
91
+ it('should return true when tag matches a top-level key', () => {
92
+ const injectedTags = { name: { name: 'Name' }, unsubscribe: { name: 'Unsubscribe' } };
93
+ expect(checkIfSupportedTag('name', injectedTags)).toBe(true);
94
+ expect(checkIfSupportedTag('unsubscribe', injectedTags)).toBe(true);
95
+ });
96
+
97
+ it('should return false when tag does not match any key', () => {
98
+ const injectedTags = { name: { name: 'Name' } };
99
+ expect(checkIfSupportedTag('other', injectedTags)).toBe(false);
100
+ });
101
+
102
+ it('should return true when tag matches a nested subtag', () => {
103
+ const injectedTags = {
104
+ customer: {
105
+ name: 'Customer',
106
+ subtags: {
107
+ first_name: { name: 'First Name' },
108
+ last_name: { name: 'Last Name' },
109
+ },
110
+ },
111
+ };
112
+ expect(checkIfSupportedTag('first_name', injectedTags)).toBe(true);
113
+ expect(checkIfSupportedTag('last_name', injectedTags)).toBe(true);
114
+ });
115
+
116
+ it('should return false when tag is in neither top-level nor subtags', () => {
117
+ const injectedTags = {
118
+ customer: {
119
+ name: 'Customer',
120
+ subtags: { first_name: { name: 'First Name' } },
121
+ },
122
+ };
123
+ expect(checkIfSupportedTag('unknown', injectedTags)).toBe(false);
124
+ });
125
+ });
126
+
127
+ describe('transformInjectedTags', () => {
128
+ it('should add tag-header and normalize subtags when key contains "subtags"', () => {
129
+ const tags = [
130
+ {
131
+ name: 'Customer',
132
+ subtags: {
133
+ first_name: { name: 'First Name' },
134
+ },
135
+ },
136
+ ];
137
+ const result = transformInjectedTags(tags);
138
+ expect(result).toBe(tags);
139
+ expect(tags[0]['tag-header']).toBe(true);
140
+ expect(tags[0].subtags).toEqual({ first_name: { name: 'First Name' } });
141
+ });
142
+
143
+ it('should recursively transform nested subtags', () => {
144
+ const tags = [
145
+ {
146
+ name: 'Parent',
147
+ subtags: {
148
+ child: {
149
+ name: 'Child',
150
+ subtags: { grandchild: { name: 'Grandchild' } },
151
+ },
152
+ },
153
+ },
154
+ ];
155
+ transformInjectedTags(tags);
156
+ expect(tags[0].subtags.child.subtags).toEqual({ grandchild: { name: 'Grandchild' } });
157
+ });
158
+
159
+ it('should return tags unchanged when no subtag keys exist', () => {
160
+ const tags = [{ name: 'Simple', desc: 'No subtags' }];
161
+ const result = transformInjectedTags(tags);
162
+ expect(result).toBe(tags);
163
+ expect(tags[0]).toEqual({ name: 'Simple', desc: 'No subtags' });
164
+ });
165
+ });
16
166
 
17
167
  describe("check if curly brackets are balanced", () => {
18
168
  it("test for balanced curly brackets", () => {
@@ -22,6 +172,9 @@ describe("check if curly brackets are balanced", () => {
22
172
  value += "}}"
23
173
  let result = validateIfTagClosed(value);
24
174
  expect(result).toEqual(true);
175
+ // no braces or empty string: match returns null, l1/l2/l3 undefined -> true
176
+ expect(validateIfTagClosed("")).toEqual(true);
177
+ expect(validateIfTagClosed("plain text no braces")).toEqual(true);
25
178
  //valid cases
26
179
  expect(validateIfTagClosed("{{{Hello}}}")).toEqual(true);
27
180
  expect(validateIfTagClosed("{{{Hello}}")).toEqual(true);
@@ -64,53 +217,42 @@ describe("validateTags", () => {
64
217
  it("should return valid response when all tags are present", () => {
65
218
  const content = "Hello {{tag1}}, {{tag2}}, {{tag3}} {{entryTrigger.lifetimePurchases}}";
66
219
 
67
- const injectedTagsParams = [];
68
220
  const location = { query: { module: "DEFAULT" } };
69
221
  const tagModule = null;
70
222
 
71
223
  const result = validateTags({
72
224
  content,
73
225
  tagsParam,
74
- injectedTagsParams,
75
226
  location,
76
227
  tagModule,
77
- eventContextTags,
78
228
  });
79
229
 
80
230
  expect(result.valid).toEqual(true);
81
231
  expect(result.missingTags).toEqual([]);
82
- expect(result.unsupportedTags).toEqual([]);
83
232
  expect(result.isBraceError).toEqual(false);
84
233
  });
85
234
 
86
- it("should return invalid response when a mandatory tag is missing", () => {
235
+ it("should return valid response when content has balanced braces and all tags present", () => {
87
236
  const content = "Hello {{tag1}}, {{tag2}}, {{tag3}}";
88
- const updatedTagsParam = [...tagsParam, {
89
- definition: {
90
- supportedModules: [{ context: "DEFAULT", mandatory: true }],
91
- value: "tag4",
92
- },
93
- },];
94
- const injectedTagsParams = [];
95
237
  const location = { query: { module: "DEFAULT" } };
96
238
  const tagModule = null;
97
239
 
98
240
  const result = validateTags({
99
241
  content,
100
- updatedTagsParam,
101
- injectedTagsParams,
242
+ tagsParam,
102
243
  location,
103
244
  tagModule,
245
+ waitEventContextTags: {},
104
246
  });
105
247
 
106
248
  expect(result.valid).toEqual(true);
107
- expect(result.unsupportedTags).toEqual([]);
249
+ expect(result.missingTags).toEqual([]);
108
250
  expect(result.isBraceError).toEqual(false);
109
251
  });
110
252
 
111
- it("should return invalid response when an unsupported tag is present", () => {
253
+ it("should return valid response when content has balanced braces with missingEventContextTags and partial tagsParam", () => {
112
254
  const content = "Hello {{tag1}}, {{tag2}}, {{tag3}} {{missingEventContextTags}}";
113
- const tagsParam = [
255
+ const tagsParamLocal = [
114
256
  {
115
257
  definition: {
116
258
  supportedModules: [{ context: "DEFAULT", mandatory: true }],
@@ -124,27 +266,25 @@ describe("validateTags", () => {
124
266
  },
125
267
  },
126
268
  ];
127
- const injectedTagsParams = [];
128
269
  const location = { query: { module: "DEFAULT" } };
129
270
  const tagModule = null;
130
271
 
131
272
  const result = validateTags({
132
273
  content,
133
- tagsParam,
134
- injectedTagsParams,
274
+ tagsParam: tagsParamLocal,
135
275
  location,
136
276
  tagModule,
277
+ waitEventContextTags: {},
137
278
  });
138
279
 
139
280
  expect(result.valid).toEqual(true);
140
281
  expect(result.missingTags).toEqual([]);
141
- expect(result.unsupportedTags).toEqual(["tag3", "missingEventContextTags"]);
142
282
  expect(result.isBraceError).toEqual(false);
143
283
  });
144
284
 
145
285
  it("should return invalid response when there is an unbalanced bracket error", () => {
146
286
  const content = "Hello {{tag1}, {{tag2}}, {{tag3}}";
147
- const tagsParam = [
287
+ const tagsParamLocal = [
148
288
  {
149
289
  definition: {
150
290
  supportedModules: [{ context: "DEFAULT", mandatory: true }],
@@ -164,26 +304,24 @@ describe("validateTags", () => {
164
304
  },
165
305
  },
166
306
  ];
167
- const injectedTagsParams = [];
168
307
  const location = { query: { module: "DEFAULT" } };
169
308
  const tagModule = null;
170
309
 
171
310
  const result = validateTags({
172
311
  content,
173
- tagsParam,
174
- injectedTagsParams,
312
+ tagsParam: tagsParamLocal,
175
313
  location,
176
314
  tagModule,
315
+ waitEventContextTags: {},
177
316
  });
178
317
 
179
318
  expect(result.valid).toEqual(false);
180
- expect(result.missingTags).toEqual(["tag1"]);
181
- expect(result.unsupportedTags).toEqual([]);
319
+ expect(result.missingTags).toEqual([]);
182
320
  expect(result.isBraceError).toEqual(true);
183
321
  });
184
322
 
185
- it("should remove 'unsubscribe' from missingTags if skipTags logic is triggered", () => {
186
- const tagsParam = [
323
+ it("should require unsubscribe when mandatory and missing, and accept skipped unsubscribe variant", () => {
324
+ const tagsParamUnsubscribe = [
187
325
  {
188
326
  definition: {
189
327
  supportedModules: [{ context: "DEFAULT", mandatory: true }],
@@ -191,288 +329,341 @@ describe("validateTags", () => {
191
329
  },
192
330
  },
193
331
  ];
194
- // Content does not include {{unsubscribe}}, so it would be missing
195
- const contentMissing = "Hello {{tag1}}";
196
- const injectedTagsParams = [];
197
332
  const location = { query: { module: "DEFAULT" } };
198
333
  const tagModule = null;
199
334
 
200
- // First, verify unsubscribe is missing if not present
335
+ const contentMissing = "Hello world";
201
336
  const resultMissing = validateTags({
202
337
  content: contentMissing,
203
- tagsParam,
204
- injectedTagsParams,
338
+ tagsParam: tagsParamUnsubscribe,
205
339
  location,
206
340
  tagModule,
341
+ waitEventContextTags: {},
207
342
  });
208
343
  expect(resultMissing.missingTags).toContain("unsubscribe");
344
+ expect(resultMissing.valid).toBe(false);
209
345
 
210
- // Now, content includes a tag that triggers skipTags logic for unsubscribe
211
- // e.g., {{unsubscribe(#123456)}} matches the skipTags regex
212
346
  const contentWithSkippedUnsubscribe = "Hello {{unsubscribe(#123456)}}";
213
347
  const resultSkipped = validateTags({
214
348
  content: contentWithSkippedUnsubscribe,
215
- tagsParam,
216
- injectedTagsParams,
349
+ tagsParam: tagsParamUnsubscribe,
217
350
  location,
218
351
  tagModule,
352
+ waitEventContextTags: {},
219
353
  });
220
354
  expect(resultSkipped.missingTags).not.toContain("unsubscribe");
221
355
  expect(resultSkipped.valid).toBe(true);
222
- });
223
- });
224
356
 
225
- describe("extractNames", () => {
226
- it("should return an empty array when data is empty", () => {
227
- const data = [];
228
- const result = extractNames(data);
229
- expect(result).toEqual([]);
357
+ const contentWithWhitespaceUnsubscribe = "Hello {{ unsubscribe }}";
358
+ const resultWhitespace = validateTags({
359
+ content: contentWithWhitespaceUnsubscribe,
360
+ tagsParam: tagsParamUnsubscribe,
361
+ location,
362
+ tagModule,
363
+ });
364
+ expect(resultWhitespace.missingTags).not.toContain("unsubscribe");
365
+ expect(resultWhitespace.valid).toBe(true);
366
+ expect(resultWhitespace.unsupportedTags ?? []).toEqual([]);
230
367
  });
231
368
 
232
- it("should return an array of names when data contains nodes with names", () => {
233
- const data = [
234
- {
235
- name: "Node 1",
236
- children: [
237
- {
238
- name: "Node 1.1",
239
- children: [],
240
- },
241
- {
242
- name: "Node 1.2",
243
- children: [],
244
- },
245
- ],
369
+ it('should treat tags from waitEventContextTags as supported', () => {
370
+ const content = 'Hello {{waitEvent.orderId}}';
371
+ const tagsParam = [];
372
+ const injectedTagsParams = [];
373
+ const location = { query: { module: 'DEFAULT' } };
374
+ const tagModule = null;
375
+ const waitEventContextTags = {
376
+ block1: {
377
+ eventName: 'Order Placed',
378
+ blockName: 'Wait Block',
379
+ tags: [{ tagName: 'waitEvent.orderId', label: 'Order ID' }],
246
380
  },
247
- {
248
- name: "Node 2",
249
- children: [
250
- {
251
- name: "Node 2.1",
252
- children: [],
253
- },
254
- ],
381
+ };
382
+
383
+ const result = validateTags({
384
+ content,
385
+ tagsParam,
386
+ injectedTagsParams,
387
+ location,
388
+ tagModule,
389
+ eventContextTags: [],
390
+ waitEventContextTags,
391
+ });
392
+
393
+ expect(result.valid).toEqual(true);
394
+ expect(result.missingTags).toEqual([]);
395
+ expect(result.isBraceError).toEqual(false);
396
+ });
397
+ });
398
+
399
+ describe('validateTags wrapper (v2 consumers)', () => {
400
+ const tagsWithUnsubscribe = [
401
+ {
402
+ definition: {
403
+ supportedModules: [{ context: 'DEFAULT', mandatory: true }],
404
+ value: 'unsubscribe',
255
405
  },
256
- ];
257
- const result = extractNames(data);
258
- expect(result).toEqual([
259
- "Node 1",
260
- "Node 1.1",
261
- "Node 1.2",
262
- "Node 2",
263
- "Node 2.1",
264
- ]);
265
- });
266
-
267
- it("should ignore nodes without names", () => {
268
- const data = [
269
- {
270
- name: "Node 1",
271
- children: [
272
- {
273
- name: "Node 1.1",
274
- children: [],
275
- },
276
- {
277
- children: [],
278
- },
279
- ],
406
+ },
407
+ ];
408
+ const tagsOutboundUnsubscribe = [
409
+ {
410
+ definition: {
411
+ supportedModules: [{ context: 'outbound', mandatory: true }],
412
+ value: 'unsubscribe',
280
413
  },
281
- {
282
- name: "Node 2",
283
- children: [
284
- {
285
- name: "Node 2.1",
286
- children: [],
287
- },
288
- {
289
- name: "Node 2.2",
290
- },
291
- ],
414
+ },
415
+ ];
416
+ const tagsDefaultLowercase = [
417
+ {
418
+ definition: {
419
+ supportedModules: [{ context: 'default', mandatory: true }],
420
+ value: 'unsubscribe',
292
421
  },
293
- ];
294
- const result = extractNames(data);
295
- expect(result).toEqual([
296
- "Node 1",
297
- "Node 1.1",
298
- "Node 2",
299
- "Node 2.1",
300
- "Node 2.2",
301
- ]);
302
- });
303
- });
422
+ },
423
+ ];
304
424
 
425
+ describe('module selection (location.query.module vs tagModule vs DEFAULT)', () => {
426
+ it('uses location.query.module when set and tagModule not provided', () => {
427
+ const content = 'Hello world';
428
+ const result = validateTags({
429
+ content,
430
+ tagsParam: tagsWithUnsubscribe,
431
+ location: { query: { module: 'DEFAULT' } },
432
+ tagModule: null,
433
+ });
434
+ expect(result.missingTags).toContain('unsubscribe');
435
+ expect(result.valid).toBe(false);
436
+ });
305
437
 
306
- describe("checkSupport", () => {
307
- it("should return an empty array when args are empty", () => {
308
- const result = checkSupport();
309
- expect(result).toEqual([]);
310
- });
311
- it("should return an empty array when response data is empty", () => {
312
- const response = { data: [] };
313
- const tagObject = {};
314
- const result = checkSupport(response, tagObject);
315
- expect(result).toEqual([]);
316
- });
438
+ it('uses tagModule override when provided (overrides location.query.module)', () => {
439
+ const content = 'Hello world';
440
+ const resultWithOutbound = validateTags({
441
+ content,
442
+ tagsParam: tagsOutboundUnsubscribe,
443
+ location: { query: { module: 'DEFAULT' } },
444
+ tagModule: 'outbound',
445
+ });
446
+ expect(resultWithOutbound.missingTags).toContain('unsubscribe');
447
+ expect(resultWithOutbound.valid).toBe(false);
448
+
449
+ const resultWithDefault = validateTags({
450
+ content,
451
+ tagsParam: tagsWithUnsubscribe,
452
+ location: { query: { module: 'outbound' } },
453
+ tagModule: 'DEFAULT',
454
+ });
455
+ expect(resultWithDefault.missingTags).toContain('unsubscribe');
456
+ expect(resultWithDefault.valid).toBe(false);
457
+ });
317
458
 
318
- it("should return an empty array when tagObject is empty", () => {
319
- const response = {
320
- data: [{ name: "tag1" }, { name: "tag2" }, { name: "tag3" }],
321
- };
322
- const tagObject = {};
323
- const result = checkSupport(response, tagObject);
324
- expect(result).toEqual([]);
325
- });
459
+ it('uses DEFAULT (lowercase) when location is undefined', () => {
460
+ const content = 'Hello world';
461
+ const result = validateTags({
462
+ content,
463
+ tagsParam: tagsDefaultLowercase,
464
+ location: undefined,
465
+ tagModule: null,
466
+ });
467
+ expect(result.missingTags).toContain('unsubscribe');
468
+ expect(result.valid).toBe(false);
469
+ });
326
470
 
327
- it("should return event context tags even if tagObject is empty", () => {
328
- const response = {
329
- data: [{ name: "tag1" }, { name: "entryTrigger.lifetimePurchases" }],
330
- };
331
- const tagObject = {};
332
- const result = checkSupport(response, tagObject, eventContextTags);
333
- expect(result).toEqual(['entryTrigger.lifetimePurchases']);
471
+ it('uses DEFAULT when location.query is undefined', () => {
472
+ const content = 'Hello world';
473
+ const result = validateTags({
474
+ content,
475
+ tagsParam: tagsDefaultLowercase,
476
+ location: {},
477
+ tagModule: null,
478
+ });
479
+ expect(result.missingTags).toContain('unsubscribe');
480
+ expect(result.valid).toBe(false);
481
+ });
482
+
483
+ it('uses DEFAULT when location.query.module is falsy', () => {
484
+ const content = 'Hello world';
485
+ const result = validateTags({
486
+ content,
487
+ tagsParam: tagsDefaultLowercase,
488
+ location: { query: { module: '' } },
489
+ tagModule: null,
490
+ });
491
+ expect(result.missingTags).toContain('unsubscribe');
492
+ expect(result.valid).toBe(false);
493
+ });
334
494
  });
335
495
 
336
- it("should return an array of supported tags", () => {
337
- const response = {
338
- data: [{ name: "tag1" }, { name: "tag2" }, { name: "tag3" }],
339
- };
340
- const tagObject = {
341
- tag1: { definition: { subtags: ["tag1.1", "tag1.2"] } },
342
- tag2: { definition: { subtags: ["tag2.1", "tag2.2"] } },
343
- tag3: { definition: { subtags: ["tag3.1", "tag3.2"] } },
344
- };
345
- const result = checkSupport(response, tagObject);
346
- expect(result).toEqual(["tag1", "tag2", "tag3"]);
496
+ describe('content passed to contentForBraceCheck and contentForUnsubscribeScan', () => {
497
+ it('uses same content for brace check (isBraceError when unbalanced)', () => {
498
+ const content = 'Hello {{tag1}, {{tag2}}';
499
+ const result = validateTags({
500
+ content,
501
+ tagsParam: [],
502
+ location: { query: { module: 'DEFAULT' } },
503
+ });
504
+ expect(result.isBraceError).toBe(true);
505
+ expect(result.valid).toBe(false);
506
+ });
507
+
508
+ it('uses same content for unsubscribe scan (missing unsubscribe when required)', () => {
509
+ const content = 'Hello {{other}}';
510
+ const result = validateTags({
511
+ content,
512
+ tagsParam: tagsWithUnsubscribe,
513
+ location: { query: { module: 'DEFAULT' } },
514
+ });
515
+ expect(result.missingTags).toContain('unsubscribe');
516
+ expect(result.valid).toBe(false);
517
+ });
518
+
519
+ it('content with {{ unsubscribe }} satisfies unsubscribe requirement', () => {
520
+ const content = 'Hello {{ unsubscribe }}';
521
+ const result = validateTags({
522
+ content,
523
+ tagsParam: tagsWithUnsubscribe,
524
+ location: { query: { module: 'DEFAULT' } },
525
+ });
526
+ expect(result.missingTags).not.toContain('unsubscribe');
527
+ expect(result.valid).toBe(true);
528
+ });
347
529
  });
348
530
 
349
- it("should return an array of supported tags with subtags", () => {
350
- const response = {
351
- data: [{ name: "tag1" }, { name: "tag2" }, { name: "tag3" }],
352
- };
353
- const tagObject = {
354
- tag1: { definition: { subtags: ["tag1.1", "tag1.2"] } },
355
- tag2: { definition: { subtags: ["tag2.1", "tag2.2"] } },
356
- tag3: { definition: { subtags: ["tag3.1", "tag3.2"] } },
357
- "tag1.1": {},
358
- "tag2.1": {},
359
- "tag3.1": {},
360
- };
361
- const result = checkSupport(response, tagObject);
362
- expect(result).toEqual(["tag1", "tag2", "tag3"]);
531
+ describe('isFullMode', () => {
532
+ it('when true skips unsubscribe check (no missingTags for mandatory unsubscribe)', () => {
533
+ const content = 'Hello world';
534
+ const result = validateTags({
535
+ content,
536
+ tagsParam: tagsWithUnsubscribe,
537
+ location: { query: { module: 'DEFAULT' } },
538
+ isFullMode: true,
539
+ });
540
+ expect(result.missingTags).toEqual([]);
541
+ expect(result.valid).toBe(true);
542
+ });
543
+
544
+ it('when true still runs brace check', () => {
545
+ const content = 'Hello {{tag1}';
546
+ const result = validateTags({
547
+ content,
548
+ tagsParam: tagsWithUnsubscribe,
549
+ location: { query: { module: 'DEFAULT' } },
550
+ isFullMode: true,
551
+ });
552
+ expect(result.isBraceError).toBe(true);
553
+ expect(result.valid).toBe(false);
554
+ });
363
555
  });
364
556
 
365
- it("should return an empty array when no tags are supported", () => {
366
- const response = {
367
- data: [{ name: "tag1" }, { name: "tag2" }, { name: "tag3" }],
368
- };
369
- const tagObject = {
370
- tag4: {},
371
- tag5: {},
372
- tag6: {},
373
- };
374
- const result = checkSupport(response, tagObject);
375
- expect(result).toEqual([]);
557
+ describe('tagsParam null or empty', () => {
558
+ it('when null: only brace check runs, no missing-tag logic', () => {
559
+ const content = 'Hello {{a}}';
560
+ const result = validateTags({
561
+ content,
562
+ tagsParam: null,
563
+ location: { query: { module: 'DEFAULT' } },
564
+ });
565
+ expect(result.missingTags).toEqual([]);
566
+ expect(result.isBraceError).toBe(false);
567
+ expect(result.valid).toBe(true);
568
+ });
569
+
570
+ it('when empty array: only brace check runs', () => {
571
+ const content = 'Hello {{a}}';
572
+ const result = validateTags({
573
+ content,
574
+ tagsParam: [],
575
+ location: { query: { module: 'DEFAULT' } },
576
+ });
577
+ expect(result.missingTags).toEqual([]);
578
+ expect(result.valid).toBe(true);
579
+ });
580
+
581
+ it('when null with unbalanced braces: returns isBraceError', () => {
582
+ const content = 'Hello {{a}';
583
+ const result = validateTags({
584
+ content,
585
+ tagsParam: null,
586
+ location: { query: { module: 'DEFAULT' } },
587
+ });
588
+ expect(result.isBraceError).toBe(true);
589
+ expect(result.valid).toBe(false);
590
+ expect(result.missingTags).toEqual([]);
591
+ });
376
592
  });
377
- it("should return an array of supported tags with subtags", () => {
378
- const response = {
379
- data: [
380
- {
381
- name: "tag1",
382
- children: [{ name: "tag1.1", children: [{ name: "tag1.2" }] }],
383
- },
384
- { name: "tag2", children: [] },
385
- { name: "tag3" },
386
- ],
387
- };
388
- const tagObject = {
389
- tag1: { definition: { subtags: [".1", "tag1.2"] } },
390
- tag2: { definition: { subtags: ["tag2.1", "tag2.2"] } },
391
- tag3: { definition: { subtags: ["tag3.1", "tag3.2"] } },
392
- "tag1.1": {},
393
- "tag2.1": {},
394
- "tag3.1": {},
395
- };
396
- const result = checkSupport(response, tagObject);
397
- expect(result).toEqual(["tag1", "tag1.1", "tag2", "tag3"]);
398
- });
399
- it("should return an array of supported tags children as an object", () => {
400
- const response = {
401
- data: [
402
- { name: "tag1", children: [{ name: "tag" }] },
403
- { name: "tag2", children: [] },
404
- { name: "tag3" },
405
- ],
406
- };
407
- const tagObject = {
408
- tag1: { definition: { subtags: [".1", "tag1.2"] } },
409
- tag2: { definition: { subtags: ["tag2.1", "tag2.2"] } },
410
- tag3: { definition: { subtags: ["tag3.1", "tag3.2"] } },
411
- "tag1.1": {},
412
- "tag2.1": {},
413
- "tag3.1": {},
414
- };
415
- const result = checkSupport(response, tagObject);
416
- expect(result).toEqual(["tag1", "tag2", "tag3"]);
417
- });
418
- it("should return an empty array if tagObject is empty", () => {
419
- const response = {
420
- data: [
421
- { name: "tag1", children: [{ name: "tag" }] },
422
- { name: "tag2", children: [] },
423
- { name: "tag3" },
424
- ],
425
- };
426
- const result = checkSupport(response, {});
427
- expect(result).toEqual([]);
428
- });
429
-
430
- it("should add childName to supportedList if it is a subtag of parentTag in eventContextTags", () => {
431
- const response = { data: [{ name: "leaderboard", children: [{name: "person.userId"}]}]};
432
- const tagObject = {};
433
- const eventContextTags = [{ tagName: "leaderboard", subTags: ["userId"]}];
434
- const isLiquidFlow = true;
435
- const result = checkSupport(response, tagObject, eventContextTags, isLiquidFlow);
436
- expect(result).toEqual( [ 'leaderboard', 'person.userId' ]);
437
- });
438
-
439
- it("should not add childName to supportedList which does not have dot in eventContextTags", () => {
440
- // This case is unlikely to happen as we are not supporting tags without dot in eventContextTags
441
- const response = { data: [{ name: "entryTrigger.lifetimePoints", children: [{name: "userId"}] }]};
442
- const tagObject = {};
443
- const eventContextTags = [{ tagName: "entryTrigger.lifetimePoints", children: [{name: "userId"}] }];
444
- const isLiquidFlow = true;
445
- const result = checkSupport(response, tagObject, eventContextTags, isLiquidFlow);
446
- expect(result).toEqual( [ "entryTrigger.lifetimePoints" ]);
447
- });
448
-
449
- it("should add only parent tag to supportedList if isLiquidFlow false", () => {
450
- const response = { data: [{ name: "leaderboard", children: [{name: "person.userId"}]}]};
451
- const tagObject = {};
452
- const eventContextTags = [{ tagName: "leaderboard", subTags: ["userId"]}];
453
- const isLiquidFlow = false;
454
- const result = checkSupport(response, tagObject, eventContextTags, isLiquidFlow);
455
- expect(result).toEqual( [ 'leaderboard' ]);
456
- });
457
-
458
- it("test for checking loyalty tags in that are coming in forwardedTags", () => {
459
- const response = { data: [{ name: "leaderboard", children: [{name: "person.userId"}]}]};
460
- const tagObject = {};
461
- const isLiquidFlow = true;
462
- // forwardedTags contains tag hierarchy with parent tags and their subtags
463
- // needed for loyalty email liquid tag resolution
464
- const forwardedTags = {
465
- leaderboard: {
466
- name: "Leaderboard",
467
- subtags: {
468
- "person.userId": {
469
- name: "User ID",
470
- },
471
- },
472
- },
473
- };
474
- const result = checkSupport(response, tagObject, [], isLiquidFlow, forwardedTags);
475
- expect(result).toEqual( [ 'leaderboard', 'person.userId' ]);
593
+
594
+ describe('v2 consumer call patterns', () => {
595
+ it('Whatsapp-style: content, tagsParam, location, tagModule (getDefaultTags), isFullMode', () => {
596
+ const content = 'Hello {{ unsubscribe }}';
597
+ const result = validateTags({
598
+ content: content,
599
+ tagsParam: tagsWithUnsubscribe,
600
+ location: { query: { module: 'DEFAULT' } },
601
+ tagModule: 'DEFAULT',
602
+ isFullMode: false,
603
+ });
604
+ expect(result.valid).toBe(true);
605
+ expect(result.isBraceError).toBe(false);
606
+ });
607
+
608
+ it('Zalo / Rcs / MobilePushNew / EmailWrapper: same pattern as Whatsapp', () => {
609
+ const result = validateTags({
610
+ content: 'Hi {{ unsubscribe }}',
611
+ tagsParam: tagsWithUnsubscribe,
612
+ location: { query: { module: 'DEFAULT' } },
613
+ tagModule: 'DEFAULT',
614
+ isFullMode: false,
615
+ });
616
+ expect(result.valid).toBe(true);
617
+ });
618
+
619
+ it('Line / Viber: tagModule "outbound"', () => {
620
+ const contentMissing = 'Hello';
621
+ const resultMissing = validateTags({
622
+ content: contentMissing,
623
+ tagsParam: tagsOutboundUnsubscribe,
624
+ location: { query: { module: 'inbound' } },
625
+ tagModule: 'outbound',
626
+ isFullMode: false,
627
+ });
628
+ expect(resultMissing.missingTags).toContain('unsubscribe');
629
+ expect(resultMissing.valid).toBe(false);
630
+
631
+ const resultOk = validateTags({
632
+ content: 'Hello {{ unsubscribe }}',
633
+ tagsParam: tagsOutboundUnsubscribe,
634
+ location: {},
635
+ tagModule: 'outbound',
636
+ isFullMode: false,
637
+ });
638
+ expect(resultOk.valid).toBe(true);
639
+ });
640
+
641
+ it('WebPush: validationConfig spread (content + tagsParam, location, tagModule, isFullMode)', () => {
642
+ const validationConfig = {
643
+ tagsParam: tagsWithUnsubscribe,
644
+ location: { query: { module: 'DEFAULT' } },
645
+ tagModule: 'DEFAULT',
646
+ };
647
+ const result = validateTags({
648
+ content: 'Hello {{ unsubscribe }}',
649
+ ...validationConfig,
650
+ isFullMode: false,
651
+ });
652
+ expect(result.valid).toBe(true);
653
+ expect(result.isBraceError).toBe(false);
654
+ });
655
+
656
+ it('WebPush with isFullMode: only brace check', () => {
657
+ const result = validateTags({
658
+ content: 'Hello world',
659
+ tagsParam: tagsWithUnsubscribe,
660
+ location: { query: { module: 'DEFAULT' } },
661
+ tagModule: 'DEFAULT',
662
+ isFullMode: true,
663
+ });
664
+ expect(result.valid).toBe(true);
665
+ expect(result.missingTags).toEqual([]);
666
+ });
476
667
  });
477
668
  });
478
669
 
@@ -1048,6 +1239,10 @@ describe('getForwardedMapValues', () => {
1048
1239
  expect(getForwardedMapValues(input)).toEqual(expected);
1049
1240
  });
1050
1241
 
1242
+ test('should return empty object when called with no argument (default param)', () => {
1243
+ expect(getForwardedMapValues()).toEqual({});
1244
+ });
1245
+
1051
1246
  test('should correctly process objects with subtags', () => {
1052
1247
  const input = {
1053
1248
  customer: {
@@ -1309,84 +1504,6 @@ describe('getForwardedMapValues', () => {
1309
1504
  });
1310
1505
  });
1311
1506
 
1312
- describe('isInsideLiquidBlock', () => {
1313
- it('returns true for index inside a single block', () => {
1314
- const content = 'Hello {% assign foo = 1 %} World';
1315
- // Index of 'a' in 'assign' inside the block
1316
- const tagIndex = content.indexOf('assign');
1317
- expect(isInsideLiquidBlock(content, tagIndex)).toBe(true);
1318
- });
1319
-
1320
- it('returns false for index outside any block', () => {
1321
- const content = 'Hello {% assign foo = 1 %} World';
1322
- // Index of 'H' in 'Hello'
1323
- expect(isInsideLiquidBlock(content, 0)).toBe(false);
1324
- // Index of 'W' in 'World'
1325
- expect(isInsideLiquidBlock(content, content.indexOf('World'))).toBe(false);
1326
- });
1327
-
1328
- it('returns true for index at the start of a block', () => {
1329
- const content = 'Hello {% assign foo = 1 %} World';
1330
- // Index of '{' in '{%'
1331
- const tagIndex = content.indexOf('{%');
1332
- expect(isInsideLiquidBlock(content, tagIndex)).toBe(true);
1333
- });
1334
-
1335
- it('returns false for index at the end of a block (exclusive)', () => {
1336
- const content = 'Hello {% assign foo = 1 %} World';
1337
- // Index just after the closing '%}'
1338
- const blockEnd = content.indexOf('%}') + 2;
1339
- expect(isInsideLiquidBlock(content, blockEnd)).toBe(false);
1340
- });
1341
-
1342
- it('returns true for index inside the second of multiple blocks', () => {
1343
- const content = 'A {% first %} B {% second %} C';
1344
- const tagIndex = content.indexOf('second');
1345
- expect(isInsideLiquidBlock(content, tagIndex)).toBe(true);
1346
- });
1347
-
1348
- it('returns false for index between blocks', () => {
1349
- const content = 'A {% first %} B {% second %} C';
1350
- // Index of 'B' (between blocks)
1351
- const tagIndex = content.indexOf('B');
1352
- expect(isInsideLiquidBlock(content, tagIndex)).toBe(false);
1353
- });
1354
-
1355
- it('returns false for empty string', () => {
1356
- expect(isInsideLiquidBlock('', 0)).toBe(false);
1357
- });
1358
-
1359
- it('returns false if there are no blocks', () => {
1360
- const content = 'Just some text with no blocks';
1361
- expect(isInsideLiquidBlock(content, 5)).toBe(false);
1362
- });
1363
-
1364
- it('returns false for negative index', () => {
1365
- const content = 'Hello {% assign foo = 1 %} World';
1366
- expect(isInsideLiquidBlock(content, -1)).toBe(false);
1367
- });
1368
-
1369
- it('returns false for index beyond string length', () => {
1370
- const content = 'Hello {% assign foo = 1 %} World';
1371
- expect(isInsideLiquidBlock(content, 100)).toBe(false);
1372
- });
1373
-
1374
- it('works for nested-like blocks (not truly nested)', () => {
1375
- const content = 'A {% outer {% inner %} outer %} B';
1376
- // Index of 'inner' (should be inside the first block)
1377
- const tagIndex = content.indexOf('inner');
1378
- expect(isInsideLiquidBlock(content, tagIndex)).toBe(true);
1379
- });
1380
-
1381
- it('returns true for index at last char inside block', () => {
1382
- const content = 'A {% foo %} B';
1383
- // Index of last char inside block (just before %})
1384
- const blockStart = content.indexOf('{%');
1385
- const blockEnd = content.indexOf('%}');
1386
- expect(isInsideLiquidBlock(content, blockEnd - 1)).toBe(true);
1387
- });
1388
- });
1389
-
1390
1507
  describe('containsBase64Images', () => {
1391
1508
  let mockCapNotification;
1392
1509
  let mockCallback;