@capillarytech/creatives-library 8.0.317 → 8.0.319

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 (38) hide show
  1. package/constants/unified.js +1 -0
  2. package/package.json +1 -1
  3. package/services/api.js +6 -0
  4. package/services/tests/api.test.js +7 -0
  5. package/utils/common.js +6 -1
  6. package/v2Containers/CommunicationFlow/CommunicationFlow.js +291 -0
  7. package/v2Containers/CommunicationFlow/CommunicationFlow.scss +25 -0
  8. package/v2Containers/CommunicationFlow/Tests/CommunicationFlow.test.js +255 -0
  9. package/v2Containers/CommunicationFlow/constants.js +200 -0
  10. package/v2Containers/CommunicationFlow/index.js +102 -0
  11. package/v2Containers/CommunicationFlow/messages.js +346 -0
  12. package/v2Containers/CommunicationFlow/steps/ChannelSelectionStep/ChannelSelectionStep.js +522 -0
  13. package/v2Containers/CommunicationFlow/steps/ChannelSelectionStep/ChannelSelectionStep.scss +170 -0
  14. package/v2Containers/CommunicationFlow/steps/ChannelSelectionStep/Tests/ChannelSelectionStep.test.js +796 -0
  15. package/v2Containers/CommunicationFlow/steps/ChannelSelectionStep/index.js +5 -0
  16. package/v2Containers/CommunicationFlow/steps/CommunicationStrategyStep/CommunicationStrategyStep.js +95 -0
  17. package/v2Containers/CommunicationFlow/steps/CommunicationStrategyStep/Tests/CommunicationStrategyStep.test.js +133 -0
  18. package/v2Containers/CommunicationFlow/steps/CommunicationStrategyStep/index.js +5 -0
  19. package/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/DeliverySettingsSection.js +289 -0
  20. package/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/DeliverySettingsSection.scss +70 -0
  21. package/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/SenderDetails.js +319 -0
  22. package/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/SenderDetails.scss +69 -0
  23. package/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/Tests/DeliverySettingsSection.test.js +616 -0
  24. package/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/Tests/SenderDetails.test.js +577 -0
  25. package/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/Tests/deliverySettingsConfig.test.js +1111 -0
  26. package/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/deliverySettingsConfig.js +696 -0
  27. package/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/index.js +7 -0
  28. package/v2Containers/CommunicationFlow/steps/DynamicControlsStep/DynamicControlsStep.js +102 -0
  29. package/v2Containers/CommunicationFlow/steps/DynamicControlsStep/DynamicControlsStep.scss +36 -0
  30. package/v2Containers/CommunicationFlow/steps/DynamicControlsStep/Tests/DynamicControlsStep.test.js +91 -0
  31. package/v2Containers/CommunicationFlow/steps/DynamicControlsStep/index.js +5 -0
  32. package/v2Containers/CommunicationFlow/steps/MessageTypeStep/MessageTypeStep.js +86 -0
  33. package/v2Containers/CommunicationFlow/steps/MessageTypeStep/Tests/MessageTypeStep.test.js +100 -0
  34. package/v2Containers/CommunicationFlow/steps/MessageTypeStep/index.js +5 -0
  35. package/v2Containers/CommunicationFlow/utils/getEnabledSteps.js +30 -0
  36. package/v2Containers/CreativesContainer/constants.js +3 -0
  37. package/v2Containers/CreativesContainer/index.js +1 -1
  38. package/v2Containers/Rcs/index.js +4 -2
