@capillarytech/creatives-library 9.0.56-alpha.3 → 9.0.56-alpha.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (29) hide show
  1. package/package.json +1 -1
  2. package/services/api.js +8 -6
  3. package/v2Components/CommonTestAndPreview/UnifiedPreview/index.js +2 -1
  4. package/v2Components/CommonTestAndPreview/actions.js +13 -0
  5. package/v2Components/CommonTestAndPreview/constants.js +6 -0
  6. package/v2Components/CommonTestAndPreview/index.js +23 -21
  7. package/v2Components/CommonTestAndPreview/reducer.js +20 -0
  8. package/v2Components/CommonTestAndPreview/sagas.js +28 -0
  9. package/v2Components/CommonTestAndPreview/selectors.js +6 -0
  10. package/v2Components/CommonTestAndPreview/tests/actions.test.js +15 -0
  11. package/v2Components/CommonTestAndPreview/tests/index.test.js +101 -0
  12. package/v2Components/CommonTestAndPreview/tests/reducer.test.js +41 -0
  13. package/v2Components/CommonTestAndPreview/tests/sagas.test.js +84 -0
  14. package/v2Components/CommonTestAndPreview/tests/selectors.test.js +12 -0
  15. package/v2Components/TestAndPreviewSlidebox/index.js +3 -0
  16. package/v2Containers/CommunicationFlow/CommunicationFlow.js +40 -37
  17. package/v2Containers/CommunicationFlow/CommunicationFlow.scss +29 -1
  18. package/v2Containers/CommunicationFlow/CommunicationFlowCard.js +2 -4
  19. package/v2Containers/CommunicationFlow/Tests/CommunicationFlow.test.js +16 -3
  20. package/v2Containers/CommunicationFlow/constants.js +12 -8
  21. package/v2Containers/CommunicationFlow/index.js +6 -8
  22. package/v2Containers/CommunicationFlow/steps/ChannelSelectionStep/ChannelSelectionStep.js +2 -22
  23. package/v2Containers/CommunicationFlow/steps/ChannelSelectionStep/ChannelSelectionStep.scss +4 -0
  24. package/v2Containers/CreativesContainer/index.js +0 -1
  25. package/v2Containers/Rcs/tests/__snapshots__/index.test.js.snap +79 -7
  26. package/v2Containers/SmsTrai/Edit/tests/__snapshots__/index.test.js.snap +18 -0
  27. package/v2Containers/WebPush/Create/preview/WebPushPreview.js +17 -12
  28. package/v2Containers/WebPush/Create/preview/tests/WebPushPreview.test.js +5 -0
  29. package/v2Containers/Whatsapp/tests/__snapshots__/index.test.js.snap +204 -0
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@capillarytech/creatives-library",
3
3
  "author": "meharaj",
4
- "version": "9.0.56-alpha.3",
4
+ "version": "9.0.56-alpha.5",
5
5
  "description": "Capillary creatives ui",
6
6
  "main": "./index.js",
7
7
  "module": "./index.es.js",
package/services/api.js CHANGED
@@ -701,21 +701,23 @@ export const createCentralCommsMetaId = (payload, metaType = TRANSACTION) => {
701
701
  return request(url, getAPICallObject('POST', payload, false, false, false, true));
702
702
  };
703
703
 
704
- // CCS CommDefinition API (new Veyron /api/v1/commdefinition surface, proxied through cap-creatives-api /comm-definitions); used by CommunicationFlow's Save, while legacy messageMeta functions remain for CreativesContainer's save flow (Cap/sagas.js).
704
+ // CCS CommDefinition API, proxied through cap-creatives-api; used by CommunicationFlow's Save, while legacy messageMeta functions remain for CreativesContainer's save flow.
705
+ const COMM_DEFINITIONS_PATH = `${API_ENDPOINT}/comm-definitions`;
706
+
705
707
  export const createCommDefinition = (payload) => {
706
- const url = `${API_ENDPOINT}/comm-definitions`;
708
+ const url = COMM_DEFINITIONS_PATH;
707
709
  return request(url, getAPICallObject('POST', payload, false, false, false, true));
708
710
  };
709
711
 
