@capillarytech/creatives-library 9.0.56-betaa.0 → 9.0.57-alpha.0

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 (91) hide show
  1. package/constants/unified.js +7 -1
  2. package/package.json +1 -1
  3. package/services/api.js +25 -4
  4. package/utils/common.js +0 -4
  5. package/utils/templateVarUtils.js +16 -0
  6. package/utils/tests/templateVarUtils.test.js +44 -0
  7. package/v2Components/CapActionButton/index.js +73 -60
  8. package/v2Components/CapActionButton/index.scss +44 -28
  9. package/v2Components/CapActionButton/messages.js +7 -3
  10. package/v2Components/CapActionButton/tests/index.test.js +17 -1
  11. package/v2Components/CapWhatsappCTA/messages.js +4 -0
  12. package/v2Components/CapWhatsappCarouselButton/index.js +42 -33
  13. package/v2Components/CapWhatsappCarouselButton/index.scss +44 -2
  14. package/v2Components/CommonTestAndPreview/UnifiedPreview/index.js +13 -0
  15. package/v2Components/CommonTestAndPreview/actions.js +13 -0
  16. package/v2Components/CommonTestAndPreview/constants.js +8 -0
  17. package/v2Components/CommonTestAndPreview/index.js +55 -3
  18. package/v2Components/CommonTestAndPreview/reducer.js +20 -0
  19. package/v2Components/CommonTestAndPreview/sagas.js +28 -0
  20. package/v2Components/CommonTestAndPreview/selectors.js +6 -0
  21. package/v2Components/CommonTestAndPreview/tests/UnifiedPreview/index.test.js +106 -0
  22. package/v2Components/CommonTestAndPreview/tests/actions.test.js +15 -0
  23. package/v2Components/CommonTestAndPreview/tests/constants.test.js +9 -1
  24. package/v2Components/CommonTestAndPreview/tests/index.test.js +101 -0
  25. package/v2Components/CommonTestAndPreview/tests/reducer.test.js +41 -0
  26. package/v2Components/CommonTestAndPreview/tests/sagas.test.js +84 -0
  27. package/v2Components/CommonTestAndPreview/tests/selectors.test.js +12 -0
  28. package/v2Components/FormBuilder/Functional/FormBuilderShell.js +36 -22
  29. package/v2Components/FormBuilder/Functional/channels/mobilepush/config.js +5 -13
  30. package/v2Components/FormBuilder/Functional/channels/mobilepush/modals.js +6 -4
  31. package/v2Components/FormBuilder/Functional/channels/mobilepush/validate.js +13 -14
  32. package/v2Components/FormBuilder/Functional/renderers/mpushRenderers.js +15 -8
  33. package/v2Components/FormBuilder/Functional/tests/mpush.ctaFlows.parity.test.js +31 -0
  34. package/v2Components/FormBuilder/Functional/tests/mpush.shellEvents.test.js +13 -0
  35. package/v2Components/FormBuilder/Functional/tests/mpushRenderers.test.js +26 -0
  36. package/v2Components/FormBuilder/_formBuilder.scss +8 -0
  37. package/v2Components/TestAndPreviewSlidebox/index.js +9 -0
  38. package/v2Containers/CommunicationFlow/CommunicationFlow.js +140 -75
  39. package/v2Containers/CommunicationFlow/CommunicationFlow.scss +18 -2
  40. package/v2Containers/CommunicationFlow/CommunicationFlowCard.js +67 -28
  41. package/v2Containers/CommunicationFlow/Tests/CommunicationFlow.test.js +503 -51
  42. package/v2Containers/CommunicationFlow/Tests/CommunicationFlowCard.test.js +98 -4
  43. package/v2Containers/CommunicationFlow/Tests/constants.test.js +85 -0
  44. package/v2Containers/CommunicationFlow/constants.js +65 -4
  45. package/v2Containers/CommunicationFlow/index.js +16 -23
  46. package/v2Containers/CommunicationFlow/messages.js +11 -3
  47. package/v2Containers/CommunicationFlow/steps/ChannelSelectionStep/ChannelSelectionStep.js +27 -11
  48. package/v2Containers/CommunicationFlow/steps/ChannelSelectionStep/ChannelSelectionStep.scss +4 -0
  49. package/v2Containers/CommunicationFlow/steps/ChannelSelectionStep/Tests/ChannelSelectionStep.test.js +176 -4
  50. package/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/DeliverySettingsSection.js +22 -4
  51. package/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/SenderDetails.js +14 -4
  52. package/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/Tests/DeliverySettingsSection.test.js +11 -0
  53. package/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/Tests/SenderDetails.test.js +51 -0
  54. package/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/Tests/deliverySettingsConfig.test.js +11 -4
  55. package/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/deliverySettingsConfig.js +19 -5
  56. package/v2Containers/CreativesContainer/SlideBoxContent.js +15 -0
  57. package/v2Containers/CreativesContainer/SlideBoxFooter.js +6 -2
  58. package/v2Containers/CreativesContainer/index.js +29 -3
  59. package/v2Containers/CreativesContainer/tests/SlideBoxFooter.test.js +29 -0
  60. package/v2Containers/CreativesContainer/tests/__snapshots__/index.test.js.snap +18 -5
  61. package/v2Containers/CreativesContainer/tests/index.test.js +12 -0
  62. package/v2Containers/MobilePush/Create/index.js +18 -1
  63. package/v2Containers/MobilePush/Create/test/contentValidity.test.js +96 -0
  64. package/v2Containers/MobilePush/Edit/index.js +18 -1
  65. package/v2Containers/MobilePush/Edit/test/contentValidity.test.js +116 -0
  66. package/v2Containers/MobilePush/commonMethods.js +49 -1
  67. package/v2Containers/MobilePushNew/index.js +29 -4
  68. package/v2Containers/MobilePushNew/tests/index.test.js +119 -0
  69. package/v2Containers/MobilePushNew/tests/utils.test.js +82 -0
  70. package/v2Containers/MobilePushNew/utils.js +34 -1
  71. package/v2Containers/MobilepushWrapper/index.js +3 -1
  72. package/v2Containers/Rcs/index.js +34 -0
  73. package/v2Containers/Rcs/index.scss +15 -0
  74. package/v2Containers/Rcs/tests/__snapshots__/index.test.js.snap +115 -19
  75. package/v2Containers/Rcs/tests/index.test.js +67 -0
  76. package/v2Containers/Sms/Create/index.js +3 -1
  77. package/v2Containers/Sms/Edit/index.js +25 -0
  78. package/v2Containers/Sms/Edit/tests/index.test.js +85 -0
  79. package/v2Containers/SmsTrai/Edit/tests/__snapshots__/index.test.js.snap +27 -3
  80. package/v2Containers/Viber/index.js +24 -1
  81. package/v2Containers/Viber/tests/index.test.js +103 -0
  82. package/v2Containers/WebPush/Create/index.js +19 -0
  83. package/v2Containers/WebPush/Create/preview/WebPushPreview.js +17 -12
  84. package/v2Containers/WebPush/Create/preview/tests/WebPushPreview.test.js +5 -0
  85. package/v2Containers/WebPush/Create/tests/contentValidity.test.js +294 -0
  86. package/v2Containers/Whatsapp/index.js +25 -4
  87. package/v2Containers/Whatsapp/tests/__snapshots__/index.test.js.snap +306 -34
  88. package/v2Containers/Whatsapp/tests/index.test.js +115 -0
  89. package/v2Containers/Zalo/index.js +28 -1
  90. package/v2Containers/Zalo/tests/index.test.js +99 -0
  91. package/v2Containers/mockdata.js +25 -0
