@capillarytech/creatives-library 8.0.316-alpha.3 → 8.0.316

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@capillarytech/creatives-library",
3
3
  "author": "meharaj",
4
- "version": "8.0.316-alpha.3",
4
+ "version": "8.0.316",
5
5
  "description": "Capillary creatives ui",
6
6
  "main": "./index.js",
7
7
  "module": "./index.es.js",
@@ -12,6 +12,52 @@ import CapAskAira from '@capillarytech/cap-ui-library/CapAskAira';
12
12
  import { MESSAGE_MAX_LENGTH, SHOW_CHARACTER_COUNT } from '../../constants';
13
13
  import { useAiraTriggerPosition } from '../hooks/useAiraTriggerPosition';
14
14
 
15
+ const InputWrapper = ({
16
+ children,
17
+ inputRef,
18
+ error,
19
+ value,
20
+ onChange,
21
+ isAiContentBotDisabled,
22
+ }) => {
23
+ const wrapperRef = useRef(null);
24
+ const airaRootStyle = useAiraTriggerPosition({ wrapperRef, inputRef, error });
25
+
26
+ return (
27
+ <div className="webpush-message-input-wrapper" ref={wrapperRef}>
28
+ {children}
29
+ {!isAiContentBotDisabled && (
30
+ <CapAskAira.ContentGenerationBot
31
+ text={value || ''}
32
+ setText={(text) => onChange(text)}
33
+ iconPlacement="float-br"
34
+ iconSize="1.6rem"
35
+ rootStyle={airaRootStyle}
36
+ />
37
+ )}
38
+ </div>
39
+ );
40
+ };
41
+
42
+ InputWrapper.propTypes = {
43
+ children: PropTypes.node.isRequired,
44
+ inputRef: PropTypes.oneOfType([
45
+ PropTypes.shape({ current: PropTypes.any }),
46
+ PropTypes.func,
47
+ ]),
48
+ error: PropTypes.string,
49
+ value: PropTypes.string,
50
+ onChange: PropTypes.func.isRequired,
51
+ isAiContentBotDisabled: PropTypes.bool,
52
+ };
53
+
54
+ InputWrapper.defaultProps = {
55
+ inputRef: null,
56
+ error: '',
57
+ value: '',
58
+ isAiContentBotDisabled: false,
59
+ };
60
+
15
61
  /**
16
62
  * MessageSection component - Message textarea with tags, emoji picker, and character count
17
63
  */