710
- // Opens a new DRAFT version with the supplied content on an existing CommDefinition (same id/referenceId); used by CommunicationFlow edit-mode saves to preserve the alert's identity instead of creating a new CommDefinition. Route verified against Veyron's /commdefinition/{id}/edit endpoint.
712
+ // Opens a new DRAFT version on an existing CommDefinition, preserving its id/referenceId.
711
713
  export const editCommDefinition = (commDefinitionId, payload) => {
712
- const url = `${API_ENDPOINT}/comm-definitions/${commDefinitionId}/edit`;
714
+ const url = `${COMM_DEFINITIONS_PATH}/${commDefinitionId}/edit`;
713
715
  return request(url, getAPICallObject('POST', payload, false, false, false, true));
714
716
  };
715
717
 
716
- // Sends a real transactional comm via CCS (notify/ui's "Send test" action uses this instead of the legacy createMessageMeta/sendTestMessage flow — see CommonTestAndPreview's handleSendTestMessage).
718
+ // notify/ui's "Send test" uses this instead of the legacy createMessageMeta/sendTestMessage flow.
717
719
  export const sendTransactionalComm = (payload) => {
718
- const url = `${API_ENDPOINT}/comm-definitions/send/transaction`;
720
+ const url = `${COMM_DEFINITIONS_PATH}/send/transaction`;
719
721
  return request(url, getAPICallObject('POST', payload, false, false, false, true));
720
722
  };
721
723
 