@@ -37,7 +37,49 @@
37
37
  height: auto;
38
38
  }
39
39
 
40
- .whatsapp-url-preview-wrapper {
41
- max-width: 5.714rem;
40
+ .whatsapp-saved-cta-cell {
41
+ display: flex;
42
+ align-items: center;
43
+ min-width: 0;
44
+
45
+ > * {
46
+ min-width: 0;
47
+ }
48
+ }
49
+
50
+ .whatsapp-saved-cta-text-cell {
51
+ flex: 0 1 10rem;
52
+ }
53
+
54
+ .whatsapp-saved-cta-type-cell {
55
+ flex: 0 1 5.5rem;
56
+ }
57
+
58
+ .whatsapp-saved-cta-url-cell {
59
+ flex: 1 1 0;
60
+ }
61
+
62
+ .whatsapp-saved-cta-actions-cell {
63
+ flex: 0 0 4rem;
64
+ justify-content: flex-end;
65
+ }
66
+
67
+ .whatsapp-saved-cta-action-icons {
68
+ display: flex;
69
+ align-items: center;
70
+ flex-shrink: 0;
71
+ gap: $CAP_SPACE_12;
72
+ }
73
+
74
+ // CapTooltip nests `.url` two levels down, so these — not `.url` — are the
75
+ // boxes that must be allowed to shrink for the ellipsis to apply.
76
+ .whatsapp-cta-url-tooltip-wrapper {
77
+ flex: 1 1 0;
78
+ min-width: 0;
79
+ }
80
+
81
+ .whatsapp-cta-url-tooltip-trigger {
82
+ width: 100%;
83
+ min-width: 0;
42
84
  }
43
85
  }
@@ -139,6 +139,19 @@ const UnifiedPreview = ({
139
139
  );
140
140
  }
141
141
 