@@ -0,0 +1,577 @@
1
+ import React from 'react';
2
+ import {
3
+ render, screen, waitFor, within,
4
+ } from '@testing-library/react';
5
+ import userEvent from '@testing-library/user-event';
6
+ import '@testing-library/jest-dom';
7
+ import { IntlProvider } from 'react-intl';
8
+ import SenderDetails, { parseSenderDetailsFromEntity } from '../SenderDetails';
9
+ import * as deliverySettingsConfig from '../deliverySettingsConfig';
10
+
11
+ const { parseEntityForDisplay } = deliverySettingsConfig;
12
+
13
+ /** Shared domainProperties entity shapes (see deliverySettingsConfig). */
14
+ const ENTITIES = {
15
+ smsOk: {
16
+ SMS: [
17
+ {
18
+ domainProperties: {
19
+ id: 'sms-dom-1',
20
+ domainName: 'SMS domain',
21
+ contactInfo: [
22
+ {
23
+ type: 'gsm_sender_id',
24
+ value: '+1002003001',
25
+ valid: true,
26
+ default: true,
27
+ },
28
+ ],
29
+ },
30
+ },
31
+ ],
32
+ },
33
+ smsTwoDomains: {
34
+ SMS: [
35
+ {
36
+ domainProperties: {
37
+ id: 'sms-a',
38
+ domainName: 'Gateway A',
39
+ contactInfo: [
40
+ {
41
+ type: 'gsm_sender_id', value: '+111', valid: true, default: true,
42
+ },
43
+ ],
44
+ },
45
+ },
46
+ {
47
+ domainProperties: {
48
+ id: 'sms-b',
49
+ domainName: 'Gateway B',
50
+ contactInfo: [
51
+ {
52
+ type: 'gsm_sender_id', value: '+222', valid: true, default: true,
53
+ },
54
+ ],
55
+ },
56
+ },
57
+ ],
58
+ },
59
+ smsTwoSenders: {
60
+ SMS: [
61
+ {
62
+ domainProperties: {
63
+ id: 'sms-dom-1',
64
+ domainName: 'SMS domain',
65
+ contactInfo: [
66
+ {
67
+ type: 'gsm_sender_id', value: '+1002003001', valid: true, default: true,
68
+ },
69
+ {
70
+ type: 'gsm_sender_id', value: '+1002003002', valid: true, default: false,
71
+ },
72
+ ],
73
+ },
74
+ },
75
+ ],
76
+ },
77
+ emailTwoDomains: {
78
+ EMAIL: [
79
+ {
80
+ domainProperties: {
81
+ id: 'em-a',
82
+ domainName: 'Mail Alpha',
83
+ contactInfo: [
84
+ {
85
+ type: 'sender_id',
86
+ value: 'alpha@brand.com',
87
+ label: 'Alpha Name',
88
+ valid: true,
89
+ default: true,
90
+ },
91
+ {
92
+ type: 'reply_to_id', value: 'reply-a@brand.com', valid: true, default: true,
93
+ },
94
+ ],
95
+ },
96
+ },
97
+ {
98
+ domainProperties: {
99
+ id: 'em-b',
100
+ domainName: 'Mail Beta',
101
+ contactInfo: [
102
+ {
103
+ type: 'sender_id',
104
+ value: 'beta@other.com',
105
+ label: 'Beta Name',
106
+ valid: true,
107
+ default: true,
108
+ },
109
+ {
110
+ type: 'reply_to_id', value: 'reply-b@other.com', valid: true, default: true,
111
+ },
112
+ ],
113
+ },
114
+ },
115
+ ],
116
+ },
117
+ whatsappWaba: {
118
+ WHATSAPP: [
119
+ {
120
+ domainProperties: {
121
+ id: 'wa-dom-99',
122
+ domainName: 'WA Business',
123
+ connectionProperties: { sourceAccountIdentifier: 'waba-xyz' },
124
+ contactInfo: [
125
+ {
126
+ type: 'gsm_sender_id', value: '+91900111222', valid: true, default: true,
127
+ },
128
+ {
129
+ type: 'gsm_sender_id', value: '+91900111333', valid: true, default: false,
130
+ },
131
+ ],
132
+ },
133
+ },
134
+ ],
135
+ },
136
+ rcsBrand: {
137
+ RCS: [
138
+ {
139
+ domainProperties: {
140
+ id: 'rcs-1',
141
+ domainName: 'RCS Brand Co',
142
+ contactInfo: [],
143
+ },
144
+ },
145
+ ],
146
+ },
147
+ };
148
+
149
+ function defaultSenderProps(override = {}) {
150
+ const { onClose: overrideOnClose, onSave: overrideOnSave, ...rest } = override;
151
+ return {
152
+ show: true,
153
+ channels: ['SMS'],
154
+ preloadedDomainProperties: ENTITIES.smsOk,
155
+ savedFieldValues: null,
156
+ whatsappSourceAccountId: '',
157
+ whatsappAccountName: '',
158
+ ...rest,
159
+ onClose: overrideOnClose ?? jest.fn(),
160
+ onSave: overrideOnSave ?? jest.fn(),
161
+ };
162
+ }
163
+
164
+ function renderSenderDetails(override = {}) {
165
+ const props = defaultSenderProps(override);
166
+ const { onClose, onSave } = props;
167
+ const view = render(
168
+ <IntlProvider locale="en" messages={{}} defaultLocale="en">
169
+ <SenderDetails {...props} />
170
+ </IntlProvider>,
171
+ );
172
+ return { ...view, onClose, onSave };
173
+ }
174
+
175
+ async function openSelectAndChoose(root, comboIndex, optionText) {
176
+ const combos = within(root).getAllByRole('combobox');
177
+ await userEvent.click(combos[comboIndex]);
178
+ await waitFor(() => expect(screen.getByRole('listbox')).toBeInTheDocument());
179
+ await userEvent.click(within(screen.getByRole('listbox')).getByText(optionText));
180
+ }
181
+
182
+ describe('SenderDetails', () => {
183
+ it('renders SMS fields when domain properties are preloaded', async () => {
184
+ renderSenderDetails();
185
+ const root = document.querySelector('.sender-details');
186
+ expect(root).toBeInTheDocument();
187
+ await waitFor(() => {
188
+ expect(within(root).getByText('Sender details')).toBeInTheDocument();
189
+ expect(within(root).getByText('SMS Domain')).toBeInTheDocument();
190
+ expect(within(root).getByText('Sender ID')).toBeInTheDocument();
191
+ expect(within(root).getByText('Save changes')).toBeInTheDocument();
192
+ });
193
+ });
194
+
195
+ it('calls onClose when the back control is used', async () => {
196
+ const { onClose } = renderSenderDetails();
197
+ const root = document.querySelector('.sender-details');
198
+ await waitFor(() => {
199
+ expect(within(root).getByLabelText('Back')).toBeInTheDocument();
200
+ });
201
+ await userEvent.click(within(root).getByLabelText('Back'));
202
+ expect(onClose).toHaveBeenCalledTimes(1);
203
+ });
204
+
205
+ it('shows domain gateway error when no SMS domains exist', async () => {
206
+ renderSenderDetails({
207
+ preloadedDomainProperties: { SMS: [] },
208
+ });
209
+ const root = document.querySelector('.sender-details');
210
+ await waitFor(() => {
211
+ expect(
212
+ within(root).getByText(/Domain gateway id is not found for the selected channel/i),
213
+ ).toBeInTheDocument();
214
+ });
215
+ });
216
+
217
+ it('shows sender not configured when the domain has no sender options', async () => {
218
+ renderSenderDetails({
219
+ preloadedDomainProperties: {
220
+ SMS: [
221
+ {
222
+ domainProperties: {
223
+ id: 'sms-empty-senders',
224
+ domainName: 'Gateway without senders',
225
+ contactInfo: [],
226
+ },
227
+ },
228
+ ],
229
+ },
230
+ });
231
+ const root = document.querySelector('.sender-details');
232
+ await waitFor(() => {
233
+ expect(
234
+ within(root).getByText(/Selected domain gateway id is not correct/i),
235
+ ).toBeInTheDocument();
236
+ });
237
+ });
238
+
239
+ it('stays empty of field labels when slidebox is open but preloaded data is missing', () => {
240
+ renderSenderDetails({
241
+ preloadedDomainProperties: null,
242
+ });
243
+ const root = document.querySelector('.sender-details');
244
+ expect(root).toBeInTheDocument();
245
+ expect(screen.queryByText('SMS Domain')).not.toBeInTheDocument();
246
+ });
247
+
248
+ it('applies savedFieldValues over API defaults for SMS sender', async () => {
249
+ renderSenderDetails({
250
+ preloadedDomainProperties: ENTITIES.smsTwoSenders,
251
+ savedFieldValues: { smsSenderId: '+1002003002' },
252
+ });
253
+ const root = document.querySelector('.sender-details');
254
+ await waitFor(() => {
255
+ expect(within(root).getByText('+1002003002')).toBeInTheDocument();
256
+ });
257
+ });
258
+
259
+ it('saves SMS after changing sender and invokes onSave then onClose', async () => {
260
+ const { onSave, onClose } = renderSenderDetails({
261
+ preloadedDomainProperties: ENTITIES.smsTwoSenders,
262
+ });
263
+ const root = document.querySelector('.sender-details');
264
+ await waitFor(() => expect(within(root).getByText('Sender ID')).toBeInTheDocument());
265
+
266
+ const combos = within(root).getAllByRole('combobox');
267
+ await userEvent.click(combos[1]);
268
+ await waitFor(() => expect(screen.getByRole('listbox')).toBeInTheDocument());
269
+ await userEvent.click(within(screen.getByRole('listbox')).getByText('+1002003002'));
270
+
271
+ const saveBtn = within(root).getByRole('button', { name: /save changes/i });
272
+ await waitFor(() => expect(saveBtn).not.toBeDisabled());
273
+ await userEvent.click(saveBtn);
274
+
275
+ expect(onSave).toHaveBeenCalledTimes(1);
276
+ expect(onSave.mock.calls[0][0]).toMatchObject({
277
+ smsSenderId: '+1002003002',
278
+ });
279
+ expect(onClose).toHaveBeenCalled();
280
+ });
281
+
282
+ it('resets SMS domain when multiple gateways exist', async () => {
283
+ renderSenderDetails({
284
+ preloadedDomainProperties: ENTITIES.smsTwoDomains,
285
+ });
286
+ const root = document.querySelector('.sender-details');
287
+ await waitFor(() => expect(within(root).getByText('Gateway A')).toBeInTheDocument());
288
+
289
+ await openSelectAndChoose(root, 0, 'Gateway B');
290
+ await waitFor(() => expect(within(root).getByText('+222')).toBeInTheDocument());
291
+
292
+ const resetLinks = within(root).getAllByTitle('Reset');
293
+ await userEvent.click(resetLinks[0]);
294
+
295
+ await waitFor(() => {
296
+ expect(within(root).getByText('Gateway A')).toBeInTheDocument();
297
+ expect(within(root).getByText('+111')).toBeInTheDocument();
298
+ });
299
+ });
300
+
301
+ it('updates email sender fields when email domain changes', async () => {
302
+ renderSenderDetails({
303
+ channels: ['EMAIL'],
304
+ preloadedDomainProperties: ENTITIES.emailTwoDomains,
305
+ });
306
+ const root = document.querySelector('.sender-details');
307
+ await waitFor(() => expect(within(root).getByText('Mail Alpha')).toBeInTheDocument());
308
+
309
+ await openSelectAndChoose(root, 0, 'Mail Beta');
310
+
311
+ await waitFor(() => {
312
+ expect(within(root).getByText('beta@other.com')).toBeInTheDocument();
313
+ });
314
+ });
315
+
316
+ it('resets email domain row back to the first gateway', async () => {
317
+ renderSenderDetails({
318
+ channels: ['EMAIL'],
319
+ preloadedDomainProperties: ENTITIES.emailTwoDomains,
320
+ });
321
+ const root = document.querySelector('.sender-details');
322
+ await waitFor(() => expect(within(root).getByText('Mail Alpha')).toBeInTheDocument());
323
+
324
+ await openSelectAndChoose(root, 0, 'Mail Beta');
325
+ await waitFor(() => expect(within(root).getByText('beta@other.com')).toBeInTheDocument());
326
+
327
+ const resetLinks = within(root).getAllByTitle('Reset');
328
+ await userEvent.click(resetLinks[0]);
329
+
330
+ await waitFor(() => {
331
+ expect(within(root).getByText('alpha@brand.com')).toBeInTheDocument();
332
+ });
333
+ });
334
+
335
+ it('resets SMS sender ID via generic reset branch', async () => {
336
+ renderSenderDetails({
337
+ preloadedDomainProperties: ENTITIES.smsTwoSenders,
338
+ });
339
+ const root = document.querySelector('.sender-details');
340
+ await waitFor(() => expect(within(root).getByText('+1002003001')).toBeInTheDocument());
341
+
342
+ const combos = within(root).getAllByRole('combobox');
343
+ await userEvent.click(combos[1]);
344
+ await waitFor(() => expect(screen.getByRole('listbox')).toBeInTheDocument());
345
+ await userEvent.click(within(screen.getByRole('listbox')).getByText('+1002003002'));
346
+
347
+ const resetLinks = within(root).getAllByTitle('Reset');
348
+ await userEvent.click(resetLinks[1]);
349
+
350
+ await waitFor(() => expect(within(root).getByText('+1002003001')).toBeInTheDocument());
351
+ });
352
+
353
+ it('persists whatsappDomainId on save when WABA matches template source id', async () => {
354
+ const { onSave } = renderSenderDetails({
355
+ channels: ['WHATSAPP'],
356
+ preloadedDomainProperties: ENTITIES.whatsappWaba,
357
+ whatsappSourceAccountId: 'waba-xyz',
358
+ whatsappAccountName: 'My Business',
359
+ });
360
+ const root = document.querySelector('.sender-details');
361
+ await waitFor(() => expect(within(root).getByText('My Business')).toBeInTheDocument());
362
+
363
+ const combos = within(root).getAllByRole('combobox');
364
+ await userEvent.click(combos[combos.length - 1]);
365
+ await waitFor(() => expect(screen.getByRole('listbox')).toBeInTheDocument());
366
+ await userEvent.click(within(screen.getByRole('listbox')).getByText('+91900111333'));
367
+
368
+ const saveBtn = within(root).getByRole('button', { name: /save changes/i });
369
+ await waitFor(() => expect(saveBtn).not.toBeDisabled());
370
+ await userEvent.click(saveBtn);
371
+
372
+ expect(onSave).toHaveBeenCalledWith(
373
+ expect.objectContaining({
374
+ whatsappDomainId: 'wa-dom-99',
375
+ whatsappSenderNumber: '+91900111333',
376
+ }),
377
+ );
378
+ });
379
+
380
+ it('shows RCS account name from preloaded entity', async () => {
381
+ renderSenderDetails({
382
+ channels: ['RCS'],
383
+ preloadedDomainProperties: ENTITIES.rcsBrand,
384
+ });
385
+ const root = document.querySelector('.sender-details');
386
+ await waitFor(() => {
387
+ expect(within(root).getByText('RCS Brand Co')).toBeInTheDocument();
388
+ });
389
+ });
390
+
391
+ it('exposes parseSenderDetailsFromEntity aligned with parseEntityForDisplay', () => {
392
+ expect(parseSenderDetailsFromEntity(ENTITIES.smsOk)).toEqual(
393
+ parseEntityForDisplay(ENTITIES.smsOk, {}),
394
+ );
395
+ });
396
+
397
+ it('renders nothing substantive when channels list is empty', async () => {
398
+ renderSenderDetails({
399
+ channels: [],
400
+ preloadedDomainProperties: ENTITIES.smsOk,
401
+ });
402
+ const root = document.querySelector('.sender-details');
403
+ expect(root).toBeInTheDocument();
404
+ await waitFor(() => {
405
+ expect(within(root).queryByText('SMS Domain')).not.toBeInTheDocument();
406
+ });
407
+ });
408
+
409
+ it('changing SMS domain auto-populates the sender ID (handleFieldChange cascade)', async () => {
410
+ // Covers lines 102-104: fieldKey === 'smsDomain' && entity → auto-populate smsSenderId
411
+ renderSenderDetails({
412
+ channels: ['SMS'],
413
+ preloadedDomainProperties: ENTITIES.smsTwoDomains,
414
+ });
415
+ const root = document.querySelector('.sender-details');
416
+ await waitFor(() => expect(within(root).getByText('Gateway A')).toBeInTheDocument());
417
+
418
+ const combos = within(root).getAllByRole('combobox');
419
+ await userEvent.click(combos[0]);
420
+ await waitFor(() => expect(screen.getByRole('listbox')).toBeInTheDocument());
421
+ await userEvent.click(within(screen.getByRole('listbox')).getByText('Gateway B'));
422
+
423
+ // The sender ID should now auto-update to Gateway B's sender
424
+ await waitFor(() => expect(within(root).getByText('+222')).toBeInTheDocument());
425
+ });
426
+
427
+ it('changing EMAIL domain cascades into sender ID, name, and reply-to fields', async () => {
428
+ // Covers lines 106-110: fieldKey === 'emailDomain' && entity → cascade update
429
+ renderSenderDetails({
430
+ channels: ['EMAIL'],
431
+ preloadedDomainProperties: ENTITIES.emailTwoDomains,
432
+ });
433
+ const root = document.querySelector('.sender-details');
434
+ await waitFor(() => expect(within(root).getByText('Mail Alpha')).toBeInTheDocument());
435
+
436
+ // Switch to Mail Beta domain
437
+ const combos = within(root).getAllByRole('combobox');
438
+ await userEvent.click(combos[0]);
439
+ await waitFor(() => expect(screen.getByRole('listbox')).toBeInTheDocument());
440
+ await userEvent.click(within(screen.getByRole('listbox')).getByText('Mail Beta'));
441
+
442
+ // Sender ID should auto-update to beta@other.com
443
+ await waitFor(() => expect(within(root).getByText('beta@other.com')).toBeInTheDocument());
444
+ });
445
+
446
+ it('handles WhatsApp domain derivation in handleSave with sourceAccountIdentifier connectionProperties', async () => {
447
+ // Covers lines 147-155: whatsappSourceAccountId && entity → derive whatsappDomainId
448
+ const onSave = jest.fn();
449
+ renderSenderDetails({
450
+ channels: ['WHATSAPP'],
451
+ preloadedDomainProperties: ENTITIES.whatsappWaba,
452
+ whatsappSourceAccountId: 'waba-xyz',
453
+ whatsappAccountName: 'WA Business',
454
+ onSave,
455
+ });
456
+ const root = document.querySelector('.sender-details');
457
+ await waitFor(() => expect(within(root).getByText('WA Business')).toBeInTheDocument());
458
+
459
+ // Change sender to enable save
460
+ const combos = within(root).getAllByRole('combobox');
461
+ const senderCombo = combos[combos.length - 1];
462
+ await userEvent.click(senderCombo);
463
+ await waitFor(() => expect(screen.getByRole('listbox')).toBeInTheDocument());
464
+ await userEvent.click(within(screen.getByRole('listbox')).getByText('+91900111333'));
465
+
466
+ const saveBtn = within(root).getByRole('button', { name: /save changes/i });
467
+ await waitFor(() => expect(saveBtn).not.toBeDisabled());
468
+ await userEvent.click(saveBtn);
469
+
470
+ // Should have called onSave with whatsappDomainId derived from the matched WABA domain
471
+ expect(onSave).toHaveBeenCalledWith(
472
+ expect.objectContaining({ whatsappDomainId: 'wa-dom-99' }),
473
+ );
474
+ });
475
+
476
+ it('resets viberSenderId via the else branch in handleReset (non-domain field)', async () => {
477
+ // Covers lines 136-138: else branch in handleReset for non-smsDomain/emailDomain fields
478
+ const viberEntity = {
479
+ VIBER: [{
480
+ id: 'vgw',
481
+ domainProperties: {
482
+ id: 0,
483
+ domainName: 'Viber GW',
484
+ contactInfo: [
485
+ { type: 'gsm_sender_id', value: '+viber-def', valid: true, default: true },
486
+ { type: 'gsm_sender_id', value: '+viber-alt', valid: true, default: false },
487
+ ],
488
+ },
489
+ }],
490
+ };
491
+ renderSenderDetails({
492
+ channels: ['VIBER'],
493
+ preloadedDomainProperties: viberEntity,
494
+ });
495
+ const root = document.querySelector('.sender-details');
496
+ await waitFor(() => expect(within(root).getByText('Sender ID')).toBeInTheDocument());
497
+
498
+ // Change to an alternate sender
499
+ const combos = within(root).getAllByRole('combobox');
500
+ const senderCombo = combos[combos.length - 1];
501
+ await userEvent.click(senderCombo);
502
+ await waitFor(() => expect(screen.getByRole('listbox')).toBeInTheDocument());
503
+ await userEvent.click(within(screen.getByRole('listbox')).getByText('+viber-alt'));
504
+
505
+ await waitFor(() => expect(within(root).getByText('+viber-alt')).toBeInTheDocument());
506
+
507
+ // Now click reset — for viberSenderId the else branch in handleReset is hit
508
+ // (viberSenderId is not smsDomain/emailDomain, so getDefaultValueForField returns '')
509
+ const resetLinks = within(root).getAllByTitle('Reset');
510
+ await userEvent.click(resetLinks[resetLinks.length - 1]);
511
+ // After reset, the field goes to the default value returned by getDefaultValueForField
512
+ // For viberSenderId this is '' (no specific default handler)
513
+ await waitFor(() => expect(within(root).queryByText('+viber-alt')).not.toBeInTheDocument());
514
+ });
515
+
516
+ it('renders a DISPLAY-type field as a read-only heading (no select or reset)', async () => {
517
+ // Covers line 227-231: type === FIELD_TYPE.DISPLAY rendering path
518
+ jest.spyOn(deliverySettingsConfig, 'getFieldsForChannels').mockReturnValue([
519
+ {
520
+ channel: 'EMAIL',
521
+ fieldKey: 'emailDomainDisplay',
522
+ messageKey: 'emailDomainLabel',
523
+ type: deliverySettingsConfig.FIELD_TYPE.DISPLAY,
524
+ getValue: () => 'Transactional domain',
525
+ getOptions: () => [],
526
+ },
527
+ ]);
528
+ renderSenderDetails({
529
+ preloadedDomainProperties: ENTITIES.smsOk,
530
+ });
531
+ const root = document.querySelector('.sender-details');
532
+ await waitFor(() => {
533
+ expect(within(root).getByText('Email Domain')).toBeInTheDocument();
534
+ });
535
+ // DISPLAY type should not show a combobox
536
+ expect(within(root).queryByRole('combobox')).not.toBeInTheDocument();
537
+ });
538
+
539
+ it('returns null (renders nothing) for a field with an unknown type', async () => {
540
+ // Covers line 271: return null fallthrough at end of renderField
541
+ jest.spyOn(deliverySettingsConfig, 'getFieldsForChannels').mockReturnValue([
542
+ {
543
+ channel: 'SMS',
544
+ fieldKey: 'unknownField',
545
+ messageKey: 'smsDomain',
546
+ type: 'not-a-real-type',
547
+ getValue: () => '',
548
+ getOptions: () => [],
549
+ },
550
+ ]);
551
+ renderSenderDetails({
552
+ preloadedDomainProperties: ENTITIES.smsOk,
553
+ });
554
+ const root = document.querySelector('.sender-details');
555
+ await waitFor(() => expect(within(root).getByText('Sender details')).toBeInTheDocument());
556
+ // No combobox should be rendered for an unknown field type
557
+ expect(within(root).queryByRole('combobox')).not.toBeInTheDocument();
558
+ });
559
+
560
+ it('reset link click when only one option available does not call handleReset (isSingleOption guard)', async () => {
561
+ // Covers line 259: onClick: if (!isSingleOption) handleReset(fieldKey) — single option branch
562
+ // Ensure no active spies from previous tests interfere
563
+ jest.restoreAllMocks();
564
+ renderSenderDetails({
565
+ preloadedDomainProperties: ENTITIES.smsOk,
566
+ });
567
+ const root = document.querySelector('.sender-details');
568
+ await waitFor(() => expect(within(root).getByText('Sender ID')).toBeInTheDocument());
569
+
570
+ // smsOk has exactly 1 sender option — clicking reset should be a no-op
571
+ const resetLinks = within(root).getAllByTitle('Reset');
572
+ const lastReset = resetLinks[resetLinks.length - 1];
573
+ await userEvent.click(lastReset);
574
+ // Value should remain the same (no state change)
575
+ await waitFor(() => expect(within(root).getByText('+1002003001')).toBeInTheDocument());
576
+ });
577
+ });