@@ -140,7 +140,8 @@ const UnifiedPreview = ({
140
140
  }
141
141
 
142
142
  case CHANNELS.LINE:
143
- // LINE currently carries a single text message; CCS stores lineMessageContent.messageBody as JSON for LINE's native messages[] (see testAndPreviewDataTransform.js), already reduced to plain text before this point, so reuse the SMS chat-bubble preview. Rich LINE types (image/video/sticker/imageMap/carousel/flex) are not specially rendered yet.
143
+ // LINE carries a single plain-text message today, so it reuses the SMS chat-bubble
144
+ // preview. Rich types (image/video/sticker/imageMap/carousel/flex) aren't rendered yet.
144
145
  return (
145
146
  <SmsPreviewContent
146
147
  content={typeof content === 'string' ? content : (content?.resolvedBody || '')}
@@ -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';
@@ -292,6 +297,7 @@ export const ERROR_MESSAGES = {
292
297
  FAILED_TO_EXTRACT_TAGS: 'Failed to extract tags',
293
298
  FAILED_TO_UPDATE_PREVIEW: 'Failed to update preview',
294
299
  FAILED_TO_SEND_TEST_EMAIL: 'Failed to send test email',
300
+ FAILED_TO_SEND_CCS_TEST_MESSAGE: 'Failed to send test message',
295
301
  PROFILE_IS_EMPTY: 'Profile is empty',
296
302
  NO_TEST_CUSTOMERS_FOUND: 'No test customers found',
297
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,7 +209,7 @@ const CommonTestAndPreview = (props) => {
208
209
  wecrmAccounts = [],
209
210
  isLoadingSenderDetails = false,
210
211
  orgUnitId = -1,
211
- // notify/ui only: { commDefinitionId, referenceId } — when present, "Send test" calls CCS's send/transaction endpoint directly instead of the legacy createMessageMeta/sendTestMessage flow.
212
+ // notify/ui only: { commDefinitionId, referenceId } — routes "Send test" to CCS's send/transaction endpoint.
212
213
  ccsSendTransaction,
213
214
  // Email-specific props
214
215
  beeInstance,
@@ -233,7 +234,6 @@ const CommonTestAndPreview = (props) => {
233
234
  const [smsFallbackOptionalTags, setSmsFallbackOptionalTags] = useState([]);
234
235
  const [isExtractingSmsFallbackTags, setIsExtractingSmsFallbackTags] = useState(false);
235
236
  const [customValues, setCustomValues] = useState({});
236
- const [isSendingCcsTest, setIsSendingCcsTest] = useState(false);
237
237
  const previewCustomValuesRef = useRef({});
238
238
  const [showJSON, setShowJSON] = useState(false);
239
239
  const [tagsExtracted, setTagsExtracted] = useState(false);
@@ -3630,7 +3630,9 @@ const CommonTestAndPreview = (props) => {
3630
3630
  */
3631
3631
  // notify/ui: send the test through CCS's own send/transaction endpoint against the alert's
3632
3632
  // real CommDefinition, instead of the legacy createMessageMeta/sendTestMessage flow below.
3633
- const handleCcsSendTestMessage = async () => {
3633
+ // Dispatched via the same action/saga pattern as every other API call in this component,
3634
+ // rather than calling Api.sendTransactionalComm directly.
3635
+ const handleCcsSendTestMessage = () => {
3634
3636
  const allUserIds = [];
3635
3637
  selectedTestEntities.forEach((entityId) => {
3636
3638
  const group = testGroups.find((testGroup) => testEntityIdsEqual(testGroup.groupId, entityId));
@@ -3643,9 +3645,8 @@ const CommonTestAndPreview = (props) => {
3643
3645
  const uniqueUserIds = [...new Set(allUserIds)];
3644
3646
  const { commDefinitionId, referenceId } = ccsSendTransaction;
3645
3647
 
3646
- setIsSendingCcsTest(true);
3647
- try {
3648
- const res = await Api.sendTransactionalComm({
3648
+ actions.sendCcsTestMessageRequested(
3649
+ {
3649
3650
  ...(commDefinitionId ? { commDefinitionId } : { referenceId }),
3650
3651
  uniqueKey: `test_${Date.now()}`,
3651
3652
  isTest: true,
@@ -3653,20 +3654,19 @@ const CommonTestAndPreview = (props) => {
3653
3654
  identifiers: uniqueUserIds.map((userId) => ({ type: 'USER_ID', value: String(userId) })),
3654
3655
  tagValues: customValues,
3655
3656
  },
3656
- });
3657
- if (res?.response?.success === false) {
3658
- throw new Error(res?.response?.errors?.[0]?.message || 'Failed to send test message');
3659
- }
3660
- CapNotification.success({
3661
- message: formatMessage(messages.testMessageSent),
3662
- });
3663
- } catch (error) {
3664
- CapNotification.error({
3665
- message: formatMessage(messages.testMessageFailed),
3666
- });
3667
- } finally {
3668
- setIsSendingCcsTest(false);
3669
- }
3657
+ },
3658
+ (success) => {
3659
+ if (success) {
3660
+ CapNotification.success({
3661
+ message: formatMessage(messages.testMessageSent),
3662
+ });
3663
+ } else {
3664
+ CapNotification.error({
3665
+ message: formatMessage(messages.testMessageFailed),
3666
+ });
3667
+ }
3668
+ },
3669
+ );
3670
3670
  };
3671
3671
 
3672
3672
  const handleSendTestMessage = () => {
@@ -3811,7 +3811,7 @@ const CommonTestAndPreview = (props) => {
3811
3811
  formData={formDataForSendTest}
3812
3812
  content={getCurrentContent}
3813
3813
  channel={channel}
3814
- isSendingTestMessage={isSendingTestMessage || isSendingCcsTest}
3814
+ isSendingTestMessage={isSendingTestMessage || isSendingCcsTestMessage}
3815
3815
  renderAddTestCustomerButton={renderAddTestCustomerButton}
3816
3816
  formatMessage={formatMessage}
3817
3817
  deliverySettings={testPreviewDeliverySettings[channel]}
@@ -3961,6 +3961,7 @@ CommonTestAndPreview.propTypes = {
3961
3961
  message: PropTypes.string,
3962
3962
  })),
3963
3963
  isSendingTestMessage: PropTypes.bool.isRequired,
3964
+ isSendingCcsTestMessage: PropTypes.bool,
3964
3965
  intl: PropTypes.object.isRequired,
3965
3966
  senderDetailsByChannel: PropTypes.object,
3966
3967
  wecrmAccounts: PropTypes.array,
@@ -4010,6 +4011,7 @@ CommonTestAndPreview.defaultProps = {
4010
4011
  isLoadingSenderDetails: false,
4011
4012
  orgUnitId: -1,
4012
4013
  ccsSendTransaction: null,
4014
+ isSendingCcsTestMessage: false,
4013
4015
  };
4014
4016
 
4015
4017
  // ============================================
@@ -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,
@@ -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 = {
@@ -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 = {
@@ -19,6 +19,9 @@ import {
19
19
  SEND_TEST_MESSAGE_REQUESTED,
20
20
  SEND_TEST_MESSAGE_SUCCESS,
21
21
  SEND_TEST_MESSAGE_FAILURE,
22
+ SEND_CCS_TEST_MESSAGE_REQUESTED,
23
+ SEND_CCS_TEST_MESSAGE_SUCCESS,
24
+ SEND_CCS_TEST_MESSAGE_FAILURE,
22
25
  CLEAR_CUSTOMER_SEARCH_STATE,
23
26
  CLEAR_SEARCH_RESULTS,
24
27
  GET_TEST_CUSTOMERS_REQUESTED,
@@ -69,6 +72,8 @@ describe('previewAndTestReducer', () => {
69
72
  isSendingTestMessage: false,
70
73
  sendTestMessageError: null,
71
74
  testMessageResponse: null,
75
+ isSendingCcsTestMessage: false,
76
+ sendCcsTestMessageError: null,
72
77
  tags: {
73
78
  loading: false,
74
79
  error: null,
@@ -533,6 +538,42 @@ describe('previewAndTestReducer', () => {
533
538
  });
534
539
  });
535
540
 
541
+ describe('SEND_CCS_TEST_MESSAGE_REQUESTED', () => {
542
+ it('should set the sending flag and clear errors', () => {
543
+ const action = { type: SEND_CCS_TEST_MESSAGE_REQUESTED };
544
+ const result = previewAndTestReducer(initialState, action);
545
+
546
+ expect(result.get('isSendingCcsTestMessage')).toBe(true);
547
+ expect(result.get('sendCcsTestMessageError')).toBeNull();
548
+ });
549
+ });
550
+
551
+ describe('SEND_CCS_TEST_MESSAGE_SUCCESS', () => {
552
+ it('should clear the sending flag and any error', () => {
553
+ const stateWhileSending = initialState
554
+ .set('isSendingCcsTestMessage', true)
555
+ .set('sendCcsTestMessageError', 'previous error');
556
+ const action = { type: SEND_CCS_TEST_MESSAGE_SUCCESS };
557
+ const result = previewAndTestReducer(stateWhileSending, action);
558
+
559
+ expect(result.get('isSendingCcsTestMessage')).toBe(false);
560
+ expect(result.get('sendCcsTestMessageError')).toBeNull();
561
+ });
562
+ });
563
+
564
+ describe('SEND_CCS_TEST_MESSAGE_FAILURE', () => {
565
+ it('should clear the sending flag and set the error', () => {
566
+ const action = {
567
+ type: SEND_CCS_TEST_MESSAGE_FAILURE,
568
+ payload: { error: 'NO_LIVE_VERSION' },
569
+ };
570
+ const result = previewAndTestReducer(initialState, action);
571
+
572
+ expect(result.get('isSendingCcsTestMessage')).toBe(false);
573
+ expect(result.get('sendCcsTestMessageError')).toBe('NO_LIVE_VERSION');
574
+ });
575
+ });
576
+
536
577
  describe('CLEAR_SEARCH_RESULTS', () => {
537
578
  it('should clear search results and reset flags', () => {
538
579
  const stateWithResults = initialState
@@ -12,6 +12,7 @@ import {
12
12
  extractTagsSaga,
13
13
  updatePreviewSaga,
14
14
  sendTestMessageSaga,
15
+ sendCcsTestMessageSaga,
15
16
  getBulkCustomerDetails,
16
17
  fetchTestCustomersSaga,
17
18
  fetchTestGroupsSaga,
@@ -45,6 +46,8 @@ import {
45
46
  UPDATE_PREVIEW_FAILURE,
46
47
  SEND_TEST_MESSAGE_SUCCESS,
47
48
  SEND_TEST_MESSAGE_FAILURE,
49
+ SEND_CCS_TEST_MESSAGE_SUCCESS,
50
+ SEND_CCS_TEST_MESSAGE_FAILURE,
48
51
  GET_TEST_CUSTOMERS_SUCCESS,
49
52
  GET_TEST_CUSTOMERS_FAILURE,
50
53
  GET_TEST_GROUPS_SUCCESS,
@@ -747,6 +750,87 @@ describe('CommonTestAndPreview Sagas', () => {
747
750
  });
748
751
  });
749
752
 
753
+ describe('sendCcsTestMessageSaga', () => {
754
+ it('should send the CCS test message successfully', () => {
755
+ const callback = jest.fn();
756
+ const action = {
757
+ payload: { commDefinitionId: 'cd_123', recipient: { identifiers: [] } },
758
+ callback,
759
+ };
760
+ const response = { response: { success: true, data: {} } };
761
+
762
+ const generator = sendCcsTestMessageSaga(action);
763
+
764
+ expect(generator.next().value).toEqual(
765
+ call(Api.sendTransactionalComm, action.payload)
766
+ );
767
+
768
+ expect(generator.next(response).value).toEqual(
769
+ put({ type: SEND_CCS_TEST_MESSAGE_SUCCESS })
770
+ );
771
+
772
+ generator.next();
773
+ expect(callback).toHaveBeenCalledWith(true);
774
+ expect(generator.next().done).toBe(true);
775
+ });
776
+
777
+ it('should handle a CCS envelope failure (success: false)', () => {
778
+ const callback = jest.fn();
779
+ const action = {
780
+ payload: { commDefinitionId: 'cd_123', recipient: { identifiers: [] } },
781
+ callback,
782
+ };
783
+ const response = {
784
+ response: { success: false, errors: [{ message: 'NO_LIVE_VERSION' }] },
785
+ };
786
+
787
+ const generator = sendCcsTestMessageSaga(action);
788
+
789
+ generator.next();
790
+ expect(generator.next(response).value).toEqual(
791
+ put({ type: SEND_CCS_TEST_MESSAGE_FAILURE, payload: { error: 'NO_LIVE_VERSION' } })
792
+ );
793
+
794
+ generator.next();
795
+ expect(callback).toHaveBeenCalledWith(false);
796
+ });
797
+
798
+ it('falls back to a default error message when the envelope carries none', () => {
799
+ const callback = jest.fn();
800
+ const action = {
801
+ payload: { commDefinitionId: 'cd_123', recipient: { identifiers: [] } },
802
+ callback,
803
+ };
804
+ const response = { response: { success: false, errors: [] } };
805
+
806
+ const generator = sendCcsTestMessageSaga(action);
807
+
808
+ generator.next();
809
+ expect(generator.next(response).value).toEqual(
810
+ put({ type: SEND_CCS_TEST_MESSAGE_FAILURE, payload: { error: 'Failed to send test message' } })
811
+ );
812
+ });
813
+
814
+ it('should handle network error', () => {
815
+ const callback = jest.fn();
816
+ const action = {
817
+ payload: { commDefinitionId: 'cd_123', recipient: { identifiers: [] } },
818
+ callback,
819
+ };
820
+ const error = new Error('Network error');
821
+
822
+ const generator = sendCcsTestMessageSaga(action);
823
+
824
+ generator.next();
825
+ expect(generator.throw(error).value).toEqual(
826
+ put({ type: SEND_CCS_TEST_MESSAGE_FAILURE, payload: { error: 'Network error' } })
827
+ );
828
+
829
+ generator.next();
830
+ expect(callback).toHaveBeenCalledWith(false);
831
+ });
832
+ });
833
+
750
834
  describe('fetchTestCustomersSaga', () => {
751
835
  it('should fetch test customers successfully', () => {
752
836
  const response = {
@@ -28,6 +28,7 @@ import {
28
28
  makeSelectPrefilledValues,
29
29
  makeSelectTestMessageResponse,
30
30
  makeSelectIsSendingTestMessage,
31
+ makeSelectIsSendingCcsTestMessage,
31
32
  makeSelectUpdatePreviewError,
32
33
  makeSelectUpdatePreviewErrors,
33
34
  makeSelectFetchPrefilledValuesError,
@@ -78,6 +79,8 @@ describe('CommonTestAndPreview Selectors', () => {
78
79
  isSendingTestEmail: false,
79
80
  sendTestMessageError: null,
80
81
  testMessageResponse: { messageId: '123', status: 'sent' },
82
+ isSendingCcsTestMessage: false,
83
+ sendCcsTestMessageError: null,
81
84
  tags: {
82
85
  loading: false,
83
86
  error: null,
@@ -470,6 +473,15 @@ describe('CommonTestAndPreview Selectors', () => {
470
473
  });
471
474
  });
472
475
 
476
+ describe('makeSelectIsSendingCcsTestMessage', () => {
477
+ it('should select isSendingCcsTestMessage flag', () => {
478
+ const selector = makeSelectIsSendingCcsTestMessage();
479
+ const result = selector(mockState);
480
+
481
+ expect(result).toBe(false);
482
+ });
483
+ });
484
+
473
485
  describe('makeSelectUpdatePreviewError', () => {
474
486
  it('should select updatePreviewError from state', () => {
475
487
  const selector = makeSelectUpdatePreviewError();