142
+ case CHANNELS.LINE:
143
+ // LINE currently supports plain text only, so reuse the SMS bubble preview; rich message types are not rendered yet.
144
+ return (
145
+ <SmsPreviewContent
146
+ content={typeof content === 'string' ? content : (content?.resolvedBody || '')}
147
+ device={device}
148
+ isUpdating={isUpdating}
149
+ error={error}
150
+ formatMessage={formatMessage}
151
+ showHeader={showHeader}
152
+ />
153
+ );
154
+
142
155
  case CHANNELS.WHATSAPP: {
143
156
  // WhatsApp content is an object with templateMsg, media, CTA, etc.
144
157
  const whatsappContent = typeof content === 'object' ? content : {};
@@ -10,6 +10,7 @@ import {
10
10
  EXTRACT_TAGS_REQUESTED,
11
11
  UPDATE_PREVIEW_REQUESTED,
12
12
  SEND_TEST_MESSAGE_REQUESTED,
13
+ SEND_CCS_TEST_MESSAGE_REQUESTED,
13
14
  CLEAR_CUSTOMER_SEARCH_STATE,
14
15
  GET_TEST_CUSTOMERS_REQUESTED,
15
16
  GET_TEST_GROUPS_REQUESTED,
@@ -65,6 +66,18 @@ export const sendTestMessageRequested = (payload, callback) => ({
65
66
  callback,
66
67
  });
67
68
 
69
+ /**
70
+ * notify/ui only — send a test message through CCS's send/transaction endpoint
71
+ * against a real CommDefinition, instead of the legacy createMessageMeta/sendTestMessage flow.
72
+ * @param {Object} payload - { commDefinitionId|referenceId, uniqueKey, isTest, recipient }
73
+ * @param {Function} callback - Success/error callback
74
+ */
75
+ export const sendCcsTestMessageRequested = (payload, callback) => ({
76
+ type: SEND_CCS_TEST_MESSAGE_REQUESTED,
77
+ payload,
78
+ callback,
79
+ });
80
+
68
81
  /**
69
82
  * Clear all customer search state
70
83
  */
@@ -29,6 +29,11 @@ export const SEND_TEST_MESSAGE_REQUESTED = 'app/CommonTestAndPreview/SEND_TEST_M
29
29
  export const SEND_TEST_MESSAGE_SUCCESS = 'app/CommonTestAndPreview/SEND_TEST_MESSAGE_SUCCESS';
30
30
  export const SEND_TEST_MESSAGE_FAILURE = 'app/CommonTestAndPreview/SEND_TEST_MESSAGE_FAILURE';
31
31
 
32
+ // Send CCS Test Message (notify/ui — send/transaction against a real CommDefinition)
33
+ export const SEND_CCS_TEST_MESSAGE_REQUESTED = 'app/CommonTestAndPreview/SEND_CCS_TEST_MESSAGE_REQUESTED';
34
+ export const SEND_CCS_TEST_MESSAGE_SUCCESS = 'app/CommonTestAndPreview/SEND_CCS_TEST_MESSAGE_SUCCESS';
35
+ export const SEND_CCS_TEST_MESSAGE_FAILURE = 'app/CommonTestAndPreview/SEND_CCS_TEST_MESSAGE_FAILURE';
36
+
32
37
  // Test Customers
33
38
  export const GET_TEST_CUSTOMERS_REQUESTED = 'app/CommonTestAndPreview/GET_TEST_CUSTOMERS_REQUESTED';
34
39
  export const GET_TEST_CUSTOMERS_SUCCESS = 'app/CommonTestAndPreview/GET_TEST_CUSTOMERS_SUCCESS';
@@ -85,6 +90,7 @@ export const CHANNELS = {
85
90
  VIBER: 'VIBER',
86
91
  ZALO: 'ZALO',
87
92
  WEBPUSH: 'WEBPUSH',
93
+ LINE: 'LINE',
88
94
  };
89
95
 
90
96
  /** Unified preview tab keys when RCS shows RCS + SMS fallback panes. */
@@ -100,6 +106,7 @@ export const CHANNELS_USING_ANDROID_PREVIEW_DEVICE = [
100
106
  CHANNELS.INAPP,
101
107
  CHANNELS.MOBILEPUSH,
102
108
  CHANNELS.VIBER,
109
+ CHANNELS.LINE,
103
110
  ];
104
111
 
105
112
  /** RCS createMessageMeta test payload — rich card envelope literals. */
@@ -290,6 +297,7 @@ export const ERROR_MESSAGES = {
290
297
  FAILED_TO_EXTRACT_TAGS: 'Failed to extract tags',
291
298
  FAILED_TO_UPDATE_PREVIEW: 'Failed to update preview',
292
299
  FAILED_TO_SEND_TEST_EMAIL: 'Failed to send test email',
300
+ FAILED_TO_SEND_CCS_TEST_MESSAGE: 'Failed to send test message',
293
301
  PROFILE_IS_EMPTY: 'Profile is empty',
294
302
  NO_TEST_CUSTOMERS_FOUND: 'No test customers found',
295
303
  FAILED_TO_FETCH_TEST_CUSTOMERS: 'Failed to fetch test customers',
@@ -200,6 +200,7 @@ const CommonTestAndPreview = (props) => {
200
200
  messageMetaConfigId,
201
201
  prefilledValues,
202
202
  isSendingTestMessage,
203
+ isSendingCcsTestMessage,
203
204
  updatePreviewError,
204
205
  updatePreviewErrors,
205
206
  fetchPrefilledValuesError,
@@ -208,6 +209,8 @@ const CommonTestAndPreview = (props) => {
208
209
  wecrmAccounts = [],
209
210
  isLoadingSenderDetails = false,
210
211
  orgUnitId = -1,
212
+ // notify/ui only:{commDefinitionId, referenceId} routes "Send test" to CCS's send/transaction endpoint.
213
+ ccsSendTransaction,
211
214
  // Email-specific props
212
215
  beeInstance,
213
216
  currentTab = 1,
@@ -3621,11 +3624,53 @@ const CommonTestAndPreview = (props) => {
3621
3624
  setIsCustomerDataLoading(false);
3622
3625
  }
3623
3626
  };
3624
-
3627
+
3625
3628
  /**
3626
3629
  * Handle send test message
3627
3630
  */
3631
+ // notify/ui sends tests via CCS's CommDefinition send/transaction endpoint using the standard action/saga flow, not the legacy messageMeta flow.
3632
+ const handleCcsSendTestMessage = () => {
3633
+ const allUserIds = [];
3634
+ selectedTestEntities.forEach((entityId) => {
3635
+ const group = testGroups.find((testGroup) => testEntityIdsEqual(testGroup.groupId, entityId));
3636
+ if (group) {
3637
+ allUserIds.push(...group.userIds);
3638
+ } else {
3639
+ allUserIds.push(entityId);
3640
+ }
3641
+ });
3642
+ const uniqueUserIds = [...new Set(allUserIds)];
3643
+ const { commDefinitionId, referenceId } = ccsSendTransaction;
3644
+
3645
+ actions.sendCcsTestMessageRequested(
3646
+ {
3647
+ ...(commDefinitionId ? { commDefinitionId } : { referenceId }),
3648
+ uniqueKey: `test_${Date.now()}`,
3649
+ isTest: true,
3650
+ recipient: {
3651
+ identifiers: uniqueUserIds.map((userId) => ({ type: 'USER_ID', value: String(userId) })),
3652
+ tagValues: customValues,
3653
+ },
3654
+ },
3655
+ (success) => {
3656
+ if (success) {
3657
+ CapNotification.success({
3658
+ message: formatMessage(messages.testMessageSent),
3659
+ });
3660
+ } else {
3661
+ CapNotification.error({
3662
+ message: formatMessage(messages.testMessageFailed),
3663
+ });
3664
+ }
3665
+ },
3666
+ );
3667
+ };
3668
+
3628
3669
  const handleSendTestMessage = () => {
3670
+ if (ccsSendTransaction) {
3671
+ handleCcsSendTestMessage();
3672
+ return;
3673
+ }
3629
3674
  const allUserIds = [];
3630
3675
  selectedTestEntities.forEach((entityId) => {
3631
3676
  const group = testGroups.find((testGroup) => testEntityIdsEqual(testGroup.groupId, entityId));
@@ -3763,7 +3808,7 @@ const CommonTestAndPreview = (props) => {
3763
3808
  formData={formDataForSendTest}
3764
3809
  content={getCurrentContent}
3765
3810
  channel={channel}
3766
- isSendingTestMessage={isSendingTestMessage}
3811
+ isSendingTestMessage={isSendingTestMessage || isSendingCcsTestMessage}
3767
3812
  renderAddTestCustomerButton={renderAddTestCustomerButton}
3768
3813
  formatMessage={formatMessage}
3769
3814
  deliverySettings={testPreviewDeliverySettings[channel]}
@@ -3816,7 +3861,7 @@ const CommonTestAndPreview = (props) => {
3816
3861
  header={slideboxHeader}
3817
3862
  handleClose={handleClose}
3818
3863
  show={show}
3819
- size="size-xl"
3864
+ size="size-l"
3820
3865
  content={(
3821
3866
  <CapSpin
3822
3867
  spinning={isCustomerDataLoading}
@@ -3913,11 +3958,16 @@ CommonTestAndPreview.propTypes = {
3913
3958
  message: PropTypes.string,
3914
3959
  })),
3915
3960
  isSendingTestMessage: PropTypes.bool.isRequired,
3961
+ isSendingCcsTestMessage: PropTypes.bool,
3916
3962
  intl: PropTypes.object.isRequired,
3917
3963
  senderDetailsByChannel: PropTypes.object,
3918
3964
  wecrmAccounts: PropTypes.array,
3919
3965
  isLoadingSenderDetails: PropTypes.bool,
3920
3966
  orgUnitId: PropTypes.number,
3967
+ ccsSendTransaction: PropTypes.shape({
3968
+ commDefinitionId: PropTypes.string,
3969
+ referenceId: PropTypes.string,
3970
+ }),
3921
3971
 
3922
3972
  // Email-specific props
3923
3973
  beeInstance: PropTypes.object,
@@ -3957,6 +4007,8 @@ CommonTestAndPreview.defaultProps = {
3957
4007
  wecrmAccounts: [],
3958
4008
  isLoadingSenderDetails: false,
3959
4009
  orgUnitId: -1,
4010
+ ccsSendTransaction: null,
4011
+ isSendingCcsTestMessage: false,
3960
4012
  };
3961
4013
 
3962
4014
  // ============================================
@@ -15,6 +15,9 @@ import {
15
15
  SEND_TEST_MESSAGE_REQUESTED,
16
16
  SEND_TEST_MESSAGE_SUCCESS,
17
17
  SEND_TEST_MESSAGE_FAILURE,
18
+ SEND_CCS_TEST_MESSAGE_REQUESTED,
19
+ SEND_CCS_TEST_MESSAGE_SUCCESS,
20
+ SEND_CCS_TEST_MESSAGE_FAILURE,
18
21
  CLEAR_CUSTOMER_SEARCH_STATE,
19
22
  CLEAR_SEARCH_RESULTS,
20
23
  GET_TEST_CUSTOMERS_REQUESTED,
@@ -69,6 +72,10 @@ const initialState = fromJS({
69
72
  sendTestMessageError: null,
70
73
  testMessageResponse: null,
71
74
 
75
+ // CCS test message state (notify/ui)
76
+ isSendingCcsTestMessage: false,
77
+ sendCcsTestMessageError: null,
78
+
72
79
  tags: {
73
80
  loading: false,
74
81
  error: null,
@@ -194,6 +201,19 @@ const previewAndTestReducer = (state = initialState, action) => {
194
201
  .set('isSendingTestMessage', false)
195
202
  .set('sendTestMessageError', action.payload.error);
196
203
 
204
+ // Send CCS Test Message (notify/ui)
205
+ case SEND_CCS_TEST_MESSAGE_REQUESTED:
206
+ return state.set('isSendingCcsTestMessage', true)
207
+ .set('sendCcsTestMessageError', null);
208
+
209
+ case SEND_CCS_TEST_MESSAGE_SUCCESS:
210
+ return state.set('isSendingCcsTestMessage', false)
211
+ .set('sendCcsTestMessageError', null);
212
+
213
+ case SEND_CCS_TEST_MESSAGE_FAILURE:
214
+ return state.set('isSendingCcsTestMessage', false)
215
+ .set('sendCcsTestMessageError', action.payload.error);
216
+
197
217
  // Clear Actions
198
218
  case CLEAR_SEARCH_RESULTS:
199
219
  return state.set('customers', [])
@@ -21,6 +21,9 @@ import {
21
21
  SEND_TEST_MESSAGE_REQUESTED,
22
22
  SEND_TEST_MESSAGE_SUCCESS,
23
23
  SEND_TEST_MESSAGE_FAILURE,
24
+ SEND_CCS_TEST_MESSAGE_REQUESTED,
25
+ SEND_CCS_TEST_MESSAGE_SUCCESS,
26
+ SEND_CCS_TEST_MESSAGE_FAILURE,
24
27
  GET_TEST_CUSTOMERS_REQUESTED,
25
28
  GET_TEST_GROUPS_REQUESTED,
26
29
  GET_TEST_CUSTOMERS_SUCCESS,
@@ -144,6 +147,26 @@ export function* sendTestMessageSaga(action) {
144
147
  }
145
148
  }
146
149
 
150
+ // notify/ui: Send Test Message via CCS's send/transaction endpoint, against a real
151
+ // CommDefinition, instead of the legacy createMessageMeta/sendTestMessage flow above.
152
+ export function* sendCcsTestMessageSaga(action) {
153
+ try {
154
+ const { callback } = action;
155
+ const res = yield call(Api.sendTransactionalComm, action.payload);
156
+ if (res?.response?.success === false) {
157
+ const error = res?.response?.errors?.[0]?.message || ERROR_MESSAGES.FAILED_TO_SEND_CCS_TEST_MESSAGE;
158
+ yield put({ type: SEND_CCS_TEST_MESSAGE_FAILURE, payload: { error } });
159
+ callback(false);
160
+ } else {
161
+ yield put({ type: SEND_CCS_TEST_MESSAGE_SUCCESS });
162
+ callback(true);
163
+ }
164
+ } catch (error) {
165
+ yield put({ type: SEND_CCS_TEST_MESSAGE_FAILURE, payload: { error: error.message || ERROR_MESSAGES.NETWORK_ERROR } });
166
+ action.callback(false);
167
+ }
168
+ }
169
+
147
170
  export function* getBulkCustomerDetails({fetchedUserIds}) {
148
171
  try {
149
172
  const bulkCustomerDetails = yield call(Api.getBulkCustomerDetails, {
@@ -285,6 +308,10 @@ export function* watchSendTestMessage() {
285
308
  yield takeLatest(SEND_TEST_MESSAGE_REQUESTED, sendTestMessageSaga);
286
309
  }
287
310
 
311
+ export function* watchSendCcsTestMessage() {
312
+ yield takeLatest(SEND_CCS_TEST_MESSAGE_REQUESTED, sendCcsTestMessageSaga);
313
+ }
314
+
288
315
  export function* watchFetchTestCustomers() {
289
316
  yield takeLatest(GET_TEST_CUSTOMERS_REQUESTED, fetchTestCustomersSaga);
290
317
  }
@@ -371,6 +398,7 @@ export function* commonTestAndPreviewSaga() {
371
398
  watchExtractTags(),
372
399
  watchUpdatePreview(),
373
400
  watchSendTestMessage(),
401
+ watchSendCcsTestMessage(),
374
402
  watchFetchTestCustomers(),
375
403
  watchFetchTestGroups(),
376
404
  watchCreateMessageMeta(),
@@ -124,6 +124,11 @@ const makeSelectIsSendingTestMessage = () => createSelector(
124
124
  (substate) => substate.get('isSendingTestMessage'),
125
125
  );
126
126
 
127
+ const makeSelectIsSendingCcsTestMessage = () => createSelector(
128
+ selectCommonTestAndPreviewDomain,
129
+ (substate) => substate.get('isSendingCcsTestMessage'),
130
+ );
131
+
127
132
  const makeSelectUpdatePreviewError = () => createSelector(
128
133
  selectCommonTestAndPreviewDomain,
129
134
  (substate) => substate.get('updatePreviewError'),
@@ -212,6 +217,7 @@ export {
212
217
  makeSelectPrefilledValues,
213
218
  makeSelectTestMessageResponse,
214
219
  makeSelectIsSendingTestMessage,
220
+ makeSelectIsSendingCcsTestMessage,
215
221
  makeSelectUpdatePreviewError,
216
222
  makeSelectUpdatePreviewErrors,
217
223
  makeSelectFetchPrefilledValuesError,
@@ -479,6 +479,112 @@ describe('UnifiedPreview', () => {
479
479
  });
480
480
  });
481
481
 
482
+ describe('Channel Routing - LINE', () => {
483
+ it('should render SmsPreviewContent for LINE channel with string content', () => {
484
+ const props = {
485
+ ...defaultProps,
486
+ channel: CHANNELS.LINE,
487
+ content: 'Hello world',
488
+ };
489
+
490
+ render(
491
+ <TestWrapper>
492
+ <ComponentToRender {...props} />
493
+ </TestWrapper>
494
+ );
495
+
496
+ expect(screen.getByTestId('sms-preview')).toBeTruthy();
497
+ expect(screen.getByTestId('sms-content')).toHaveTextContent('Hello world');
498
+ });
499
+
500
+ it('should handle object content with resolvedBody for LINE channel', () => {
501
+ const props = {
502
+ ...defaultProps,
503
+ channel: CHANNELS.LINE,
504
+ content: {
505
+ resolvedBody: 'some text',
506
+ },
507
+ };
508
+
509
+ render(
510
+ <TestWrapper>
511
+ <ComponentToRender {...props} />
512
+ </TestWrapper>
513
+ );
514
+
515
+ expect(screen.getByTestId('sms-preview')).toBeTruthy();
516
+ expect(screen.getByTestId('sms-content')).toHaveTextContent('some text');
517
+ });
518
+
519
+ it('should not crash and render empty content for LINE channel when content is undefined', () => {
520
+ const props = {
521
+ ...defaultProps,
522
+ channel: CHANNELS.LINE,
523
+ content: undefined,
524
+ };
525
+
526
+ render(
527
+ <TestWrapper>
528
+ <ComponentToRender {...props} />
529
+ </TestWrapper>
530
+ );
531
+
532
+ expect(screen.getByTestId('sms-preview')).toBeTruthy();
533
+ expect(screen.getByTestId('sms-content')).toHaveTextContent('');
534
+ });
535
+
536
+ it('should not crash and render empty content for LINE channel when content is null', () => {
537
+ const props = {
538
+ ...defaultProps,
539
+ channel: CHANNELS.LINE,
540
+ content: null,
541
+ };
542
+
543
+ render(
544
+ <TestWrapper>
545
+ <ComponentToRender {...props} />
546
+ </TestWrapper>
547
+ );
548
+
549
+ expect(screen.getByTestId('sms-preview')).toBeTruthy();
550
+ expect(screen.getByTestId('sms-content')).toHaveTextContent('');
551
+ });
552
+
553
+ it('should render empty content for LINE channel when content is not a string and has no resolvedBody', () => {
554
+ const props = {
555
+ ...defaultProps,
556
+ channel: CHANNELS.LINE,
557
+ content: { someOtherField: 'value' },
558
+ };
559
+
560
+ render(
561
+ <TestWrapper>
562
+ <ComponentToRender {...props} />
563
+ </TestWrapper>
564
+ );
565
+
566
+ expect(screen.getByTestId('sms-preview')).toBeTruthy();
567
+ expect(screen.getByTestId('sms-content')).toHaveTextContent('');
568
+ });
569
+
570
+ it('should not render the Unknown Channel fallback for LINE channel', () => {
571
+ const props = {
572
+ ...defaultProps,
573
+ channel: CHANNELS.LINE,
574
+ content: 'Hello world',
575
+ };
576
+
577
+ render(
578
+ <TestWrapper>
579
+ <ComponentToRender {...props} />
580
+ </TestWrapper>
581
+ );
582
+
583
+ expect(screen.queryByText('Unknown Channel')).toBeNull();
584
+ expect(screen.getByTestId('sms-preview')).toBeTruthy();
585
+ });
586
+ });
587
+
482
588
  describe('Channel Routing - WHATSAPP', () => {
483
589
  it('should render WhatsAppPreviewContent for WHATSAPP channel', () => {
484
590
  const props = {
@@ -9,6 +9,7 @@ import {
9
9
  extractTagsRequested,
10
10
  updatePreviewRequested,
11
11
  sendTestMessageRequested,
12
+ sendCcsTestMessageRequested,
12
13
  clearCustomerSearchState,
13
14
  clearSearchResults,
14
15
  getTestCustomersRequested,
@@ -26,6 +27,7 @@ import {
26
27
  EXTRACT_TAGS_REQUESTED,
27
28
  UPDATE_PREVIEW_REQUESTED,
28
29
  SEND_TEST_MESSAGE_REQUESTED,
30
+ SEND_CCS_TEST_MESSAGE_REQUESTED,
29
31
  CLEAR_CUSTOMER_SEARCH_STATE,
30
32
  CLEAR_SEARCH_RESULTS,
31
33
  GET_TEST_CUSTOMERS_REQUESTED,
@@ -205,6 +207,19 @@ describe('CommonTestAndPreview Actions', () => {
205
207
  });
206
208
  });
207
209
 
210
+ describe('sendCcsTestMessageRequested', () => {
211
+ it('should create an action to send a CCS test message with payload and callback', () => {
212
+ const payload = { commDefinitionId: 'cd_123', recipient: { identifiers: [] } };
213
+ const callback = jest.fn();
214
+ const expectedAction = {
215
+ type: SEND_CCS_TEST_MESSAGE_REQUESTED,
216
+ payload,
217
+ callback,
218
+ };
219
+ expect(sendCcsTestMessageRequested(payload, callback)).toEqual(expectedAction);
220
+ });
221
+ });
222
+
208
223
  describe('clearCustomerSearchState', () => {
209
224
  it('should create an action to clear customer search state', () => {
210
225
  const expectedAction = {
@@ -42,6 +42,7 @@ import {
42
42
  CLEAR_PREVIEW_ERRORS,
43
43
  // Channel Constants
44
44
  CHANNELS,
45
+ CHANNELS_USING_ANDROID_PREVIEW_DEVICE,
45
46
  // Device Constants
46
47
  DESKTOP,
47
48
  MOBILE,
@@ -180,15 +181,22 @@ describe('CommonTestAndPreview Constants', () => {
180
181
  expect(CHANNELS.VIBER).toBe('VIBER');
181
182
  expect(CHANNELS.ZALO).toBe('ZALO');
182
183
  expect(CHANNELS.WEBPUSH).toBe('WEBPUSH');
184
+ expect(CHANNELS.LINE).toBe('LINE');
183
185
  });
184
186
 
185
187
  it('should have all required channel keys', () => {
186
- const expectedChannels = ['EMAIL', 'SMS', 'RCS', 'WHATSAPP', 'INAPP', 'MOBILEPUSH', 'VIBER', 'ZALO', 'WEBPUSH'];
188
+ const expectedChannels = ['EMAIL', 'SMS', 'RCS', 'WHATSAPP', 'INAPP', 'MOBILEPUSH', 'VIBER', 'ZALO', 'WEBPUSH', 'LINE'];
187
189
  expect(Object.keys(CHANNELS)).toEqual(expect.arrayContaining(expectedChannels));
188
190
  expect(Object.keys(CHANNELS).length).toBe(expectedChannels.length);
189
191
  });
190
192
  });
191
193
 
194
+ describe('CHANNELS_USING_ANDROID_PREVIEW_DEVICE', () => {
195
+ it('should include CHANNELS.LINE', () => {
196
+ expect(CHANNELS_USING_ANDROID_PREVIEW_DEVICE).toContain(CHANNELS.LINE);
197
+ });
198
+ });
199
+
192
200
  describe('Device Constants', () => {
193
201
  it('should export all device constants with correct values', () => {
194
202
  expect(DESKTOP).toBe('desktop');
@@ -128,6 +128,7 @@ describe('CommonTestAndPreview', () => {
128
128
  extractTagsRequested: jest.fn(),
129
129
  updatePreviewRequested: jest.fn(),
130
130
  sendTestMessageRequested: jest.fn(),
131
+ sendCcsTestMessageRequested: jest.fn(),
131
132
  clearCustomerSearchState: jest.fn(),
132
133
  getTestCustomersRequested: jest.fn(),
133
134
  getTestGroupsRequested: jest.fn(),
@@ -1686,6 +1687,106 @@ describe('CommonTestAndPreview', () => {
1686
1687
  });
1687
1688
  });
1688
1689
 
1690
+ describe('CCS Test Message Sending (notify/ui)', () => {
1691
+ it('dispatches sendCcsTestMessageRequested (not the legacy createMessageMeta/sendTestMessage flow) when ccsSendTransaction is set', async () => {
1692
+ const props = {
1693
+ ...defaultProps,
1694
+ selectedTestEntities: ['user-1'],
1695
+ testGroups: [],
1696
+ ccsSendTransaction: { commDefinitionId: 'cd_123', referenceId: 'ORDER_PLACED' },
1697
+ };
1698
+
1699
+ render(
1700
+ <TestWrapper>
1701
+ <CommonTestAndPreview {...props} />
1702
+ </TestWrapper>
1703
+ );
1704
+
1705
+ lastSendTestMessageProps.handleSendTestMessage();
1706
+
1707
+ expect(mockActions.sendCcsTestMessageRequested).toHaveBeenCalledWith(
1708
+ expect.objectContaining({
1709
+ commDefinitionId: 'cd_123',
1710
+ isTest: true,
1711
+ recipient: expect.objectContaining({
1712
+ identifiers: expect.any(Array),
1713
+ }),
1714
+ }),
1715
+ expect.any(Function),
1716
+ );
1717
+ expect(mockActions.createMessageMetaRequested).not.toHaveBeenCalled();
1718
+ expect(mockActions.sendTestMessageRequested).not.toHaveBeenCalled();
1719
+ });
1720
+
1721
+ it('falls back to referenceId when commDefinitionId is absent', async () => {
1722
+ const props = {
1723
+ ...defaultProps,
1724
+ selectedTestEntities: ['user-1'],
1725
+ testGroups: [],
1726
+ ccsSendTransaction: { referenceId: 'ORDER_PLACED' },
1727
+ };
1728
+
1729
+ render(
1730
+ <TestWrapper>
1731
+ <CommonTestAndPreview {...props} />
1732
+ </TestWrapper>
1733
+ );
1734
+
1735
+ lastSendTestMessageProps.handleSendTestMessage();
1736
+
1737
+ expect(mockActions.sendCcsTestMessageRequested).toHaveBeenCalledWith(
1738
+ expect.objectContaining({ referenceId: 'ORDER_PLACED' }),
1739
+ expect.any(Function),
1740
+ );
1741
+ });
1742
+
1743
+ it('shows a success notification when the callback receives true', async () => {
1744
+ const CapNotification = require('@capillarytech/cap-ui-library/CapNotification');
1745
+ mockActions.sendCcsTestMessageRequested.mockImplementation((payload, cb) => cb(true));
1746
+ const props = {
1747
+ ...defaultProps,
1748
+ selectedTestEntities: ['user-1'],
1749
+ testGroups: [],
1750
+ ccsSendTransaction: { commDefinitionId: 'cd_123' },
1751
+ };
1752
+
1753
+ render(
1754
+ <TestWrapper>
1755
+ <CommonTestAndPreview {...props} />
1756
+ </TestWrapper>
1757
+ );
1758
+
1759
+ lastSendTestMessageProps.handleSendTestMessage();
1760
+
1761
+ await waitFor(() => {
1762
+ expect(CapNotification.success).toHaveBeenCalled();
1763
+ });
1764
+ });
1765
+
1766
+ it('shows an error notification when the callback receives false', async () => {
1767
+ const CapNotification = require('@capillarytech/cap-ui-library/CapNotification');
1768
+ mockActions.sendCcsTestMessageRequested.mockImplementation((payload, cb) => cb(false));
1769
+ const props = {
1770
+ ...defaultProps,
1771
+ selectedTestEntities: ['user-1'],
1772
+ testGroups: [],
1773
+ ccsSendTransaction: { commDefinitionId: 'cd_123' },
1774
+ };
1775
+
1776
+ render(
1777
+ <TestWrapper>
1778
+ <CommonTestAndPreview {...props} />
1779
+ </TestWrapper>
1780
+ );
1781
+
1782
+ lastSendTestMessageProps.handleSendTestMessage();
1783
+
1784
+ await waitFor(() => {
1785
+ expect(CapNotification.error).toHaveBeenCalled();
1786
+ });
1787
+ });
1788
+ });
1789
+
1689
1790
  describe('Content Extraction', () => {
1690
1791
  it('should extract EMAIL content from formData', async () => {
1691
1792
  const props = {