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
@@ -21,7 +21,6 @@ import {
21
21
  expectedStateGetSchemaForEntitySuccessTAG,
22
22
  expectedStateGetSchemaForEntitySuccess,
23
23
  } from '../mockData';
24
- import { TAG } from '../../Whatsapp/constants';
25
24
  import { loadItem } from '../../../services/localStorageApi';
26
25
 
27
26
 
@@ -118,104 +117,3 @@ describe('should handle GET_SUPPORT_VIDEOS_CONFIG', () => {
118
117
  expect(reducer(mockedInitialState, action).toJS())?.fetchingSchema?.toEqual(true);
119
118
  });
120
119
  });
121
-
122
- describe('GET_SCHEMA_FOR_ENTITY_SUCCESS handler', () => {
123
- it.concurrent('should handle existing tagLookupMap correctly when metaEntities and tagLookupMap exist', () => {
124
- const initialStateTest = fromJS({
125
- token: loadItem('token') || '',
126
- orgID: loadItem('orgID') || '',
127
- messages: [],
128
- metaEntities: {
129
- tags: [{ id: 1, name: 'tag1' }],
130
- layouts: ['layout1', 'layout2'],
131
- tagLookupMap: {
132
- existingTag: { definition: { value: 'existing' } },
133
- anotherTag: { definition: { value: 'another' } },
134
- },
135
- standard: [],
136
- },
137
- liquidTags: [],
138
- fetchingLiquidTags: false,
139
- fetchingSchema: false,
140
- fetchingSchemaError: '',
141
- });
142
-
143
- let action = {
144
- type: GET_SCHEMA_FOR_ENTITY_SUCCESS,
145
- entityType: TAG,
146
- data: {
147
- metaEntities: {
148
- standard: [],
149
- custom: [],
150
- extended: [],
151
- },
152
- },
153
- };
154
-
155
- let newState = reducer(initialStateTest, action);
156
- let metaEntities = newState.get('metaEntities');
157
-
158
- expect(metaEntities).toEqual(expect.objectContaining({
159
- tagLookupMap: expect.objectContaining({
160
- existingTag: expect.objectContaining({
161
- definition: expect.objectContaining({
162
- value: 'existing',
163
- }),
164
- }),
165
- }),
166
- }));
167
-
168
- action = {
169
- type: GET_SCHEMA_FOR_ENTITY_SUCCESS,
170
- entityType: 'LAYOUT',
171
- data: {
172
- metaEntities: {
173
- layouts: ['layout1', 'layout2'],
174
- standard: [],
175
- },
176
- },
177
- };
178
- newState = reducer(initialStateTest, action);
179
- metaEntities = newState.get('metaEntities');
180
- expect(metaEntities).toEqual(expect.objectContaining({
181
- layouts: expect.objectContaining({
182
- layouts: expect.arrayContaining(['layout1', 'layout2']),
183
- }),
184
- }));
185
- });
186
-
187
- it.concurrent('should handle non-existent tagLookupMap by returning empty object', () => {
188
- const initialStateTest = fromJS({
189
- token: loadItem('token') || '',
190
- orgID: loadItem('orgID') || '',
191
- messages: [],
192
- metaEntities: {
193
- tagLookupMap: {},
194
- },
195
- liquidTags: [],
196
- fetchingLiquidTags: false,
197
- fetchingSchema: false,
198
- fetchingSchemaError: '',
199
- });
200
-
201
- const action = {
202
- type: GET_SCHEMA_FOR_ENTITY_SUCCESS,
203
- entityType: TAG,
204
- data: {
205
- metaEntities: {
206
- standard: [],
207
- custom: [],
208
- extended: [],
209
- },
210
- },
211
- };
212
-
213
- const newState = reducer(initialStateTest, action);
214
- const metaEntities = newState.get('metaEntities');
215
-
216
- // Updated assertions to handle plain object
217
- expect(metaEntities).toBeDefined();
218
- expect(metaEntities.tagLookupMap).toBeDefined();
219
- expect(metaEntities.tagLookupMap).toEqual({});
220
- });
221
- });
@@ -0,0 +1,291 @@
1
+ /**
2
+ * CommunicationFlow - Orchestrator Component
3
+ *
4
+ * Manages the step flow, validation, and data aggregation.
5
+ * Renders steps based on config and calls callbacks with aggregated data.
6
+ */
7
+
8
+ import React, {
9
+ useState, useCallback, useMemo, useEffect,
10
+ } from 'react';
11
+ import PropTypes from 'prop-types';
12
+ import { injectIntl } from 'react-intl';
13
+ import { compose } from 'redux';
14
+ import { connect } from 'react-redux';
15
+ import { createStructuredSelector } from 'reselect';
16
+ // import { CouponsCapContainer } from '@capillarytech/cap-coupons'; // Commented - cap-coupons flows disabled
17
+ import CapRow from '@capillarytech/cap-ui-library/CapRow';
18
+ import CapDivider from '@capillarytech/cap-ui-library/CapDivider';
19
+ import CapButton from '@capillarytech/cap-ui-library/CapButton';
20
+ // import injectSaga from '../../utils/injectSaga'; // cap-coupons flows disabled
21
+ // import injectReducer from '../../utils/injectReducer';
22
+ import { makeSelectAuthenticated } from '../Cap/selectors';
23
+ import DynamicControlsStep from './steps/DynamicControlsStep';
24
+ import MessageTypeStep from './steps/MessageTypeStep';
25
+ import CommunicationStrategyStep from './steps/CommunicationStrategyStep';
26
+ import ChannelSelectionStep from './steps/ChannelSelectionStep';
27
+ import {
28
+ STEPS,
29
+ CHANNEL_PRIORITY,
30
+ AB_TEST,
31
+ } from './constants';
32
+ import { getEnabledSteps } from './utils/getEnabledSteps';
33
+ import messages from './messages';
34
+ import './CommunicationFlow.scss';
35
+
36
+ // Inject couponsCap reducer and saga - commented out (cap-coupons flows disabled)
37
+ // const withCouponsReducer = injectReducer({
38
+ // key: 'couponsCap',
39
+ // reducer: CouponsCapContainer.couponsCapReducer,
40
+ // });
41
+ // const withCouponsSaga = injectSaga({
42
+ // key: 'couponsCapSaga',
43
+ // saga: CouponsCapContainer.couponsCapSaga,
44
+ // });
45
+
46
+ const CommunicationFlow = ({
47
+ config,
48
+ initialData,
49
+ onSave,
50
+ onCancel, // eslint-disable-line
51
+ onChange,
52
+ intl,
53
+ capData, // From Redux - contains user/org info needed by CouponsWrapper
54
+ }) => {
55
+ const { formatMessage } = intl || {};
56
+ const { messageTypeData = {}, communicationStrategyData = {}, contentTemplateData = {} } = config?.features || {};
57
+ // Initialize step data from initialData or defaults
58
+ const [stepData, setStepData] = useState(() => {
59
+ const defaultMessageType = messageTypeData.defaultOption?.value || null;
60
+ return {
61
+ messageType: initialData?.messageType || defaultMessageType,
62
+ communicationStrategy: initialData?.communicationStrategy || null,
63
+ channel: initialData?.channel || config.channel || null,
64
+ channels: initialData?.channels || [],
65
+ selectedOfferDetails: initialData?.selectedOfferDetails || [],
66
+ contentItems: initialData?.contentItems || [],
67
+ deliverySetting: initialData?.deliverySetting || {},
68
+ dynamicControls: initialData?.dynamicControls || {},
69
+ };
70
+ });
71
+ const [validationErrors, setValidationErrors] = useState({});
72
+
73
+ // Memoize enabled steps
74
+ const enabledSteps = useMemo(() => getEnabledSteps(config), [config]);
75
+
76
+ /**
77
+ * Get aggregated data from all steps
78
+ */
79
+ const getAggregatedData = useCallback(() => {
80
+ const { communicationStrategy } = stepData;
81
+ const isMultiChannel = [CHANNEL_PRIORITY, AB_TEST].includes(communicationStrategy);
82
+
83
+ return {
84
+ messageType: stepData.messageType,
85
+ communicationStrategy: stepData.communicationStrategy,
86
+ channel: isMultiChannel ? null : stepData.channel,
87
+ channels: isMultiChannel ? stepData.channels : [],
88
+ selectedOfferDetails: stepData.selectedOfferDetails,
89
+ contentItems: stepData.contentItems || [],
90
+ deliverySetting: stepData.deliverySetting,
91
+ dynamicControls: stepData.dynamicControls,
92
+ };
93
+ }, [stepData]);
94
+
95
+ /**
96
+ * Handle step data change
97
+ */
98
+ const handleStepChange = useCallback((step, data) => {
99
+ setStepData((prevStepData) => ({
100
+ ...prevStepData,
101
+ ...data,
102
+ }));
103
+ setValidationErrors((prevErrors) => ({
104
+ ...prevErrors,
105
+ [step]: null, // Clear validation error for this step
106
+ }));
107
+ }, []);
108
+
109
+ // Call onChange callback when stepData changes
110
+ useEffect(() => {
111
+ if (onChange) {
112
+ onChange(getAggregatedData());
113
+ }
114
+ // eslint-disable-next-line react-hooks/exhaustive-deps
115
+ }, [stepData]);
116
+
117
+ /**
118
+ * Render all enabled steps
119
+ */
120
+ const renderSteps = useCallback(() => enabledSteps.map((step) => {
121
+ const commonProps = {
122
+ value: stepData,
123
+ onChange: (data) => handleStepChange(step, data),
124
+ error: validationErrors[step],
125
+ };
126
+
127
+ switch (step) {
128
+ case STEPS.MESSAGE_TYPE:
129
+ return (
130
+ <CapRow key={step}>
131
+ <MessageTypeStep
132
+ {...commonProps}
133
+ value={stepData.messageType}
134
+ options={messageTypeData.options}
135
+ defaultOption={messageTypeData.defaultOption}
136
+ onChange={(messageType) => handleStepChange(step, { messageType })}
137
+ />
138
+ <CapDivider />
139
+ </CapRow>
140
+ );
141
+ case STEPS.COMMUNICATION_STRATEGY:
142
+ return (
143
+ <CapRow key={step}>
144
+ <CommunicationStrategyStep
145
+ {...commonProps}
146
+ value={stepData.communicationStrategy}
147
+ // messageType={stepData.messageType}
148
+ options={communicationStrategyData.options}
149
+ disabled={communicationStrategyData.disabled}
150
+ onChange={(communicationStrategy) => handleStepChange(step, { communicationStrategy })}
151
+ />
152
+ <CapDivider />
153
+ </CapRow>
154
+ );
155
+ case STEPS.CHANNEL_SELECTION:
156
+ // Only show ChannelSelectionStep if communication strategy is selected
157
+ if (!stepData.communicationStrategy) {
158
+ return null;
159
+ }
160
+ return (
161
+ <CapRow key={step}>
162
+ <ChannelSelectionStep
163
+ {...commonProps}
164
+ value={stepData}
165
+ channels={contentTemplateData.channels}
166
+ onChange={(data) => handleStepChange(step, data)}
167
+ channelsToHide={contentTemplateData.channelsToHide}
168
+ channelsToDisable={contentTemplateData.channelsToDisable}
169
+ creativesMode={config.mode || 'create'}
170
+ selectedOfferDetails={stepData.selectedOfferDetails}
171
+ incentivesData={config.features?.incentivesData}
172
+ deliverySettingsData={config.features?.deliverySettingsData}
173
+ config={config}
174
+ capData={capData}
175
+ />
176
+ <CapDivider />
177
+ </CapRow>
178
+ );
179
+ // This will be added back in when coupons/points are integrated so keeping it commented out for now
180
+ // case STEPS.INCENTIVES:
181
+ // return (
182
+ // <IncentivesStep
183
+ // key={step}
184
+ // {...commonProps}
185
+ // value={stepData.selectedOfferDetails}
186
+ // onChange={(selectedOfferDetails) => handleStepChange(step, { selectedOfferDetails })}
187
+ // />
188
+ // );
189
+ case STEPS.DYNAMIC_CONTROLS:
190
+ // Only show DynamicControlsStep if communication strategy is selected
191
+ if (!stepData.communicationStrategy) {
192
+ return null;
193
+ }
194
+ return (
195
+ <DynamicControlsStep
196
+ key={step}
197
+ {...commonProps}
198
+ value={{ dynamicControls: stepData.dynamicControls }}
199
+ controls={config.features?.dynamicControlsData?.controls || []}
200
+ onChange={(data) => handleStepChange(step, { dynamicControls: data.dynamicControls })}
201
+ />
202
+ );
203
+ default:
204
+ return null;
205
+ }
206
+ }), [enabledSteps, stepData, validationErrors, config, handleStepChange, messageTypeData, communicationStrategyData, contentTemplateData]);
207
+
208
+ return (
209
+ <CapRow className="communication-flow-container">
210
+ {renderSteps()}
211
+ {onSave && (
212
+ <CapRow className="communication-flow-container__footer">
213
+ <CapButton type="primary" onClick={() => onSave(getAggregatedData())}>
214
+ {formatMessage(messages.save)}
215
+ </CapButton>
216
+ </CapRow>
217
+ )}
218
+ </CapRow>
219
+ );
220
+ };
221
+
222
+ CommunicationFlow.propTypes = {
223
+ config: PropTypes.shape({
224
+ consumer: PropTypes.oneOf(['campaigns', 'loyalty', 'adiona']).isRequired,
225
+ channel: PropTypes.string,
226
+ mode: PropTypes.oneOf(['create', 'edit', 'preview']).isRequired,
227
+ channelsToHide: PropTypes.arrayOf(PropTypes.string),
228
+ channelsToDisable: PropTypes.arrayOf(PropTypes.string),
229
+ features: PropTypes.shape({
230
+ enableMessageType: PropTypes.bool,
231
+ enableCommunicationStrategy: PropTypes.bool,
232
+ messageTypeData: PropTypes.shape({
233
+ options: PropTypes.arrayOf(PropTypes.shape({
234
+ value: PropTypes.string.isRequired,
235
+ label: PropTypes.node.isRequired,
236
+ })),
237
+ defaultOption: PropTypes.shape({
238
+ value: PropTypes.string.isRequired,
239
+ label: PropTypes.node.isRequired,
240
+ }),
241
+ required: PropTypes.bool,
242
+ }),
243
+ communicationStrategyData: PropTypes.shape({
244
+ options: PropTypes.arrayOf(PropTypes.shape({
245
+ value: PropTypes.string.isRequired,
246
+ label: PropTypes.node.isRequired,
247
+ })),
248
+ defaultOption: PropTypes.shape({
249
+ value: PropTypes.string.isRequired,
250
+ label: PropTypes.node.isRequired,
251
+ }),
252
+ required: PropTypes.bool,
253
+ disabled: PropTypes.bool,
254
+ }),
255
+ contentTemplateData: PropTypes.shape({
256
+ required: PropTypes.bool,
257
+ channels: PropTypes.object,
258
+ }),
259
+ enableIncentives: PropTypes.bool,
260
+ enableDeliverySettings: PropTypes.bool,
261
+ enableOtherSettings: PropTypes.bool,
262
+ incentivesTypes: PropTypes.arrayOf(PropTypes.oneOf(['coupons', 'points', 'promotions', 'giftVouchers', 'badges'])),
263
+ }),
264
+ context: PropTypes.object,
265
+ }).isRequired,
266
+ initialData: PropTypes.object,
267
+ onSave: PropTypes.func.isRequired,
268
+ onCancel: PropTypes.func.isRequired,
269
+ onChange: PropTypes.func,
270
+ intl: PropTypes.object.isRequired,
271
+ capData: PropTypes.object,
272
+ };
273
+
274
+ CommunicationFlow.defaultProps = {
275
+ initialData: null,
276
+ onChange: null,
277
+ capData: {},
278
+ };
279
+
280
+ const mapStateToProps = createStructuredSelector({
281
+ capData: makeSelectAuthenticated(),
282
+ });
283
+
284
+ const withConnect = connect(mapStateToProps);
285
+
286
+ export default compose(
287
+ // withCouponsReducer, // cap-coupons flows disabled
288
+ // withCouponsSaga, // cap-coupons flows disabled
289
+ withConnect,
290
+ injectIntl,
291
+ )(CommunicationFlow);
@@ -0,0 +1,25 @@
1
+ @import '~@capillarytech/cap-ui-library/styles/_variables.scss';
2
+
3
+ // .slidebox-content-container is a DOM ancestor of .communication-flow-container
4
+ // (the outer CapSlideBox wraps this component), so it cannot be scoped as a
5
+ // descendant selector — it must remain at root level to match correctly.
6
+ .slidebox-content-container {
7
+ padding: 0 $CAP_SPACE_48 !important; // 48px left and right, 0 top and bottom
8
+ .slidebox-footer {
9
+ padding: 0 $CAP_SPACE_48 !important;
10
+ }
11
+ }
12
+
13
+ .communication-flow-container {
14
+ .step-divider {
15
+ margin: $CAP_SPACE_32 0;
16
+ }
17
+
18
+ .heading-style {
19
+ margin-bottom: $CAP_SPACE_08;
20
+ }
21
+
22
+ &__footer {
23
+ padding: $CAP_SPACE_16 0 $CAP_SPACE_08;
24
+ }
25
+ }
@@ -0,0 +1,255 @@
1
+ import React from 'react';
2
+
3
+ jest.mock('../../CreativesContainer', () => function MockCreativesContainer({
4
+ getCreativesData,
5
+ handleCloseCreatives,
6
+ creativesMode,
7
+ channel,
8
+ }) {
9
+ return (
10
+ <div data-testid="creatives-mock" data-creatives-mode={creativesMode} data-creatives-channel={channel}>
11
+ <button
12
+ type="button"
13
+ data-testid="creatives-save"
14
+ onClick={() => getCreativesData({ smsBody: 'Saved' })}
15
+ >
16
+ Save creative
17
+ </button>
18
+ <button type="button" data-testid="creatives-close" onClick={handleCloseCreatives}>
19
+ Close creative
20
+ </button>
21
+ </div>
22
+ );
23
+ });
24
+
25
+ import { Provider } from 'react-redux';
26
+ import { configureStore } from '@capillarytech/vulcan-react-sdk/utils';
27
+ import {
28
+ render as rtlRender, screen, waitFor, within,
29
+ } from '@testing-library/react';
30
+ import userEvent from '@testing-library/user-event';
31
+ import '@testing-library/jest-dom';
32
+ import { IntlProvider } from 'react-intl';
33
+ import history from '../../../utils/history';
34
+ import { initialReducer } from '../../../initialReducer';
35
+ import CommunicationFlow from '../CommunicationFlow';
36
+ import { getEnabledSteps } from '../utils/getEnabledSteps';
37
+ import {
38
+ CHANNELS,
39
+ CHANNEL_PRIORITY,
40
+ SINGLE_TEMPLATE,
41
+ STEPS,
42
+ } from '../constants';
43
+
44
+ let store;
45
+
46
+ beforeAll(() => {
47
+ store = configureStore({}, initialReducer, history);
48
+ });
49
+
50
+ /** Shared option lists for feature config */
51
+ const MESSAGE_OPTIONS = [
52
+ { value: 'promotional', label: 'Promotional' },
53
+ { value: 'transactional', label: 'Transactional' },
54
+ ];
55
+
56
+ const STRATEGY_OPTIONS = [
57
+ { value: SINGLE_TEMPLATE, label: 'Single template', isMultiChannel: false },
58
+ { value: CHANNEL_PRIORITY, label: 'Channel priority', isMultiChannel: true },
59
+ ];
60
+
61
+ const DYNAMIC_CONTROLS = [
62
+ { key: 'sendToControl', label: 'Send to control', description: 'Help text' },
63
+ ];
64
+
65
+ /** Base config for all CommunicationFlow tests */
66
+ const baseConfig = {
67
+ consumer: 'campaigns',
68
+ mode: 'create',
69
+ features: {},
70
+ };
71
+
72
+ const CAP_DATA = {};
73
+
74
+ /** Reusable feature presets merged into config.features */
75
+ const FEATURES = {
76
+ messageType: {
77
+ messageTypeData: {
78
+ required: true,
79
+ options: MESSAGE_OPTIONS,
80
+ defaultOption: { value: 'promotional', label: 'Promotional' },
81
+ },
82
+ },
83
+ strategyContent: {
84
+ communicationStrategyData: { required: true, options: STRATEGY_OPTIONS, disabled: false },
85
+ contentTemplateData: { required: true, channels: CHANNELS },
86
+ },
87
+ strategyContentDynamic: {
88
+ communicationStrategyData: { required: true, options: STRATEGY_OPTIONS, disabled: false },
89
+ contentTemplateData: { required: true, channels: CHANNELS },
90
+ dynamicControlsData: { required: true, controls: DYNAMIC_CONTROLS },
91
+ },
92
+ };
93
+
94
+ function renderFlow(ui) {
95
+ return rtlRender(
96
+ <Provider store={store}>
97
+ <IntlProvider locale="en" messages={{}} defaultLocale="en">
98
+ {ui}
99
+ </IntlProvider>
100
+ </Provider>,
101
+ );
102
+ }
103
+
104
+ /**
105
+ * Renders connected CommunicationFlow with shared defaults.
106
+ * @param {object} [options]
107
+ * @param {object} [options.features] — config.features (use FEATURES.* or custom)
108
+ * @param {object|null} [options.initialData]
109
+ * @param {object} [options.capData]
110
+ * @param {jest.Mock} [options.onSave] — defaults to jest.fn()
111
+ * @param {jest.Mock} [options.onCancel] — defaults to jest.fn()
112
+ * @param {jest.Mock} [options.onChange]
113
+ */
114
+ function renderWithFlow({
115
+ features = {},
116
+ initialData = null,
117
+ capData = CAP_DATA,
118
+ onSave,
119
+ onCancel,
120
+ onChange,
121
+ ...rest
122
+ } = {}) {
123
+ return renderFlow(
124
+ <CommunicationFlow
125
+ config={{ ...baseConfig, features }}
126
+ initialData={initialData}
127
+ onSave={onSave ?? jest.fn()}
128
+ onCancel={onCancel ?? jest.fn()}
129
+ onChange={onChange}
130
+ capData={capData}
131
+ {...rest}
132
+ />,
133
+ );
134
+ }
135
+
136
+ async function selectCommunicationStrategy(label) {
137
+ const combo = screen.getByRole('combobox');
138
+ await userEvent.click(combo);
139
+ await waitFor(() => {
140
+ expect(screen.getByRole('listbox')).toBeInTheDocument();
141
+ });
142
+ await userEvent.click(within(screen.getByRole('listbox')).getByText(label));
143
+ }
144
+
145
+ describe('getEnabledSteps', () => {
146
+ it('includes delivery settings when deliverySettingsData.required is true', () => {
147
+ const steps = getEnabledSteps({
148
+ ...baseConfig,
149
+ features: {
150
+ deliverySettingsData: { required: true },
151
+ },
152
+ });
153
+ expect(steps).toContain(STEPS.DELIVERY_SETTINGS);
154
+ });
155
+
156
+ it('omits delivery settings when deliverySettingsData.required is absent or false', () => {
157
+ expect(getEnabledSteps({ ...baseConfig, features: {} })).not.toContain(
158
+ STEPS.DELIVERY_SETTINGS,
159
+ );
160
+ expect(
161
+ getEnabledSteps({
162
+ ...baseConfig,
163
+ features: { deliverySettingsData: { required: false } },
164
+ }),
165
+ ).not.toContain(STEPS.DELIVERY_SETTINGS);
166
+ });
167
+ });
168
+
169
+ describe('CommunicationFlow', () => {
170
+ it('renders only steps whose features are required', () => {
171
+ renderWithFlow({ features: FEATURES.messageType });
172
+ expect(screen.getByText(/message type/i)).toBeInTheDocument();
173
+ expect(screen.queryByText(/communication strategy/i)).not.toBeInTheDocument();
174
+ expect(screen.queryByText(/content template/i)).not.toBeInTheDocument();
175
+ });
176
+
177
+ it('hides channel selection and dynamic controls until a communication strategy is set', async () => {
178
+ renderWithFlow({ features: FEATURES.strategyContentDynamic });
179
+
180
+ expect(screen.queryByText(/content template/i)).not.toBeInTheDocument();
181
+ expect(screen.queryByText(/other controls/i)).not.toBeInTheDocument();
182
+
183
+ await selectCommunicationStrategy('Single template');
184
+
185
+ await waitFor(() => {
186
+ expect(screen.getAllByText(/content template/i).length).toBeGreaterThan(0);
187
+ });
188
+ expect(screen.getByText(/other controls/i)).toBeInTheDocument();
189
+ });
190
+
191
+ it('calls onChange with aggregated data when step data updates', async () => {
192
+ const onChange = jest.fn();
193
+ renderWithFlow({ features: FEATURES.messageType, onChange });
194
+
195
+ onChange.mockClear();
196
+ await userEvent.click(screen.getByRole('radio', { name: /transactional/i }));
197
+
198
+ expect(onChange).toHaveBeenCalled();
199
+ const payload = onChange.mock.calls[onChange.mock.calls.length - 1][0];
200
+ expect(payload.messageType).toBe('transactional');
201
+ expect(payload.communicationStrategy).toBeNull();
202
+ });
203
+
204
+ it('passes single-channel aggregate to onSave', async () => {
205
+ const onSave = jest.fn();
206
+ renderWithFlow({ features: FEATURES.strategyContent, onSave });
207
+
208
+ await selectCommunicationStrategy('Single template');
209
+
210
+ await userEvent.click(screen.getByRole('button', { name: /content template/i }));
211
+ await waitFor(() => expect(screen.getByRole('menu')).toBeInTheDocument());
212
+ await userEvent.click(within(screen.getByRole('menu')).getByText('SMS'));
213
+
214
+ await waitFor(() => expect(screen.getByTestId('creatives-mock')).toBeInTheDocument());
215
+ await userEvent.click(screen.getByRole('button', { name: /save creative/i }));
216
+
217
+ await waitFor(() => expect(screen.queryByTestId('creatives-mock')).not.toBeInTheDocument());
218
+
219
+ await userEvent.click(screen.getByRole('button', { name: /^save$/i }));
220
+
221
+ expect(onSave).toHaveBeenCalledTimes(1);
222
+ const saved = onSave.mock.calls[0][0];
223
+ expect(saved.communicationStrategy).toBe(SINGLE_TEMPLATE);
224
+ expect(saved.channel).toBe('SMS');
225
+ expect(saved.channels).toEqual([]);
226
+ expect(saved.contentItems.length).toBeGreaterThanOrEqual(1);
227
+ });
228
+
229
+ it('clears channel and keeps channels list in aggregate for multi-channel strategy', async () => {
230
+ const onSave = jest.fn();
231
+ renderWithFlow({
232
+ features: FEATURES.strategyContent,
233
+ initialData: {
234
+ communicationStrategy: CHANNEL_PRIORITY,
235
+ channels: ['SMS', 'EMAIL'],
236
+ },
237
+ onSave,
238
+ });
239
+
240
+ await userEvent.click(screen.getByRole('button', { name: /^save$/i }));
241
+
242
+ const saved = onSave.mock.calls[0][0];
243
+ expect(saved.communicationStrategy).toBe(CHANNEL_PRIORITY);
244
+ expect(saved.channel).toBeNull();
245
+ expect(saved.channels).toEqual(['SMS', 'EMAIL']);
246
+ });
247
+
248
+ it('initializes message type from initialData when provided', () => {
249
+ renderWithFlow({
250
+ features: FEATURES.messageType,
251
+ initialData: { messageType: 'transactional' },
252
+ });
253
+ expect(screen.getByRole('radio', { name: /transactional/i })).toBeChecked();
254
+ });
255
+ });