@capillarytech/creatives-library 9.0.2 → 9.0.4

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": "9.0.2",
4
+ "version": "9.0.4",
5
5
  "description": "Capillary creatives ui",
6
6
  "main": "./index.js",
7
7
  "module": "./index.es.js",
@@ -40,18 +40,15 @@ class NewCallTask extends React.Component { // eslint-disable-line react/prefer-
40
40
  this.insertAtCursor(`{{${data}}}`);
41
41
  }
42
42
 
43
- setInputRef = (textarea) => {
44
- this.textArea = textarea;
45
- }
46
-
47
43
  insertAtCursor = (myValue) => {
48
- this.textArea.focus();
49
- const textAreaRef = this.textArea.textAreaRef;
44
+ const textAreaRef = document.getElementById('new-call-task-message-body');
45
+ if (!textAreaRef) return;
46
+ textAreaRef.focus();
50
47
  let pos = textAreaRef.selectionEnd + myValue.length;
51
48
  //IE support
52
49
  let newMessage = textAreaRef.value + myValue;
53
50
  if (document.selection) {
54
- this.textArea.focus();
51
+ textAreaRef.focus();
55
52
  const sel = document.selection.createRange();
56
53
  sel.text = myValue;
57
54
  } else if (textAreaRef.selectionStart || textAreaRef.selectionStart === '0') { //MOZILLA and others
@@ -128,7 +125,7 @@ class NewCallTask extends React.Component { // eslint-disable-line react/prefer-
128
125
  injectedTags={injectedTags}
129
126
  />
130
127
  <CapInput.TextArea
131
- setInputRef={this.setInputRef}
128
+ id="new-call-task-message-body"
132
129
  label={formatMessage(messages.messageHeader)}
133
130
  onChange={this.updateMessageBody}
134
131
  value={messageBody}
@@ -0,0 +1,85 @@
1
+ import React from 'react';
2
+ import { configure, mount } from 'enzyme';
3
+ import Adapter from '@cfaester/enzyme-adapter-react-18';
4
+ import { IntlProvider } from 'react-intl';
5
+ import { act } from 'react-dom/test-utils';
6
+
7
+ // TagList is a redux-auth-wrapper-connected container that needs full app routing
8
+ // state to mount; CallTaskPreview is irrelevant to insertAtCursor. Stub both so we
9
+ // can mount the real NewCallTask and exercise its real insertAtCursor method.
10
+ jest.mock('../../../v2Containers/TagList', () => {
11
+ const Stub = () => null;
12
+ return { __esModule: true, default: Stub, TagList: Stub };
13
+ });
14
+ jest.mock('../../CallTaskPreview', () => {
15
+ const Stub = () => null;
16
+ return { __esModule: true, default: Stub };
17
+ });
18
+ // hasStore2DoorFeature() reads app/org config that isn't present in jsdom.
19
+ jest.mock('../../../utils/common', () => ({
20
+ ...jest.requireActual('../../../utils/common'),
21
+ hasStore2DoorFeature: () => false,
22
+ }));
23
+
24
+ // eslint-disable-next-line import/first
25
+ import NewCallTask from '../index';
26
+ // eslint-disable-next-line import/first
27
+ import mockData from './mockData';
28
+
29
+ configure({ adapter: new Adapter() });
30
+
31
+ let container;
32
+ let wrapper;
33
+
34
+ afterEach(() => {
35
+ if (wrapper) wrapper.unmount();
36
+ if (container) container.remove();
37
+ wrapper = undefined;
38
+ container = undefined;
39
+ });
40
+
41
+ // attachTo a real node in document.body so document.getElementById can find the
42
+ // antd-rendered <textarea> (enzyme `mount` is detached from `document` otherwise).
43
+ const mountNewCallTask = (extraProps = {}) => {
44
+ container = document.createElement('div');
45
+ document.body.appendChild(container);
46
+ wrapper = mount(
47
+ <IntlProvider locale="en">
48
+ <NewCallTask {...mockData} onCallTaskSubmit={jest.fn()} {...extraProps} />
49
+ </IntlProvider>,
50
+ { attachTo: container },
51
+ );
52
+ return wrapper;
53
+ };
54
+
55
+ // Regression coverage for the antd v3 -> v6 (cap-ui-library v6) migration: the lib
56
+ // dropped the `setInputRef` callback, so selecting a personalisation tag used to
57
+ // crash with "Cannot read properties of undefined (reading 'focus')". The fix
58
+ // resolves the DOM <textarea> via document.getElementById instead.
59
+ describe('NewCallTask insertAtCursor (antd v6 setInputRef removal)', () => {
60
+ it('forwards the id to a real DOM <textarea>', () => {
61
+ mountNewCallTask();
62
+ const textarea = document.getElementById('new-call-task-message-body');
63
+ expect(textarea).not.toBeNull();
64
+ expect(textarea.tagName).toBe('TEXTAREA');
65
+ });
66
+
67
+ it('inserts a personalisation tag at the caret without throwing', () => {
68
+ const wrapper = mountNewCallTask();
69
+ const textarea = document.getElementById('new-call-task-message-body');
70
+
71
+ // user typed "Hello world" and put the caret right after "Hello "
72
+ textarea.value = 'Hello world';
73
+ textarea.setSelectionRange(6, 6);
74
+
75
+ const instance = wrapper.find('NewCallTask').instance();
76
+ expect(() => {
77
+ act(() => {
78
+ instance.onTagSelect('firstName');
79
+ });
80
+ }).not.toThrow();
81
+
82
+ // tag spliced in at the caret, not appended blindly
83
+ expect(instance.state.messageBody).toBe('Hello {{firstName}}world');
84
+ });
85
+ });
@@ -890,10 +890,6 @@
890
890
  .sms-icon{
891
891
  left: 12% !important;
892
892
  }
893
-
894
- #mobilepush-preview .message-pop .app-header .app-header-right {
895
- margin-left: unset;
896
- }
897
893
  }
898
894
 
899
895
  #slidebox-container {
@@ -644,7 +644,7 @@ export class TemplatePreview extends React.Component { // eslint-disable-line re
644
644
  src={smsMobileAndroid}
645
645
  alt={this.props.intl.formatMessage(messages.previewGenerated)}
646
646
  />
647
- <CapRow useLegacy className="msg-container sms">
647
+ <CapRow className="msg-container sms">
648
648
  <CapRow useLegacy className={"message-pop sms"}>
649
649
  {content &&
650
650
  content.length &&
@@ -748,12 +748,12 @@ export class TemplatePreview extends React.Component { // eslint-disable-line re
748
748
  >
749
749
  {this.props.device === "android" ? (
750
750
  <CapRow className="message-pop align-left">
751
- <CapRow type='flex' align='middle' justify='space-between' className="app-header">
752
- <CapRow type='flex' align='middle' className="app-header-left">
751
+ <CapRow className="app-header">
752
+ <CapRow className="app-header-left">
753
753
  <span className="app-icon">{""}</span>
754
754
  <CapLabel className="title">{content.header}</CapLabel>
755
755
  </CapRow>
756
- <CapRow type='flex' align='middle' className="app-header-right">
756
+ <CapRow className="app-header-right">
757
757
  <span>2:29 PM</span>
758
758
  <CapIcon type="chevron-down" size="sm" />
759
759
  </CapRow>
@@ -812,12 +812,12 @@ export class TemplatePreview extends React.Component { // eslint-disable-line re
812
812
  </CapRow>
813
813
  ) : (
814
814
  <CapRow className="message-pop align-left">
815
- <CapRow type='flex' align='middle' justify='space-between' className="app-header">
816
- <CapRow type='flex' align='middle' className="app-header-left">
815
+ <CapRow className="app-header">
816
+ <CapRow className="app-header-left">
817
817
  <span className="app-icon">{""}</span>
818
818
  <CapLabel className="title">{content.header}</CapLabel>
819
819
  </CapRow>
820
- <CapRow type='flex' align='middle' className="app-header-right">
820
+ <CapRow className="app-header-right">
821
821
  <span>2:29 PM</span>
822
822
  <CapIcon type="chevron-down" size="sm" />
823
823
  </CapRow>
@@ -361,7 +361,7 @@ const ChannelSelectionStep = ({
361
361
  // Add incentive in actions (campaigns-ui style: cap-card-action-container)
362
362
  if (hasIncentiveOptions) {
363
363
  card.actions = [
364
- <CapRow className="cap-card-action-container add-incentive-action-row" key="add-incentive">
364
+ <CapRow type='flex' align="middle" className="cap-card-action-container add-incentive-action-row" key="add-incentive">
365
365
  <CapDropdown
366
366
  overlay={renderIncentiveDropdownOverlay((menuClickEvent) => handleIncentiveMenuClick(menuClickEvent, item.contentId))}
367
367
  onVisibleChange={(visible) => handleIncentivesVisibleChange(visible, item.contentId)}
@@ -166,10 +166,6 @@ export class FTP extends React.Component {
166
166
  this.insertAtCursor(`{{${data}}}`);
167
167
  };
168
168
 
169
- setInputRef = (textarea) => {
170
- this.textArea = textarea;
171
- };
172
-
173
169
  getServerSelectionContent = () => {
174
170
  const { getFTPServersInProgress } = this.props.FTP || {};
175
171
  const { formatMessage } = this.props.intl;
@@ -257,7 +253,7 @@ export class FTP extends React.Component {
257
253
  injectedTags={injectedTags || {}}
258
254
  />
259
255
  <CapInput.TextArea
260
- setInputRef={this.setInputRef}
256
+ id="ftp-message-content"
261
257
  label={formatMessage(messages.messageHeader)}
262
258
  onChange={this.updateMessageBody}
263
259
  value={messageContent}
@@ -529,13 +525,14 @@ export class FTP extends React.Component {
529
525
  };
530
526
 
531
527
  insertAtCursor = (myValue) => {
532
- this.textArea.focus();
533
- const textAreaRef = this.textArea.textAreaRef;
528
+ const textAreaRef = document.getElementById('ftp-message-content');
529
+ if (!textAreaRef) return;
530
+ textAreaRef.focus();
534
531
  let pos = textAreaRef.selectionEnd + myValue.length;
535
532
  //IE support
536
533
  let newMessage = textAreaRef.value + myValue;
537
534
  if (document.selection) {
538
- this.textArea.focus();
535
+ textAreaRef.focus();
539
536
  const sel = document.selection.createRange();
540
537
  sel.text = myValue;
541
538
  } else if (textAreaRef.selectionStart || textAreaRef.selectionStart === '0') { //MOZILLA and others
@@ -1,4 +1,4 @@
1
- import React, { useRef } from 'react';
1
+ import React, { useRef, useEffect } from 'react';
2
2
  import PropTypes from 'prop-types';
3
3
  import { FormattedMessage } from 'react-intl';
4
4
  import CapInput from '@capillarytech/cap-ui-library/CapInput';
@@ -73,6 +73,13 @@ export const MessageSection = ({
73
73
  handleMessageTextAreaRef,
74
74
  isAiContentBotDisabled,
75
75
  }) => {
76
+ // resolve the underlying DOM
77
+ // <textarea> by id and feed it to the existing ref handler. CapEmojiPicker and
78
+ // the Aira trigger both read `messageTextAreaRef.current` as that DOM node.
79
+ useEffect(() => {
80
+ handleMessageTextAreaRef(document.getElementById('webpush-message-input'));
81
+ }, [handleMessageTextAreaRef]);
82
+
76
83
  const renderCharacterCount = () => {
77
84
  if (!SHOW_CHARACTER_COUNT) return null;
78
85
 
@@ -119,7 +126,6 @@ export const MessageSection = ({
119
126
  size="default"
120
127
  isRequired
121
128
  autosize={{ minRows: 3, maxRows: 5 }}
122
- setInputRef={handleMessageTextAreaRef}
123
129
  errorMessage={
124
130
  error && (
125
131
  <CapError className="webpush-template-message-error">
@@ -1,4 +1,6 @@
1
1
  import React from 'react';
2
+ import { mount } from 'enzyme';
3
+ import { IntlProvider } from 'react-intl';
2
4
  import { mountWithIntl, shallowWithIntl } from '../../../../helpers/intl-enzym-test-helpers';
3
5
  import MessageSection from './MessageSection';
4
6
  import CapInput from '@capillarytech/cap-ui-library/CapInput';
@@ -230,10 +232,31 @@ describe('MessageSection', () => {
230
232
  });
231
233
 
232
234
  describe('TextArea Ref Handling', () => {
233
- it('should call handleMessageTextAreaRef with setInputRef', () => {
235
+ it('should not pass the dropped setInputRef prop to CapInput.TextArea', () => {
234
236
  const wrapper = mountWithIntl(<MessageSection {...defaultProps} />);
235
237
  const textArea = wrapper.find(CapInput.TextArea);
236
- expect(textArea.prop('setInputRef')).toBe(mockHandleMessageTextAreaRef);
238
+ expect(textArea.prop('setInputRef')).toBeUndefined();
239
+ });
240
+
241
+ it('should resolve the DOM textarea by id and feed it to handleMessageTextAreaRef on mount', () => {
242
+ // attachTo a real DOM node so document.getElementById can find the
243
+ // antd-rendered <textarea> (enzyme mount is detached from document otherwise).
244
+ const container = document.createElement('div');
245
+ document.body.appendChild(container);
246
+ const wrapper = mount(
247
+ <IntlProvider locale="en">
248
+ <MessageSection {...defaultProps} />
249
+ </IntlProvider>,
250
+ { attachTo: container }
251
+ );
252
+
253
+ const textarea = document.getElementById('webpush-message-input');
254
+ expect(textarea).not.toBeNull();
255
+ expect(textarea.tagName).toBe('TEXTAREA');
256
+ expect(mockHandleMessageTextAreaRef).toHaveBeenCalledWith(textarea);
257
+
258
+ wrapper.unmount();
259
+ container.remove();
237
260
  });
238
261
 
239
262
  it('should pass messageTextAreaRef to CapEmojiPicker.Wrapper', () => {
@@ -47,7 +47,6 @@ exports[`MessageSection Rendering should render correctly with default props 1`]
47
47
  id="webpush-message-input"
48
48
  isRequired={true}
49
49
  onChange={[MockFunction]}
50
- setInputRef={[MockFunction]}
51
50
  size="default"
52
51
  value="Test Message"
53
52
  />
@@ -10,6 +10,9 @@ import { getBrowserOptionsForOS, getSupportedStates } from './config/notificatio
10
10
  import messages from '../messages';
11
11
  import './preview.scss';
12
12
 
13
+ const getDropdownPopupContainer = (triggerNode) =>
14
+ triggerNode.closest('.ant-modal-container, .ant-modal-content') || document.body;
15
+
13
16
  /**
14
17
  * PreviewControls Component
15
18
  *
@@ -67,6 +70,7 @@ const PreviewControls = ({
67
70
  options={stateOptions}
68
71
  value={selectedState}
69
72
  onChange={onStateChange}
73
+ getPopupContainer={getDropdownPopupContainer}
70
74
  // Note: Add tooltip prop here when tooltip mechanism is implemented
71
75
  // tooltip={tooltipMessage ? tooltipMessage : undefined}
72
76
  />
@@ -198,6 +202,7 @@ const PreviewControls = ({
198
202
  options={options}
199
203
  value={value}
200
204
  onChange={onChange}
205
+ getPopupContainer={getDropdownPopupContainer}
201
206
  />
202
207
  </>
203
208
  );
@@ -235,6 +240,7 @@ const PreviewControls = ({
235
240
  options={osOptions}
236
241
  value={selectedOS}
237
242
  onChange={handleOSChangeWithCascade}
243
+ getPopupContainer={getDropdownPopupContainer}
238
244
  />
239
245
  </CapColumn>
240
246
  <CapColumn span={12} className="preview-control-browser">
@@ -246,6 +252,7 @@ const PreviewControls = ({
246
252
  options={browserOptions}
247
253
  value={selectedBrowser}
248
254
  onChange={handleBrowserChangeWithCascade}
255
+ getPopupContainer={getDropdownPopupContainer}
249
256
  />
250
257
  </CapColumn>
251
258
  </CapRow>