@capillarytech/creatives-library 9.0.13 → 9.0.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/services/api.js +10 -0
- package/services/tests/api.test.js +83 -0
- package/v2Components/CommonTestAndPreview/UnifiedPreview/WhatsAppPreviewContent.js +5 -3
- package/v2Components/CommonTestAndPreview/index.js +7 -0
- package/v2Components/NavigationBar/index.js +27 -0
- package/v2Components/NavigationBar/messages.js +4 -0
- package/v2Components/NavigationBar/tests/index.test.js +19 -0
- package/v2Components/NewCallTask/index.js +6 -1
- package/v2Components/TemplatePreview/index.js +4 -2
- package/v2Containers/Cap/index.js +3 -1
- package/v2Containers/CommunicationFlow/CommunicationFlow.js +130 -20
- package/v2Containers/CommunicationFlow/CommunicationFlow.scss +154 -0
- package/v2Containers/CommunicationFlow/CommunicationFlowCard.js +240 -0
- package/v2Containers/CommunicationFlow/DemoPage.js +47 -0
- package/v2Containers/CommunicationFlow/Tests/CommunicationFlow.test.js +369 -2
- package/v2Containers/CommunicationFlow/Tests/CommunicationFlowCard.test.js +619 -0
- package/v2Containers/CommunicationFlow/Tests/DemoPage.test.js +77 -0
- package/v2Containers/CommunicationFlow/Tests/getContentBody.test.js +933 -0
- package/v2Containers/CommunicationFlow/constants.js +45 -10
- package/v2Containers/CommunicationFlow/index.js +5 -2
- package/v2Containers/CommunicationFlow/messages.js +20 -0
- package/v2Containers/CommunicationFlow/steps/ChannelSelectionStep/ChannelSelectionStep.js +94 -31
- package/v2Containers/CommunicationFlow/steps/ChannelSelectionStep/ChannelSelectionStep.scss +14 -11
- package/v2Containers/CommunicationFlow/steps/ChannelSelectionStep/Tests/ChannelSelectionStep.test.js +1144 -32
- package/v2Containers/CommunicationFlow/steps/ChannelSelectionStep/extractContentForPreview.js +183 -0
- package/v2Containers/CommunicationFlow/steps/CommunicationStrategyStep/CommunicationStrategyStep.js +3 -0
- package/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/DeliverySettingsSection.js +39 -0
- package/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/Tests/DeliverySettingsSection.test.js +6 -2
- package/v2Containers/CommunicationFlow/utils/getContentBody.js +369 -0
- package/v2Containers/CommunicationFlow/utils/getContentBody.scss +19 -0
- package/v2Containers/CommunicationFlow/utils/getEnabledSteps.js +1 -1
- package/v2Containers/CreativesContainer/constants.js +6 -0
- package/v2Containers/CreativesContainer/index.js +68 -1
- package/v2Containers/CreativesContainer/tests/__snapshots__/SlideBoxContent.test.js.snap +2 -2
- package/v2Containers/Templates/index.js +2 -2
- package/v2Containers/TemplatesV2/index.js +9 -1
- package/v2Containers/Whatsapp/tests/__snapshots__/index.test.js.snap +41 -34
|
@@ -0,0 +1,619 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
|
|
3
|
+
jest.mock('../index', () => function MockCommunicationFlow({ onSave, onCancel, config }) {
|
|
4
|
+
return (
|
|
5
|
+
<div data-testid="comm-flow-mock" data-mode={config?.mode}>
|
|
6
|
+
<button
|
|
7
|
+
type="button"
|
|
8
|
+
data-testid="flow-save"
|
|
9
|
+
onClick={() => onSave({
|
|
10
|
+
contentItems: [{ channel: 'SMS', templateData: {} }],
|
|
11
|
+
communicationStrategy: 'SINGLE_TEMPLATE',
|
|
12
|
+
deliverySetting: { channelSetting: { SMS: {} } },
|
|
13
|
+
dynamicControls: {},
|
|
14
|
+
})}
|
|
15
|
+
>
|
|
16
|
+
Save
|
|
17
|
+
</button>
|
|
18
|
+
<button type="button" data-testid="flow-cancel" onClick={onCancel}>
|
|
19
|
+
Cancel
|
|
20
|
+
</button>
|
|
21
|
+
</div>
|
|
22
|
+
);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
jest.mock('@capillarytech/cap-ui-library/CapSlideBox', () => function MockCapSlideBox({ show, content, header }) {
|
|
26
|
+
if (!show) return null;
|
|
27
|
+
return (
|
|
28
|
+
<div data-testid="slide-box">
|
|
29
|
+
<div data-testid="slide-box-header">{header}</div>
|
|
30
|
+
<div data-testid="slide-box-content">{content}</div>
|
|
31
|
+
</div>
|
|
32
|
+
);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
jest.mock('@capillarytech/cap-ui-library/CapIcon', () => function MockCapIcon({ type, onClick, style }) {
|
|
36
|
+
return <span data-testid={`cap-icon-${type}`} role="button" onClick={onClick} style={style} />;
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
jest.mock('../utils/getContentBody', () => jest.fn());
|
|
40
|
+
|
|
41
|
+
import { render, screen, fireEvent } from '@testing-library/react';
|
|
42
|
+
import '@testing-library/jest-dom';
|
|
43
|
+
import { IntlProvider } from 'react-intl';
|
|
44
|
+
import CommunicationFlowCard from '../CommunicationFlowCard';
|
|
45
|
+
import getContentBody from '../utils/getContentBody';
|
|
46
|
+
|
|
47
|
+
beforeEach(() => {
|
|
48
|
+
getContentBody.mockReturnValue(<span data-testid="content-body">Content</span>);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
const baseConfig = {
|
|
52
|
+
consumer: 'campaigns',
|
|
53
|
+
mode: 'create',
|
|
54
|
+
features: {},
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
function renderCard(props = {}) {
|
|
58
|
+
const merged = {
|
|
59
|
+
config: baseConfig,
|
|
60
|
+
initialData: null,
|
|
61
|
+
onSave: jest.fn(),
|
|
62
|
+
onCancel: jest.fn(),
|
|
63
|
+
onChange: jest.fn(),
|
|
64
|
+
cap: {},
|
|
65
|
+
...props,
|
|
66
|
+
};
|
|
67
|
+
return render(
|
|
68
|
+
<IntlProvider locale="en" messages={{}} defaultLocale="en">
|
|
69
|
+
<CommunicationFlowCard {...merged} />
|
|
70
|
+
</IntlProvider>,
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const makeSavedData = (overrides = {}) => ({
|
|
75
|
+
contentItems: [{ channel: 'SMS', templateData: {} }],
|
|
76
|
+
communicationStrategy: 'SINGLE_TEMPLATE',
|
|
77
|
+
messageType: null,
|
|
78
|
+
deliverySetting: { channelSetting: { SMS: { gsmSenderId: 'TEST_ID' } } },
|
|
79
|
+
dynamicControls: {},
|
|
80
|
+
...overrides,
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
describe('CommunicationFlowCard', () => {
|
|
84
|
+
describe('empty state', () => {
|
|
85
|
+
it('renders Add creatives button when no initialData', () => {
|
|
86
|
+
renderCard();
|
|
87
|
+
expect(screen.getByText('Add creatives')).toBeInTheDocument();
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it('does not render summary card when no initialData', () => {
|
|
91
|
+
renderCard();
|
|
92
|
+
expect(screen.queryByText('Message:')).not.toBeInTheDocument();
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
it('SlideBox is not shown initially', () => {
|
|
96
|
+
renderCard();
|
|
97
|
+
expect(screen.queryByTestId('slide-box')).not.toBeInTheDocument();
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it('opens SlideBox when Add creatives button is clicked', () => {
|
|
101
|
+
renderCard();
|
|
102
|
+
fireEvent.click(screen.getByText('Add creatives'));
|
|
103
|
+
expect(screen.getByTestId('slide-box')).toBeInTheDocument();
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it('shows CommunicationFlow in create mode when opened from empty state', () => {
|
|
107
|
+
renderCard();
|
|
108
|
+
fireEvent.click(screen.getByText('Add creatives'));
|
|
109
|
+
expect(screen.getByTestId('comm-flow-mock')).toHaveAttribute('data-mode', 'create');
|
|
110
|
+
});
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
describe('configured state (initialData provided)', () => {
|
|
114
|
+
it('renders summary card and hides empty card when initialData has contentItems', () => {
|
|
115
|
+
renderCard({ initialData: makeSavedData() });
|
|
116
|
+
expect(screen.getByText('Message:')).toBeInTheDocument();
|
|
117
|
+
expect(screen.queryByText('Add creatives')).not.toBeInTheDocument();
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it('shows strategy label from DEFAULT_COMMUNICATION_STRATEGY_OPTIONS', () => {
|
|
121
|
+
renderCard({ initialData: makeSavedData() });
|
|
122
|
+
expect(screen.getByText('Single template')).toBeInTheDocument();
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
it('shows strategy label from config.features.communicationStrategyData.options when provided', () => {
|
|
126
|
+
const config = {
|
|
127
|
+
...baseConfig,
|
|
128
|
+
features: {
|
|
129
|
+
communicationStrategyData: {
|
|
130
|
+
options: [{ value: 'SINGLE_TEMPLATE', label: 'My Custom Strategy' }],
|
|
131
|
+
},
|
|
132
|
+
},
|
|
133
|
+
};
|
|
134
|
+
renderCard({ config, initialData: makeSavedData() });
|
|
135
|
+
expect(screen.getByText('My Custom Strategy')).toBeInTheDocument();
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
it('shows raw strategyValue when no matching option is found', () => {
|
|
139
|
+
renderCard({ initialData: makeSavedData({ communicationStrategy: 'UNKNOWN_STRATEGY' }) });
|
|
140
|
+
expect(screen.getByText('UNKNOWN_STRATEGY')).toBeInTheDocument();
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
it('shows empty strategy label without crashing when communicationStrategy is null', () => {
|
|
144
|
+
renderCard({ initialData: makeSavedData({ communicationStrategy: null }) });
|
|
145
|
+
expect(screen.getByText('Message:')).toBeInTheDocument();
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
it('shows messageTypeLabel in parentheses when messageType matches a config option', () => {
|
|
149
|
+
const config = {
|
|
150
|
+
...baseConfig,
|
|
151
|
+
features: {
|
|
152
|
+
messageTypeData: { options: [{ value: 'promotional', label: 'Promotional' }] },
|
|
153
|
+
},
|
|
154
|
+
};
|
|
155
|
+
renderCard({ config, initialData: makeSavedData({ messageType: 'promotional' }) });
|
|
156
|
+
expect(screen.getByText('(Promotional)')).toBeInTheDocument();
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
it('shows raw messageType in parentheses when no match is found in options', () => {
|
|
160
|
+
renderCard({ initialData: makeSavedData({ messageType: 'custom_type' }) });
|
|
161
|
+
expect(screen.getByText('(custom_type)')).toBeInTheDocument();
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
it('does not render messageType parenthetical when messageType is null', () => {
|
|
165
|
+
renderCard({
|
|
166
|
+
initialData: makeSavedData({
|
|
167
|
+
messageType: null,
|
|
168
|
+
deliverySetting: { channelSetting: { SMS: { gsmSenderId: null } } },
|
|
169
|
+
}),
|
|
170
|
+
});
|
|
171
|
+
expect(screen.queryByText(/\(.*\)/)).not.toBeInTheDocument();
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
it('calls getContentBody with the first content item', () => {
|
|
175
|
+
const item = { channel: 'SMS', templateData: { smsBody: 'Hi' } };
|
|
176
|
+
renderCard({ initialData: makeSavedData({ contentItems: [item] }) });
|
|
177
|
+
expect(getContentBody).toHaveBeenCalledWith(item);
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
it('renders the output of getContentBody', () => {
|
|
181
|
+
renderCard({ initialData: makeSavedData() });
|
|
182
|
+
expect(screen.getByTestId('content-body')).toBeInTheDocument();
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
it('shows Other controls section title', () => {
|
|
186
|
+
renderCard({ initialData: makeSavedData() });
|
|
187
|
+
expect(screen.getByText('Other controls')).toBeInTheDocument();
|
|
188
|
+
});
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
describe('channel config resolution', () => {
|
|
192
|
+
it('matches channelConfig by uppercase channel value', () => {
|
|
193
|
+
renderCard({
|
|
194
|
+
initialData: makeSavedData({
|
|
195
|
+
contentItems: [{ channel: 'EMAIL', templateData: {} }],
|
|
196
|
+
deliverySetting: { channelSetting: { EMAIL: {} } },
|
|
197
|
+
}),
|
|
198
|
+
});
|
|
199
|
+
expect(screen.getByText('Message:')).toBeInTheDocument();
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
it('matches channelConfig by channelProp when channel is lowercase', () => {
|
|
203
|
+
renderCard({
|
|
204
|
+
initialData: makeSavedData({
|
|
205
|
+
contentItems: [{ channel: 'sms', templateData: {} }],
|
|
206
|
+
deliverySetting: { channelSetting: {} },
|
|
207
|
+
}),
|
|
208
|
+
});
|
|
209
|
+
expect(screen.getByText('Message:')).toBeInTheDocument();
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
it('falls back to raw channel string when no channelConfig is found', () => {
|
|
213
|
+
renderCard({
|
|
214
|
+
initialData: makeSavedData({
|
|
215
|
+
contentItems: [{ channel: 'UNKNOWN_CH', templateData: {} }],
|
|
216
|
+
deliverySetting: { channelSetting: {} },
|
|
217
|
+
}),
|
|
218
|
+
});
|
|
219
|
+
expect(screen.getByText('UNKNOWN_CH')).toBeInTheDocument();
|
|
220
|
+
});
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
describe('sender ID display', () => {
|
|
224
|
+
it('shows gsmSenderId for SMS with Sender ID label', () => {
|
|
225
|
+
renderCard({
|
|
226
|
+
initialData: makeSavedData({
|
|
227
|
+
deliverySetting: { channelSetting: { SMS: { gsmSenderId: 'CAPS_SMS' } } },
|
|
228
|
+
}),
|
|
229
|
+
});
|
|
230
|
+
expect(screen.getByText(/CAPS_SMS/)).toBeInTheDocument();
|
|
231
|
+
expect(screen.getByText(/Sender ID/)).toBeInTheDocument();
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
it('shows senderEmail for EMAIL with Sender ID label', () => {
|
|
235
|
+
renderCard({
|
|
236
|
+
initialData: makeSavedData({
|
|
237
|
+
contentItems: [{ channel: 'EMAIL', templateData: {} }],
|
|
238
|
+
deliverySetting: { channelSetting: { EMAIL: { senderEmail: 'test@example.com' } } },
|
|
239
|
+
}),
|
|
240
|
+
});
|
|
241
|
+
expect(screen.getByText(/test@example.com/)).toBeInTheDocument();
|
|
242
|
+
expect(screen.getByText(/Sender ID/)).toBeInTheDocument();
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
it('shows sender (primary) for VIBER', () => {
|
|
246
|
+
renderCard({
|
|
247
|
+
initialData: makeSavedData({
|
|
248
|
+
contentItems: [{ channel: 'VIBER', templateData: {} }],
|
|
249
|
+
deliverySetting: { channelSetting: { VIBER: { sender: 'ViberSender' } } },
|
|
250
|
+
}),
|
|
251
|
+
});
|
|
252
|
+
expect(screen.getByText(/ViberSender/)).toBeInTheDocument();
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
it('falls back to gsmSenderId for VIBER when sender is absent', () => {
|
|
256
|
+
renderCard({
|
|
257
|
+
initialData: makeSavedData({
|
|
258
|
+
contentItems: [{ channel: 'VIBER', templateData: {} }],
|
|
259
|
+
deliverySetting: { channelSetting: { VIBER: { gsmSenderId: 'FallbackViber' } } },
|
|
260
|
+
}),
|
|
261
|
+
});
|
|
262
|
+
expect(screen.getByText(/FallbackViber/)).toBeInTheDocument();
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
it('shows senderMobNum for WHATSAPP with Sender number label', () => {
|
|
266
|
+
renderCard({
|
|
267
|
+
initialData: makeSavedData({
|
|
268
|
+
contentItems: [{ channel: 'WHATSAPP', templateData: {} }],
|
|
269
|
+
deliverySetting: { channelSetting: { WHATSAPP: { senderMobNum: '+1234567890' } } },
|
|
270
|
+
}),
|
|
271
|
+
});
|
|
272
|
+
expect(screen.getByText(/\+1234567890/)).toBeInTheDocument();
|
|
273
|
+
expect(screen.getByText(/Sender number/)).toBeInTheDocument();
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
it('shows senderMobNum for RCS with Sender number label', () => {
|
|
277
|
+
renderCard({
|
|
278
|
+
initialData: makeSavedData({
|
|
279
|
+
contentItems: [{ channel: 'RCS', templateData: {} }],
|
|
280
|
+
deliverySetting: { channelSetting: { RCS: { senderMobNum: '+9876543210' } } },
|
|
281
|
+
}),
|
|
282
|
+
});
|
|
283
|
+
expect(screen.getByText(/\+9876543210/)).toBeInTheDocument();
|
|
284
|
+
expect(screen.getByText(/Sender number/)).toBeInTheDocument();
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
it('falls back to rcsSender for RCS when senderMobNum is absent', () => {
|
|
288
|
+
renderCard({
|
|
289
|
+
initialData: makeSavedData({
|
|
290
|
+
contentItems: [{ channel: 'RCS', templateData: {} }],
|
|
291
|
+
deliverySetting: { channelSetting: { RCS: { rcsSender: 'RCS_SENDER_ID' } } },
|
|
292
|
+
}),
|
|
293
|
+
});
|
|
294
|
+
expect(screen.getByText(/RCS_SENDER_ID/)).toBeInTheDocument();
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
it('shows zaloSenderId for ZALO', () => {
|
|
298
|
+
renderCard({
|
|
299
|
+
initialData: makeSavedData({
|
|
300
|
+
contentItems: [{ channel: 'ZALO', templateData: {} }],
|
|
301
|
+
deliverySetting: { channelSetting: { ZALO: { zaloSenderId: 'ZALO_123' } } },
|
|
302
|
+
}),
|
|
303
|
+
});
|
|
304
|
+
expect(screen.getByText(/ZALO_123/)).toBeInTheDocument();
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
it('does not show sender ID for LINE (excluded from delivery settings)', () => {
|
|
308
|
+
renderCard({
|
|
309
|
+
initialData: makeSavedData({
|
|
310
|
+
contentItems: [{ channel: 'LINE', templateData: {} }],
|
|
311
|
+
deliverySetting: { channelSetting: { LINE: { lineSenderId: 'LINE_ID' } } },
|
|
312
|
+
}),
|
|
313
|
+
});
|
|
314
|
+
expect(screen.queryByText(/LINE_ID/)).not.toBeInTheDocument();
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
it('does not show sender row when senderIdValue is null', () => {
|
|
318
|
+
renderCard({
|
|
319
|
+
initialData: makeSavedData({
|
|
320
|
+
deliverySetting: { channelSetting: { SMS: { gsmSenderId: null } } },
|
|
321
|
+
}),
|
|
322
|
+
});
|
|
323
|
+
expect(screen.queryByText(/Sender ID/)).not.toBeInTheDocument();
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
it('returns null sender for channels with no matching sender logic (e.g. MOBILEPUSH)', () => {
|
|
327
|
+
renderCard({
|
|
328
|
+
initialData: makeSavedData({
|
|
329
|
+
contentItems: [{ channel: 'MOBILEPUSH', templateData: {} }],
|
|
330
|
+
deliverySetting: { channelSetting: {} },
|
|
331
|
+
}),
|
|
332
|
+
});
|
|
333
|
+
expect(screen.queryByText(/Sender ID/)).not.toBeInTheDocument();
|
|
334
|
+
expect(screen.queryByText(/Sender number/)).not.toBeInTheDocument();
|
|
335
|
+
});
|
|
336
|
+
});
|
|
337
|
+
|
|
338
|
+
describe('resolveValue edge cases (tested via sender ID)', () => {
|
|
339
|
+
it('resolves an array to its first element', () => {
|
|
340
|
+
renderCard({
|
|
341
|
+
initialData: makeSavedData({
|
|
342
|
+
deliverySetting: { channelSetting: { SMS: { gsmSenderId: ['FIRST', 'SECOND'] } } },
|
|
343
|
+
}),
|
|
344
|
+
});
|
|
345
|
+
expect(screen.getByText(/FIRST/)).toBeInTheDocument();
|
|
346
|
+
});
|
|
347
|
+
|
|
348
|
+
it('resolves an object with a .value property', () => {
|
|
349
|
+
renderCard({
|
|
350
|
+
initialData: makeSavedData({
|
|
351
|
+
deliverySetting: { channelSetting: { SMS: { gsmSenderId: { value: 'OBJ_VAL' } } } },
|
|
352
|
+
}),
|
|
353
|
+
});
|
|
354
|
+
expect(screen.getByText(/OBJ_VAL/)).toBeInTheDocument();
|
|
355
|
+
});
|
|
356
|
+
|
|
357
|
+
it('resolves an object with .label when .value is absent', () => {
|
|
358
|
+
renderCard({
|
|
359
|
+
initialData: makeSavedData({
|
|
360
|
+
deliverySetting: { channelSetting: { SMS: { gsmSenderId: { label: 'OBJ_LBL' } } } },
|
|
361
|
+
}),
|
|
362
|
+
});
|
|
363
|
+
expect(screen.getByText(/OBJ_LBL/)).toBeInTheDocument();
|
|
364
|
+
});
|
|
365
|
+
|
|
366
|
+
it('returns null for an object with neither .value nor .label', () => {
|
|
367
|
+
renderCard({
|
|
368
|
+
initialData: makeSavedData({
|
|
369
|
+
deliverySetting: { channelSetting: { SMS: { gsmSenderId: { other: 'x' } } } },
|
|
370
|
+
}),
|
|
371
|
+
});
|
|
372
|
+
expect(screen.queryByText(/Sender ID/)).not.toBeInTheDocument();
|
|
373
|
+
});
|
|
374
|
+
|
|
375
|
+
it('returns null for a null value', () => {
|
|
376
|
+
renderCard({
|
|
377
|
+
initialData: makeSavedData({
|
|
378
|
+
deliverySetting: { channelSetting: { SMS: { gsmSenderId: null } } },
|
|
379
|
+
}),
|
|
380
|
+
});
|
|
381
|
+
expect(screen.queryByText(/Sender ID/)).not.toBeInTheDocument();
|
|
382
|
+
});
|
|
383
|
+
|
|
384
|
+
it('returns null for an empty string value', () => {
|
|
385
|
+
renderCard({
|
|
386
|
+
initialData: makeSavedData({
|
|
387
|
+
deliverySetting: { channelSetting: { SMS: { gsmSenderId: '' } } },
|
|
388
|
+
}),
|
|
389
|
+
});
|
|
390
|
+
expect(screen.queryByText(/Sender ID/)).not.toBeInTheDocument();
|
|
391
|
+
});
|
|
392
|
+
|
|
393
|
+
it('returns null for an array whose first element is null', () => {
|
|
394
|
+
renderCard({
|
|
395
|
+
initialData: makeSavedData({
|
|
396
|
+
deliverySetting: { channelSetting: { SMS: { gsmSenderId: [null] } } },
|
|
397
|
+
}),
|
|
398
|
+
});
|
|
399
|
+
expect(screen.queryByText(/Sender ID/)).not.toBeInTheDocument();
|
|
400
|
+
});
|
|
401
|
+
|
|
402
|
+
it('returns null for an array whose first element is an empty string', () => {
|
|
403
|
+
renderCard({
|
|
404
|
+
initialData: makeSavedData({
|
|
405
|
+
deliverySetting: { channelSetting: { SMS: { gsmSenderId: [''] } } },
|
|
406
|
+
}),
|
|
407
|
+
});
|
|
408
|
+
expect(screen.queryByText(/Sender ID/)).not.toBeInTheDocument();
|
|
409
|
+
});
|
|
410
|
+
});
|
|
411
|
+
|
|
412
|
+
describe('dynamic controls', () => {
|
|
413
|
+
const makeControlConfig = (controls) => ({
|
|
414
|
+
...baseConfig,
|
|
415
|
+
features: { dynamicControlsData: { controls } },
|
|
416
|
+
});
|
|
417
|
+
|
|
418
|
+
it('renders Yes for true dynamic control values', () => {
|
|
419
|
+
const controls = [{ key: 'sendToControlCustomers', label: 'Control Customers' }];
|
|
420
|
+
renderCard({
|
|
421
|
+
config: makeControlConfig(controls),
|
|
422
|
+
initialData: makeSavedData({ dynamicControls: { sendToControlCustomers: true } }),
|
|
423
|
+
});
|
|
424
|
+
expect(screen.getByText('Control Customers')).toBeInTheDocument();
|
|
425
|
+
expect(screen.getByText('Yes')).toBeInTheDocument();
|
|
426
|
+
});
|
|
427
|
+
|
|
428
|
+
it('renders No for false dynamic control values', () => {
|
|
429
|
+
const controls = [{ key: 'sendToControlCustomers', label: 'Control Customers' }];
|
|
430
|
+
renderCard({
|
|
431
|
+
config: makeControlConfig(controls),
|
|
432
|
+
initialData: makeSavedData({ dynamicControls: { sendToControlCustomers: false } }),
|
|
433
|
+
});
|
|
434
|
+
expect(screen.getByText('No')).toBeInTheDocument();
|
|
435
|
+
});
|
|
436
|
+
|
|
437
|
+
it('renders multiple control rows', () => {
|
|
438
|
+
const controls = [
|
|
439
|
+
{ key: 'sendToControlCustomers', label: 'Label A' },
|
|
440
|
+
{ key: 'sendToBrandPocs', label: 'Label B' },
|
|
441
|
+
];
|
|
442
|
+
renderCard({
|
|
443
|
+
config: makeControlConfig(controls),
|
|
444
|
+
initialData: makeSavedData({
|
|
445
|
+
dynamicControls: { sendToControlCustomers: true, sendToBrandPocs: false },
|
|
446
|
+
}),
|
|
447
|
+
});
|
|
448
|
+
expect(screen.getByText('Label A')).toBeInTheDocument();
|
|
449
|
+
expect(screen.getByText('Label B')).toBeInTheDocument();
|
|
450
|
+
});
|
|
451
|
+
|
|
452
|
+
it('skips control keys not found in controls config', () => {
|
|
453
|
+
const controls = [{ key: 'knownKey', label: 'Known Label' }];
|
|
454
|
+
renderCard({
|
|
455
|
+
config: makeControlConfig(controls),
|
|
456
|
+
initialData: makeSavedData({ dynamicControls: { unknownKey: true } }),
|
|
457
|
+
});
|
|
458
|
+
expect(screen.queryByText('Known Label')).not.toBeInTheDocument();
|
|
459
|
+
});
|
|
460
|
+
|
|
461
|
+
it('falls back to DYNAMIC_CONTROLS_CONFIG when config.features.dynamicControlsData is absent', () => {
|
|
462
|
+
renderCard({
|
|
463
|
+
initialData: makeSavedData({ dynamicControls: { useTinyUrl: true } }),
|
|
464
|
+
});
|
|
465
|
+
expect(screen.getByText('Yes')).toBeInTheDocument();
|
|
466
|
+
});
|
|
467
|
+
|
|
468
|
+
it('renders no control rows when dynamicControls is an empty object', () => {
|
|
469
|
+
renderCard({ initialData: makeSavedData({ dynamicControls: {} }) });
|
|
470
|
+
expect(screen.queryByText('Yes')).not.toBeInTheDocument();
|
|
471
|
+
expect(screen.queryByText('No')).not.toBeInTheDocument();
|
|
472
|
+
});
|
|
473
|
+
});
|
|
474
|
+
|
|
475
|
+
describe('edit icon opens SlideBox', () => {
|
|
476
|
+
it('opens SlideBox when edit icon is clicked on the summary card', () => {
|
|
477
|
+
renderCard({ initialData: makeSavedData() });
|
|
478
|
+
expect(screen.queryByTestId('slide-box')).not.toBeInTheDocument();
|
|
479
|
+
fireEvent.click(screen.getByTestId('cap-icon-edit'));
|
|
480
|
+
expect(screen.getByTestId('slide-box')).toBeInTheDocument();
|
|
481
|
+
});
|
|
482
|
+
|
|
483
|
+
it('shows CommunicationFlow in edit mode when opened via edit icon', () => {
|
|
484
|
+
renderCard({ initialData: makeSavedData() });
|
|
485
|
+
fireEvent.click(screen.getByTestId('cap-icon-edit'));
|
|
486
|
+
expect(screen.getByTestId('comm-flow-mock')).toHaveAttribute('data-mode', 'edit');
|
|
487
|
+
});
|
|
488
|
+
});
|
|
489
|
+
|
|
490
|
+
describe('handleSave', () => {
|
|
491
|
+
it('transitions to summary card view after save', () => {
|
|
492
|
+
renderCard();
|
|
493
|
+
fireEvent.click(screen.getByText('Add creatives'));
|
|
494
|
+
fireEvent.click(screen.getByTestId('flow-save'));
|
|
495
|
+
expect(screen.getByText('Message:')).toBeInTheDocument();
|
|
496
|
+
});
|
|
497
|
+
|
|
498
|
+
it('closes SlideBox after save', () => {
|
|
499
|
+
renderCard();
|
|
500
|
+
fireEvent.click(screen.getByText('Add creatives'));
|
|
501
|
+
fireEvent.click(screen.getByTestId('flow-save'));
|
|
502
|
+
expect(screen.queryByTestId('slide-box')).not.toBeInTheDocument();
|
|
503
|
+
});
|
|
504
|
+
|
|
505
|
+
it('calls onSave prop with the saved data', () => {
|
|
506
|
+
const onSave = jest.fn();
|
|
507
|
+
renderCard({ onSave });
|
|
508
|
+
fireEvent.click(screen.getByText('Add creatives'));
|
|
509
|
+
fireEvent.click(screen.getByTestId('flow-save'));
|
|
510
|
+
expect(onSave).toHaveBeenCalledWith(expect.objectContaining({
|
|
511
|
+
contentItems: expect.any(Array),
|
|
512
|
+
communicationStrategy: 'SINGLE_TEMPLATE',
|
|
513
|
+
}));
|
|
514
|
+
});
|
|
515
|
+
|
|
516
|
+
it('does not throw when onSave prop is null', () => {
|
|
517
|
+
renderCard({ onSave: null });
|
|
518
|
+
fireEvent.click(screen.getByText('Add creatives'));
|
|
519
|
+
expect(() => fireEvent.click(screen.getByTestId('flow-save'))).not.toThrow();
|
|
520
|
+
});
|
|
521
|
+
});
|
|
522
|
+
|
|
523
|
+
describe('handleClose', () => {
|
|
524
|
+
it('closes SlideBox on cancel', () => {
|
|
525
|
+
renderCard();
|
|
526
|
+
fireEvent.click(screen.getByText('Add creatives'));
|
|
527
|
+
expect(screen.getByTestId('slide-box')).toBeInTheDocument();
|
|
528
|
+
fireEvent.click(screen.getByTestId('flow-cancel'));
|
|
529
|
+
expect(screen.queryByTestId('slide-box')).not.toBeInTheDocument();
|
|
530
|
+
});
|
|
531
|
+
|
|
532
|
+
it('calls onCancel prop when SlideBox is closed', () => {
|
|
533
|
+
const onCancel = jest.fn();
|
|
534
|
+
renderCard({ onCancel });
|
|
535
|
+
fireEvent.click(screen.getByText('Add creatives'));
|
|
536
|
+
fireEvent.click(screen.getByTestId('flow-cancel'));
|
|
537
|
+
expect(onCancel).toHaveBeenCalledTimes(1);
|
|
538
|
+
});
|
|
539
|
+
|
|
540
|
+
it('does not throw when onCancel prop is null', () => {
|
|
541
|
+
renderCard({ onCancel: null });
|
|
542
|
+
fireEvent.click(screen.getByText('Add creatives'));
|
|
543
|
+
expect(() => fireEvent.click(screen.getByTestId('flow-cancel'))).not.toThrow();
|
|
544
|
+
});
|
|
545
|
+
});
|
|
546
|
+
|
|
547
|
+
describe('SlideBox header', () => {
|
|
548
|
+
it('renders Add message title in the slidebox header', () => {
|
|
549
|
+
renderCard();
|
|
550
|
+
fireEvent.click(screen.getByText('Add creatives'));
|
|
551
|
+
expect(screen.getByText('Add message')).toBeInTheDocument();
|
|
552
|
+
});
|
|
553
|
+
});
|
|
554
|
+
|
|
555
|
+
describe('optional chaining safety', () => {
|
|
556
|
+
it('renders without crash when config.features is absent', () => {
|
|
557
|
+
renderCard({
|
|
558
|
+
config: { consumer: 'campaigns', mode: 'create' },
|
|
559
|
+
initialData: makeSavedData(),
|
|
560
|
+
});
|
|
561
|
+
expect(screen.getByText('Message:')).toBeInTheDocument();
|
|
562
|
+
});
|
|
563
|
+
|
|
564
|
+
it('renders without crash when deliverySetting is absent from savedData', () => {
|
|
565
|
+
renderCard({
|
|
566
|
+
initialData: {
|
|
567
|
+
contentItems: [{ channel: 'SMS', templateData: {} }],
|
|
568
|
+
communicationStrategy: 'SINGLE_TEMPLATE',
|
|
569
|
+
dynamicControls: {},
|
|
570
|
+
},
|
|
571
|
+
});
|
|
572
|
+
expect(screen.getByText('Message:')).toBeInTheDocument();
|
|
573
|
+
});
|
|
574
|
+
|
|
575
|
+
it('renders without crash when channelSetting is absent inside deliverySetting', () => {
|
|
576
|
+
renderCard({
|
|
577
|
+
initialData: {
|
|
578
|
+
contentItems: [{ channel: 'SMS', templateData: {} }],
|
|
579
|
+
communicationStrategy: 'SINGLE_TEMPLATE',
|
|
580
|
+
deliverySetting: {},
|
|
581
|
+
dynamicControls: {},
|
|
582
|
+
},
|
|
583
|
+
});
|
|
584
|
+
expect(screen.getByText('Message:')).toBeInTheDocument();
|
|
585
|
+
});
|
|
586
|
+
|
|
587
|
+
it('treats contentItems=[] the same as no initialData (renders empty state)', () => {
|
|
588
|
+
renderCard({
|
|
589
|
+
initialData: {
|
|
590
|
+
contentItems: [],
|
|
591
|
+
communicationStrategy: 'SINGLE_TEMPLATE',
|
|
592
|
+
dynamicControls: {},
|
|
593
|
+
},
|
|
594
|
+
});
|
|
595
|
+
expect(screen.getByText('Add creatives')).toBeInTheDocument();
|
|
596
|
+
});
|
|
597
|
+
|
|
598
|
+
it('renders without crash when dynamicControls is absent from savedData', () => {
|
|
599
|
+
renderCard({
|
|
600
|
+
initialData: {
|
|
601
|
+
contentItems: [{ channel: 'SMS', templateData: {} }],
|
|
602
|
+
communicationStrategy: 'SINGLE_TEMPLATE',
|
|
603
|
+
deliverySetting: { channelSetting: {} },
|
|
604
|
+
},
|
|
605
|
+
});
|
|
606
|
+
expect(screen.getByText('Message:')).toBeInTheDocument();
|
|
607
|
+
});
|
|
608
|
+
|
|
609
|
+
it('renders without crash when firstItem.channel is undefined', () => {
|
|
610
|
+
renderCard({
|
|
611
|
+
initialData: makeSavedData({
|
|
612
|
+
contentItems: [{ templateData: {} }],
|
|
613
|
+
deliverySetting: { channelSetting: {} },
|
|
614
|
+
}),
|
|
615
|
+
});
|
|
616
|
+
expect(screen.getByText('Message:')).toBeInTheDocument();
|
|
617
|
+
});
|
|
618
|
+
});
|
|
619
|
+
});
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
|
|
3
|
+
let capturedProps = {};
|
|
4
|
+
|
|
5
|
+
jest.mock('../index', () => ({
|
|
6
|
+
CommunicationFlowCard: function MockCommunicationFlowCard(props) {
|
|
7
|
+
capturedProps = props;
|
|
8
|
+
return <div data-testid="comm-flow-card" />;
|
|
9
|
+
},
|
|
10
|
+
}));
|
|
11
|
+
|
|
12
|
+
import { render, screen } from '@testing-library/react';
|
|
13
|
+
import '@testing-library/jest-dom';
|
|
14
|
+
import CommunicationFlowDemoPage from '../DemoPage';
|
|
15
|
+
|
|
16
|
+
beforeEach(() => {
|
|
17
|
+
capturedProps = {};
|
|
18
|
+
jest.spyOn(console, 'log').mockImplementation(() => {});
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
afterEach(() => {
|
|
22
|
+
jest.restoreAllMocks();
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
describe('CommunicationFlowDemoPage', () => {
|
|
26
|
+
it('renders without crashing', () => {
|
|
27
|
+
render(<CommunicationFlowDemoPage />);
|
|
28
|
+
expect(screen.getByTestId('comm-flow-card')).toBeInTheDocument();
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it('passes config with consumer PROGRAM and create mode', () => {
|
|
32
|
+
render(<CommunicationFlowDemoPage />);
|
|
33
|
+
expect(capturedProps.config.consumer).toBe('PROGRAM');
|
|
34
|
+
expect(capturedProps.config.mode).toBe('create');
|
|
35
|
+
expect(capturedProps.config.useCCS).toBe(true);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it('passes config.features with the expected shape', () => {
|
|
39
|
+
render(<CommunicationFlowDemoPage />);
|
|
40
|
+
const { features } = capturedProps.config;
|
|
41
|
+
expect(features.messageTypeData).toEqual({ required: false });
|
|
42
|
+
expect(features.communicationStrategyData).toEqual({ required: true, disabled: false });
|
|
43
|
+
expect(features.contentTemplateData).toEqual({
|
|
44
|
+
required: true,
|
|
45
|
+
channelsToHide: [],
|
|
46
|
+
channelsToDisable: [],
|
|
47
|
+
});
|
|
48
|
+
expect(features.incentivesData).toEqual({ required: false });
|
|
49
|
+
expect(features.deliverySettingsData).toEqual({ required: false, deliverySettings: [{}] });
|
|
50
|
+
expect(features.showDynamicControls).toBe(true);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it('passes config.context with orgUnitId and campaignId', () => {
|
|
54
|
+
render(<CommunicationFlowDemoPage />);
|
|
55
|
+
expect(capturedProps.config.context).toEqual({ orgUnitId: '1231', campaignId: '456789' });
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it('passes an onSave function prop', () => {
|
|
59
|
+
render(<CommunicationFlowDemoPage />);
|
|
60
|
+
expect(typeof capturedProps.onSave).toBe('function');
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it('onSave logs the data via console.log', () => {
|
|
64
|
+
render(<CommunicationFlowDemoPage />);
|
|
65
|
+
const sampleData = { channel: 'SMS', contentItems: [] };
|
|
66
|
+
capturedProps.onSave(sampleData);
|
|
67
|
+
expect(console.log).toHaveBeenCalledWith('Save called with data:', sampleData);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it('renders without a wrapper div with inline styles', () => {
|
|
71
|
+
const { container } = render(<CommunicationFlowDemoPage />);
|
|
72
|
+
const wrapper = container.firstChild;
|
|
73
|
+
expect(wrapper.style.padding).toBe('');
|
|
74
|
+
expect(wrapper.style.maxWidth).toBe('');
|
|
75
|
+
expect(wrapper.style.margin).toBe('');
|
|
76
|
+
});
|
|
77
|
+
});
|