@@ -27,13 +73,6 @@ export const MessageSection = ({
27
73
  handleMessageTextAreaRef,
28
74
  isAiContentBotDisabled,
29
75
  }) => {
30
- const messageInputWrapperRef = useRef(null);
31
- const airaRootStyle = useAiraTriggerPosition({
32
- wrapperRef: messageInputWrapperRef,
33
- inputRef: messageTextAreaRef,
34
- error,
35
- });
36
-
37
76
  const renderCharacterCount = () => {
38
77
  if (!SHOW_CHARACTER_COUNT) return null;
39
78
 
@@ -60,7 +99,13 @@ export const MessageSection = ({
60
99
  <CapHeading type="h4" className="webpush-message">
61
100
  <FormattedMessage {...messages.message} />
62
101
  </CapHeading>
63
- <div className="webpush-message-input-wrapper" ref={messageInputWrapperRef}>
102
+ <InputWrapper
103
+ inputRef={messageTextAreaRef}
104
+ error={error}
105
+ value={value}
106
+ onChange={onChange}
107
+ isAiContentBotDisabled={isAiContentBotDisabled}
108
+ >
64
109
  <CapEmojiPicker.Wrapper
65
110
  value={value}
66
111
  onChange={onChange}
@@ -84,16 +129,7 @@ export const MessageSection = ({
84
129
  }
85
130
  />
86
131
  </CapEmojiPicker.Wrapper>
87
- {!isAiContentBotDisabled && (
88
- <CapAskAira.ContentGenerationBot
89
- text={value || ''}
90
- setText={(text) => onChange(text)}
91
- iconPlacement="float-br"
92
- iconSize="1.6rem"
93
- rootStyle={airaRootStyle}
94
- />
95
- )}
96
- </div>
132
+ </InputWrapper>
97
133
  {renderCharacterCount()}
98
134
  </CapRow>
99
135
  <CapDivider className="webpush-message-divider" />
@@ -318,5 +318,33 @@ describe('MessageSection', () => {
318
318
  expect(textArea.prop('value')).toBe(longValue);
319
319
  });
320
320
  });
321
+
322
+ describe('InputWrapper', () => {
323
+ it('should render the wrapper div with correct class', () => {
324
+ const wrapper = mountWithIntl(<MessageSection {...defaultProps} />);
325
+ const inputWrapper = wrapper.find('.webpush-message-input-wrapper');
326
+ expect(inputWrapper.exists()).toBe(true);
327
+ });
328
+
329
+ it('should render children inside InputWrapper', () => {
330
+ const wrapper = mountWithIntl(<MessageSection {...defaultProps} />);
331
+ const inputWrapper = wrapper.find('.webpush-message-input-wrapper');
332
+ expect(inputWrapper.find(CapEmojiPicker.Wrapper).exists()).toBe(true);
333
+ });
334
+
335
+ it('should not render aiRA bot inside InputWrapper when disabled', () => {
336
+ const wrapper = mountWithIntl(<MessageSection {...defaultProps} isAiContentBotDisabled />);
337
+ const inputWrapper = wrapper.find('.webpush-message-input-wrapper');
338
+ expect(inputWrapper.find(CapAskAira.ContentGenerationBot).exists()).toBe(false);
339
+ });
340
+
341
+ it('should render aiRA bot inside InputWrapper when enabled', () => {
342
+ const wrapper = mountWithIntl(
343
+ <MessageSection {...defaultProps} isAiContentBotDisabled={false} />
344
+ );
345
+ const inputWrapper = wrapper.find('.webpush-message-input-wrapper');
346
+ expect(inputWrapper.find(CapAskAira.ContentGenerationBot).exists()).toBe(true);
347
+ });
348
+ });
321
349
  });
322
350
 
@@ -18,8 +18,12 @@ exports[`MessageSection Rendering should render correctly with default props 1`]
18
18
  values={Object {}}
19
19
  />
20
20
  </CapHeading>
21
- <div
22
- className="webpush-message-input-wrapper"
21
+ <InputWrapper
22
+ error=""
23
+ inputRef={null}
24
+ isAiContentBotDisabled={true}
25
+ onChange={[MockFunction]}
26
+ value="Test Message"
23
27
  >
24
28
  <InjectIntl(Wrapper)
25
29
  onChange={[MockFunction]}
@@ -43,7 +47,7 @@ exports[`MessageSection Rendering should render correctly with default props 1`]
43
47
  value="Test Message"
44
48
  />
45
49
  </InjectIntl(Wrapper)>
46
- </div>
50
+ </InputWrapper>
47
51
  <CapLabel
48
52
  className="webpush-character-count"
49
53
  type="label2"
@@ -2957,12 +2957,13 @@ const isAuthenticationTemplate = isEqual(templateCategory, WHATSAPP_CATEGORIES.a
2957
2957
  };
2958
2958
 
2959
2959
  const isEditDoneDisabled = () => {
2960
+ const isBlankInput = (inputValue) => !String(inputValue ?? '').trim();
2960
2961
  let carouselDisableCheck = false;
2961
2962
  if (isMediaTypeCarousel) {
2962
2963
  carouselDisableCheck = carouselData.some((data) => {
2963
2964
  return (
2964
2965
  data.carouselTagValidationErr ||
2965
- Object.values(data.varMap).some((inputValue) => inputValue === "") ||
2966
+ Object.values(data.varMap).some((inputValue) => isBlankInput(inputValue)) ||
2966
2967
  computeTextLength(CAROUSEL_TEXT, data) > TEMPLATE_MESSAGE_MAX_LENGTH ||
2967
2968
  (carouselMediaType === IMAGE.toLowerCase() && !data.imageSrc) ||
2968
2969
  (carouselMediaType === VIDEO.toLowerCase() && !data.videoSrc) ||
@@ -2972,8 +2973,8 @@ const isAuthenticationTemplate = isEqual(templateCategory, WHATSAPP_CATEGORIES.a
2972
2973
  }
2973
2974
  return (isTagValidationError ||
2974
2975
  isHeaderTagValidationError ||
2975
- Object.values(varMap).some((inputValue) => inputValue === "") ||
2976
- Object.values(headerVarMappedData).some((inputValue) => inputValue === "") ||
2976
+ Object.values(varMap).some((inputValue) => isBlankInput(inputValue)) ||
2977
+ Object.values(headerVarMappedData).some((inputValue) => isBlankInput(inputValue)) ||
2977
2978
  computeTextLength(MESSAGE_TEXT) > TEMPLATE_MESSAGE_MAX_LENGTH ||
2978
2979
  computeTextLength(HEADER_TEXT) > TEMPLATE_HEADER_MAX_LENGTH ||
2979
2980
  (isBtnTypeCta && ctaData.some((btn) => btn?.url?.includes("{{1}}"))) || isMediatypeValid()) || carouselDisableCheck;
@@ -1354,3 +1354,175 @@ describe('Haptic carousel file handle mapping', () => {
1354
1354
  expect(cd[0].karixFileHandle).toBe('');
1355
1355
  });
1356
1356
  });
1357
+
1358
+ describe('WhatsApp edit Done button whitespace validation', () => {
1359
+ let renderedComponent;
1360
+
1361
+ const createWhatsappTemplate = jest.fn();
1362
+ const getFormData = jest.fn();
1363
+ const clearCreateResponse = jest.fn();
1364
+ const getTemplateDetails = jest.fn();
1365
+ const resetEditTemplate = jest.fn();
1366
+ const fetchSchemaForEntity = jest.fn();
1367
+ const handleClose = jest.fn();
1368
+ const onCreateComplete = jest.fn();
1369
+ const formatMessage = jest.fn();
1370
+ const getMetaTags = jest.fn();
1371
+ const resetMetaTags = jest.fn();
1372
+
1373
+ const mountEditFlow = (editData, accountData = mockData.accountData1) => {
1374
+ renderedComponent = mountWithIntl(
1375
+ <Provider store={store}>
1376
+ <Whatsapp
1377
+ actions={{
1378
+ createWhatsappTemplate,
1379
+ clearCreateResponse,
1380
+ getTemplateDetails,
1381
+ resetEditTemplate,
1382
+ getMetaTags,
1383
+ resetMetaTags,
1384
+ }}
1385
+ globalActions={{ fetchSchemaForEntity }}
1386
+ onCreateComplete={onCreateComplete}
1387
+ handleClose={handleClose}
1388
+ getFormData={getFormData}
1389
+ params={{ id: editData?.templateDetails?._id || 'edit-whitespace-id' }}
1390
+ editData={editData}
1391
+ accountData={accountData}
1392
+ location={mockData.location}
1393
+ intl={{ formatMessage }}
1394
+ isFullMode={false}
1395
+ isEditFlow
1396
+ loadingTags={false}
1397
+ metaEntities={[]}
1398
+ getDefaultTags
1399
+ Templates={{
1400
+ senderDetails: {
1401
+ status: 'SUCCESS',
1402
+ domainProperties: [
1403
+ {
1404
+ domainProperties: {
1405
+ connectionProperties: {
1406
+ sourceAccountIdentifier: '2000222347',
1407
+ baseUrl: 'https://api.gupshup.io/whatsapp/v1',
1408
+ },
1409
+ },
1410
+ },
1411
+ ],
1412
+ },
1413
+ }}
1414
+ />
1415
+ </Provider>,
1416
+ );
1417
+ };
1418
+
1419
+ const getDoneButton = () => renderedComponent.find('CapButton.whatsapp-create-btn').at(0);
1420
+
1421
+ const getFirstVariableTextarea = () => renderedComponent
1422
+ .find('textarea.TextArea__StyledTextArea-sc-177dfyt-2')
1423
+ .at(0);
1424
+
1425
+ it('keeps Done disabled for whitespace-only message variable', () => {
1426
+ const editData = {
1427
+ templateDetails: {
1428
+ _id: 'edit-msg-whitespace',
1429
+ name: 'msg_whitespace',
1430
+ type: 'WHATSAPP',
1431
+ versions: {
1432
+ base: {
1433
+ content: {
1434
+ whatsapp: {
1435
+ status: 'approved',
1436
+ category: 'ALERT_UPDATE',
1437
+ mediaType: 'TEXT',
1438
+ buttonType: 'NONE',
1439
+ varMapped: {},
1440
+ languages: [{ language: 'en', content: 'Your code is {{1}}' }],
1441
+ },
1442
+ },
1443
+ },
1444
+ },
1445
+ },
1446
+ };
1447
+
1448
+ mountEditFlow(editData);
1449
+ getFirstVariableTextarea().simulate('change', { target: { value: ' ', id: '{{1}}_3' } });
1450
+ renderedComponent.update();
1451
+
1452
+ expect(getDoneButton().props().disabled).toBe(true);
1453
+ });
1454
+
1455
+ it('keeps Done disabled for whitespace-only header variable', () => {
1456
+ const editData = {
1457
+ templateDetails: {
1458
+ _id: 'edit-header-whitespace',
1459
+ name: 'header_whitespace',
1460
+ type: 'WHATSAPP',
1461
+ versions: {
1462
+ base: {
1463
+ content: {
1464
+ whatsapp: {
1465
+ status: 'approved',
1466
+ category: 'ALERT_UPDATE',
1467
+ mediaType: 'TEXT',
1468
+ buttonType: 'NONE',
1469
+ varMapped: {},
1470
+ whatsappMedia: {
1471
+ header: 'Hi {{1}}',
1472
+ footer: '',
1473
+ headerVarMapped: {},
1474
+ },
1475
+ languages: [{ language: 'en', content: 'Simple message without vars' }],
1476
+ },
1477
+ },
1478
+ },
1479
+ },
1480
+ },
1481
+ };
1482
+
1483
+ mountEditFlow(editData);
1484
+ getFirstVariableTextarea().simulate('change', { target: { value: ' ', id: '{{1}}_1' } });
1485
+ renderedComponent.update();
1486
+
1487
+ expect(getDoneButton().props().disabled).toBe(true);
1488
+ });
1489
+
1490
+ it('keeps Done disabled for whitespace-only carousel variable', () => {
1491
+ const editData = {
1492
+ templateDetails: {
1493
+ _id: 'edit-carousel-whitespace',
1494
+ name: 'carousel_whitespace',
1495
+ type: 'WHATSAPP',
1496
+ versions: {
1497
+ base: {
1498
+ content: {
1499
+ whatsapp: {
1500
+ status: 'approved',
1501
+ category: 'MARKETING',
1502
+ mediaType: 'CAROUSEL',
1503
+ buttonType: 'NONE',
1504
+ varMapped: {},
1505
+ languages: [{ language: 'en', content: 'Main message' }],
1506
+ carouselData: [
1507
+ {
1508
+ mediaType: 'image',
1509
+ imageUrl: 'https://cdn.example.com/card.jpg',
1510
+ bodyText: 'Card body {{1}}',
1511
+ varMap: {},
1512
+ buttons: [],
1513
+ },
1514
+ ],
1515
+ },
1516
+ },
1517
+ },
1518
+ },
1519
+ },
1520
+ };
1521
+
1522
+ mountEditFlow(editData, mockData.accountData2);
1523
+ getFirstVariableTextarea().simulate('change', { target: { value: ' ', id: '{{1}}_2' } });
1524
+ renderedComponent.update();
1525
+
1526
+ expect(getDoneButton().props().disabled).toBe(true);
1527
+ });
1528
+ });