@capillarytech/creatives-library 8.0.299-alpha.0 → 8.0.299-alpha.2

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.299-alpha.0",
4
+ "version": "8.0.299-alpha.2",
5
5
  "description": "Capillary creatives ui",
6
6
  "main": "./index.js",
7
7
  "module": "./index.es.js",
package/utils/common.js CHANGED
@@ -138,16 +138,15 @@ export const isEmailUnsubscribeTagMandatory = Auth.hasFeatureAccess.bind(
138
138
  EMAIL_UNSUBSCRIBE_TAG_MANDATORY,
139
139
  );
140
140
 
141
- // export const hasNewMobilePushFeatureEnabled = Auth.hasFeatureAccess.bind(
142
- // null,
143
- // ENABLE_NEW_MPUSH,
144
- // );
145
-
146
-
141
+ export const hasNewMobilePushFeatureEnabled = Auth.hasFeatureAccess.bind(
142
+ null,
143
+ ENABLE_NEW_MPUSH,
144
+ );
147
145
 
148
- export const hasNewEditorFlowInAppEnabled = () => {
149
- return false;
150
- }
146
+ export const hasNewEditorFlowInAppEnabled = Auth.hasFeatureAccess.bind(
147
+ null,
148
+ ENABLE_NEW_EDITOR_FLOW_INAPP,
149
+ );
151
150
 
152
151
  //filtering tags based on scope
153
152
  export const filterTags = (tagsToFilter, tagsList) => tagsList?.filter(
@@ -74,7 +74,7 @@ const CustomerCreationModal = ({
74
74
  const details = res.customerDetails || [];
75
75
  if (!success) {
76
76
  const errorMessage = response?.message || response?.status?.message
77
- if (errorMessage.toLowerCase().includes("merged customer found")) {
77
+ if (errorMessage?.toLowerCase().includes("merged customer found")) {
78
78
  return { success: true, exists: true, details: [] };
79
79
  }
80
80
  }
@@ -2,7 +2,7 @@ import CapModal from "@capillarytech/cap-ui-library/CapModal";
2
2
  import { FormattedMessage, injectIntl } from "react-intl";
3
3
  import messages from "./messages";
4
4
  import React, { useState } from "react";
5
- import { CapCard, CapRow, CapColumn, CapLabel } from "@capillarytech/cap-ui-library";
5
+ import { CapCard, CapRow, CapColumn } from "@capillarytech/cap-ui-library";
6
6
  import { CHANNELS } from "./constants";
7
7
  import CapButton from "@capillarytech/cap-ui-library/CapButton";
8
8
  import CapIcon from "@capillarytech/cap-ui-library/CapIcon";
@@ -134,7 +134,7 @@ SendTestMessage.defaultProps = {
134
134
  channel: undefined,
135
135
  renderAddTestCustomerButton: undefined,
136
136
  searchValue: '',
137
- setSearchValue: undefined,
137
+ setSearchValue: () => {}, // no-op when not provided; required by TreeSelect when showSearch is true
138
138
  deliverySettings: {},
139
139
  senderDetailsOptions: [],
140
140
  wecrmAccounts: [],
@@ -410,13 +410,15 @@ const CommonTestAndPreview = (props) => {
410
410
  /**
411
411
  * Common handler for saving test customers (both new and existing)
412
412
  */
413
- const handleSaveTestCustomer = async (validationErrors = {},setIsLoading = false) => {
413
+ const handleSaveTestCustomer = async (validationErrors = {},setIsLoading) => {
414
414
  // Check for validation errors before saving (for new customers)
415
415
  if (customerModal[1] === CUSTOMER_MODAL_NEW && (validationErrors.email || validationErrors.mobile)) {
416
416
  return;
417
417
  }
418
418
 
419
- setIsLoading(true);
419
+ if (typeof setIsLoading === 'function') {
420
+ setIsLoading(true);
421
+ }
420
422
 
421
423
  try {
422
424
  let payload;
@@ -40,6 +40,11 @@ function BeePopupEditor(props) {
40
40
  const savedCallback = useRef();
41
41
  const beeInstanceRef = useRef(null);
42
42
  const isInitializedRef = useRef(false);
43
+ const beeJsonRef = useRef(beeJson);
44
+
45
+ useEffect(() => {
46
+ beeJsonRef.current = beeJson;
47
+ }, [beeJson]);
43
48
 
44
49
  const [visibleTaglist, setVisibleTaglist] = useState(false);
45
50
  const [selectedTag, setSelectedTag] = useState({});
@@ -111,8 +116,10 @@ function BeePopupEditor(props) {
111
116
  window.BeePlugin.create(tokenData, beeConfig, (instance) => {
112
117
  beePluginInstance = instance;
113
118
  beeInstanceRef.current = instance;
114
- // Check if beeJson is already an object (happens when layout type changes)
115
- const parseJson = typeof beeJson === 'string' ? JSON.parse(beeJson) : beeJson;
119
+ // Use ref to get the latest beeJson the closure captures the value at effect time,
120
+ // but beeJsonRef.current is always up-to-date (e.g. when template data loads async)
121
+ const latestBeeJson = beeJsonRef.current;
122
+ const parseJson = typeof latestBeeJson === 'string' ? JSON.parse(latestBeeJson) : latestBeeJson;
116
123
  beePluginInstance.start(parseJson);
117
124
  saveBeeInstance(beePluginInstance, device);
118
125
  isInitializedRef.current = true;
@@ -1103,17 +1103,26 @@ exports[`Test SlideBoxContent container Should render correct component for what
1103
1103
 
1104
1104
  exports[`Test SlideBoxContent container Should render correct component for whatsapp channel create mode 2`] = `
1105
1105
  <SlideBoxContent__CreativesWrapper>
1106
- <Connect(Connect(UserIsAuthenticated(InjectIntl(CreativesCommon))))
1107
- date={0}
1106
+ <ForwardRef
1108
1107
  getDefaultTags=""
1109
- key="creatives-inapp-wrapper"
1108
+ key="creatives-inapp-create"
1109
+ location={
1110
+ Object {
1111
+ "pathname": "/inapp/create",
1112
+ "query": Object {
1113
+ "isEditFromCampaigns": undefined,
1114
+ "module": "library",
1115
+ "type": "embedded",
1116
+ },
1117
+ "search": "",
1118
+ }
1119
+ }
1110
1120
  onCreateComplete={[MockFunction]}
1111
1121
  templateData={
1112
1122
  Object {
1113
1123
  "mode": "create",
1114
1124
  }
1115
1125
  }
1116
- type=""
1117
1126
  />
1118
1127
  </SlideBoxContent__CreativesWrapper>
1119
1128
  `;
@@ -39,33 +39,58 @@ jest.mock('../../../utils/common', () => ({
39
39
  // Mock HTMLEditor component - must be before imports
40
40
  jest.mock('../../../v2Components/HtmlEditor', () => {
41
41
  const React = require('react');
42
- const MockHTMLEditor = function MockHTMLEditor({ variant, initialContent, onContentChange, onSave, 'data-test': dataTest }) {
43
- return React.createElement('div', { 'data-testid': dataTest || 'html-editor' },
42
+
43
+ const MockHTMLEditor = function MockHTMLEditor({
44
+ variant,
45
+ initialContent,
46
+ onContentChange,
47
+ onSave,
48
+ 'data-test': dataTest,
49
+ }) {
50
+ return React.createElement(
51
+ 'div',
52
+ { 'data-testid': dataTest || 'inapp-html-editor' },
44
53
  React.createElement('div', { 'data-testid': 'html-editor-variant' }, variant),
45
- React.createElement('div', { 'data-testid': 'html-editor-initial-android' }, initialContent?.android || ''),
46
- React.createElement('div', { 'data-testid': 'html-editor-initial-ios' }, initialContent?.ios || ''),
47
- React.createElement('button', {
48
- 'data-testid': 'html-editor-change-button',
49
- onClick: () => {
50
- if (onContentChange) {
51
- onContentChange({
52
- android: '<p>Updated Android HTML</p>',
53
- ios: '<p>Updated iOS HTML</p>',
54
- });
55
- }
56
- }
57
- }, 'Simulate Content Change'),
58
- React.createElement('button', {
59
- 'data-testid': 'html-editor-save-button',
60
- onClick: () => {
61
- if (onSave) {
62
- onSave({
63
- android: '<p>Saved Android HTML</p>',
64
- ios: '<p>Saved iOS HTML</p>',
65
- });
66
- }
67
- }
68
- }, 'Simulate Save')
54
+ React.createElement(
55
+ 'div',
56
+ { 'data-testid': 'html-editor-initial-android' },
57
+ initialContent?.android || ''
58
+ ),
59
+ React.createElement(
60
+ 'div',
61
+ { 'data-testid': 'html-editor-initial-ios' },
62
+ initialContent?.ios || ''
63
+ ),
64
+ React.createElement(
65
+ 'button',
66
+ {
67
+ 'data-testid': 'html-editor-change-button',
68
+ onClick: () => {
69
+ if (onContentChange) {
70
+ onContentChange({
71
+ android: '<p>Updated Android HTML</p>',
72
+ ios: '<p>Updated iOS HTML</p>',
73
+ });
74
+ }
75
+ },
76
+ },
77
+ 'Simulate Content Change'
78
+ ),
79
+ React.createElement(
80
+ 'button',
81
+ {
82
+ 'data-testid': 'html-editor-save-button',
83
+ onClick: () => {
84
+ if (onSave) {
85
+ onSave({
86
+ android: '<p>Saved Android HTML</p>',
87
+ ios: '<p>Saved iOS HTML</p>',
88
+ });
89
+ }
90
+ },
91
+ },
92
+ 'Simulate Save'
93
+ )
69
94
  );
70
95
  };
71
96
  MockHTMLEditor.displayName = 'MockHTMLEditor';
@@ -75,22 +100,25 @@ jest.mock('../../../v2Components/HtmlEditor', () => {
75
100
  };
76
101
  });
77
102
 
78
- import React from 'react';
79
- import { render, screen, fireEvent, waitFor, act } from '@testing-library/react';
80
- import '@testing-library/jest-dom';
81
- import { IntlProvider } from 'react-intl';
82
- import { Provider } from 'react-redux';
83
- import { configureStore } from '@capillarytech/vulcan-react-sdk/utils';
84
- import history from '../../../utils/history';
85
- import { initialReducer } from '../../../initialReducer';
86
- import { InApp } from '../index';
87
- import { INAPP_EDITOR_TYPES } from '../../InAppWrapper/constants';
88
-
89
-
90
- // Mock other dependencies
103
+ /**
104
+ * Mock CapDeviceContent to ensure HTMLEditor renders
105
+ */
91
106
  jest.mock('../../../v2Components/CapDeviceContent', () => {
107
+ const React = require('react');
108
+ const HTMLEditor = require('../../../v2Components/HtmlEditor').default;
109
+
92
110
  return function MockCapDeviceContent() {
93
- return <div data-testid="cap-device-content">Device Content</div>;
111
+ return (
112
+ <div data-testid="cap-device-content">
113
+ <HTMLEditor
114
+ variant="inapp"
115
+ initialContent={{ android: '', ios: '' }}
116
+ onContentChange={jest.fn()}
117
+ onSave={jest.fn()}
118
+ data-test="inapp-html-editor"
119
+ />
120
+ </div>
121
+ );
94
122
  };
95
123
  });
96
124
 
@@ -100,7 +128,20 @@ jest.mock('../../../v2Components/TemplatePreview', () => {
100
128
  };
101
129
  });
102
130
 
131
+ import React from 'react';
132
+ import { render, screen, fireEvent, act } from '@testing-library/react';
133
+ import '@testing-library/jest-dom';
134
+ import { IntlProvider } from 'react-intl';
135
+ import { Provider } from 'react-redux';
136
+ import { configureStore } from '@capillarytech/vulcan-react-sdk/utils';
137
+
138
+ import history from '../../../utils/history';
139
+ import { initialReducer } from '../../../initialReducer';
140
+ import { InApp } from '../index';
141
+ import { INAPP_EDITOR_TYPES } from '../../InAppWrapper/constants';
142
+
103
143
  let store;
144
+
104
145
  beforeAll(() => {
105
146
  store = configureStore({}, initialReducer, history);
106
147
  });
@@ -135,8 +176,6 @@ const defaultProps = {
135
176
  configs: {
136
177
  android: '1',
137
178
  ios: '1',
138
- accessToken: 'test-token',
139
- deeplink: '{}',
140
179
  },
141
180
  },
142
181
  },
@@ -145,7 +184,6 @@ const defaultProps = {
145
184
  query: {},
146
185
  search: '',
147
186
  },
148
- getDefaultTags: null,
149
187
  supportedTags: [],
150
188
  metaEntities: {
151
189
  tags: {
@@ -158,26 +196,11 @@ const defaultProps = {
158
196
  currentOrgDetails: {
159
197
  accessibleFeatures: [],
160
198
  },
161
- fetchingLiquidValidation: false,
162
- getTemplateDetailsInProgress: false,
163
- isEditInApp: false,
164
- };
165
-
166
- // Create stable defaultData objects outside renderComponent to avoid reference changes
167
- const createStableDefaultData = (editorType) => {
168
- if (!editorType) return {};
169
- return { 'editor-type': editorType };
170
199
  };
171
200
 
172
201
  const renderComponent = (props = {}) => {
173
202
  const mergedProps = { ...defaultProps, ...props };
174
- // Ensure defaultData is always an object and stable
175
- if (!mergedProps.defaultData) {
176
- mergedProps.defaultData = {};
177
- } else if (mergedProps.defaultData['editor-type']) {
178
- // Create a stable reference for defaultData
179
- mergedProps.defaultData = createStableDefaultData(mergedProps.defaultData['editor-type']);
180
- }
203
+
181
204
  return render(
182
205
  <Provider store={store}>
183
206
  <IntlProvider locale="en" messages={{}}>
@@ -195,36 +218,33 @@ describe('InApp HTMLEditor Integration', () => {
195
218
  describe('Template Name Editing', () => {
196
219
  test('should not truncate template name when editing', async () => {
197
220
  const showTemplateNameMock = jest.fn();
221
+
198
222
  renderComponent({
199
- defaultData: { 'editor-type': INAPP_EDITOR_TYPES.HTML_EDITOR, 'template-name': 'abcd' },
223
+ defaultData: {
224
+ 'editor-type': INAPP_EDITOR_TYPES.HTML_EDITOR,
225
+ 'template-name': 'abcd',
226
+ },
200
227
  showTemplateName: showTemplateNameMock,
201
228
  });
202
229
 
203
- // Wait for HTML editor to render
204
- await waitFor(() => {
205
- expect(screen.getByTestId('inapp-html-editor')).toBeInTheDocument();
206
- });
207
-
208
- // Verify showTemplateName was called with the template name
209
- // The template name input is handled by the wrapper, not the InApp component
210
- // So we verify that the callback was set up correctly
230
+ const editor = await screen.findByTestId('inapp-html-editor');
231
+ expect(editor).toBeInTheDocument();
211
232
  expect(showTemplateNameMock).toBeDefined();
212
233
  });
213
234
 
214
235
  test('should preserve template name when showTemplateName callback is provided', async () => {
215
236
  const showTemplateNameMock = jest.fn();
237
+
216
238
  renderComponent({
217
- defaultData: { 'editor-type': INAPP_EDITOR_TYPES.HTML_EDITOR, 'template-name': 'test' },
239
+ defaultData: {
240
+ 'editor-type': INAPP_EDITOR_TYPES.HTML_EDITOR,
241
+ 'template-name': 'test',
242
+ },
218
243
  showTemplateName: showTemplateNameMock,
219
244
  });
220
245
 
221
- // Wait for HTML editor to render
222
- await waitFor(() => {
223
- expect(screen.getByTestId('inapp-html-editor')).toBeInTheDocument();
224
- });
225
-
226
- // Verify the component renders correctly with template name
227
- // The template name is managed by the wrapper component
246
+ const editor = await screen.findByTestId('inapp-html-editor');
247
+ expect(editor).toBeInTheDocument();
228
248
  expect(showTemplateNameMock).toBeDefined();
229
249
  });
230
250
  });
@@ -232,40 +252,7 @@ describe('InApp HTMLEditor Integration', () => {
232
252
  describe('TAG API Calls', () => {
233
253
  test('should only make one TAG API call when HTML Editor is used', async () => {
234
254
  const fetchSchemaForEntityMock = jest.fn();
235
-
236
- renderComponent({
237
- defaultData: { 'editor-type': INAPP_EDITOR_TYPES.HTML_EDITOR },
238
- globalActions: {
239
- ...defaultProps.globalActions,
240
- fetchSchemaForEntity: fetchSchemaForEntityMock,
241
- },
242
- });
243
-
244
- // Wait for component to mount and initialize
245
- await waitFor(() => {
246
- // Component should be rendered
247
- expect(screen.getByTestId('inapp-html-editor')).toBeInTheDocument();
248
- });
249
255
 
250
- // Wait a bit for all useEffects to complete
251
- await waitFor(() => {
252
- // After isHTMLTemplate is set to true, fetchSchemaForEntity should not be called again
253
- // The initial call might happen before isHTMLTemplate is set, but subsequent calls should not happen
254
- // For HTML Editor, tags should only be fetched via handleOnTagsContextChange
255
- }, { timeout: 1000 });
256
-
257
- // For HTML Editor, fetchSchemaForEntity should not be called after isHTMLTemplate is set
258
- // (It might be called once initially before isHTMLTemplate is set, but that's acceptable)
259
- // The key is that it's not called multiple times
260
- const callCount = fetchSchemaForEntityMock.mock.calls.length;
261
- // Allow 0 or 1 calls (1 if it was called before isHTMLTemplate was set)
262
- expect(callCount).toBeLessThanOrEqual(1);
263
- });
264
-
265
- test('should make TAG API call only once when handleOnTagsContextChange is called', async () => {
266
- const fetchSchemaForEntityMock = jest.fn();
267
-
268
- // We need to access the handler, but since it's internal, we'll test via HTMLEditor's onContextChange
269
256
  renderComponent({
270
257
  defaultData: { 'editor-type': INAPP_EDITOR_TYPES.HTML_EDITOR },
271
258
  globalActions: {
@@ -274,19 +261,8 @@ describe('InApp HTMLEditor Integration', () => {
274
261
  },
275
262
  });
276
263
 
277
- await waitFor(() => {
278
- expect(screen.getByTestId('inapp-html-editor')).toBeInTheDocument();
279
- });
280
-
281
- // Wait a bit for all useEffects to complete
282
- await waitFor(() => {
283
- // After isHTMLTemplate is set to true, fetchSchemaForEntity should not be called again
284
- }, { timeout: 1000 });
264
+ await screen.findByTestId('inapp-html-editor');
285
265
 
286
- // Simulate context change from HTMLEditor (which would call onContextChange)
287
- // Since we can't directly access the handler, we verify the behavior
288
- // by checking that fetchSchemaForEntity is not called multiple times
289
- // (It might be called once initially before isHTMLTemplate is set, but that's acceptable)
290
266
  const callCount = fetchSchemaForEntityMock.mock.calls.length;
291
267
  expect(callCount).toBeLessThanOrEqual(1);
292
268
  });
@@ -298,45 +274,37 @@ describe('InApp HTMLEditor Integration', () => {
298
274
  defaultData: { 'editor-type': INAPP_EDITOR_TYPES.HTML_EDITOR },
299
275
  });
300
276
 
301
- await waitFor(() => {
302
- expect(screen.getByTestId('inapp-html-editor')).toBeInTheDocument();
303
- });
277
+ await screen.findByTestId('inapp-html-editor');
304
278
 
305
- // Simulate content being added
306
279
  const changeButton = screen.getByTestId('html-editor-change-button');
280
+
307
281
  act(() => {
308
282
  fireEvent.click(changeButton);
309
283
  });
310
284
 
311
- // Get the layout radio buttons
312
285
  const layoutRadios = container.querySelectorAll('input[type="radio"]');
313
- expect(layoutRadios.length).toBeGreaterThan(0);
314
286
 
315
- // Change layout type
316
287
  if (layoutRadios.length > 1) {
317
288
  act(() => {
318
- fireEvent.change(layoutRadios[1], { target: { value: 'HEADER' } });
289
+ fireEvent.change(layoutRadios[1], {
290
+ target: { value: 'HEADER' },
291
+ });
319
292
  });
320
293
 
321
- // Content should still be present in the editor
322
- // The HTMLEditor should preserve content via initialContent prop
323
- await waitFor(() => {
324
- const editor = screen.getByTestId('inapp-html-editor');
325
- expect(editor).toBeInTheDocument();
326
- });
294
+ const editor = await screen.findByTestId('inapp-html-editor');
295
+ expect(editor).toBeInTheDocument();
327
296
  }
328
297
  });
329
298
  });
330
299
 
331
300
  describe('Layout Labels', () => {
332
301
  test('should use correct layout labels in constants', () => {
333
- const { INAPP_LAYOUT_DETAILS, INAPP_MESSAGE_LAYOUT_TYPES } = require('../constants');
334
-
335
- // Verify that HEADER maps to Top banner
302
+ const { INAPP_LAYOUT_DETAILS, INAPP_MESSAGE_LAYOUT_TYPES } =
303
+ require('../constants');
304
+
336
305
  expect(INAPP_MESSAGE_LAYOUT_TYPES.TOPBANNER).toBe('HEADER');
337
306
  expect(INAPP_LAYOUT_DETAILS.HEADER).toBeDefined();
338
-
339
- // Verify that FOOTER maps to Bottom banner
307
+
340
308
  expect(INAPP_MESSAGE_LAYOUT_TYPES.BOTTOMBANNER).toBe('FOOTER');
341
309
  expect(INAPP_LAYOUT_DETAILS.FOOTER).toBeDefined();
342
310
  });
@@ -346,29 +314,16 @@ describe('InApp HTMLEditor Integration', () => {
346
314
  test('should allow creating template with Android-only content when both devices supported', async () => {
347
315
  renderComponent({
348
316
  defaultData: { 'editor-type': INAPP_EDITOR_TYPES.HTML_EDITOR },
349
- accountData: {
350
- selectedWeChatAccount: {
351
- ...defaultProps.accountData.selectedWeChatAccount,
352
- configs: {
353
- android: '1',
354
- ios: '1',
355
- },
356
- },
357
- },
358
317
  });
359
318
 
360
- await waitFor(() => {
361
- expect(screen.getByTestId('inapp-html-editor')).toBeInTheDocument();
362
- });
319
+ await screen.findByTestId('inapp-html-editor');
363
320
 
364
- // Simulate adding content only for Android
365
321
  const changeButton = screen.getByTestId('html-editor-change-button');
322
+
366
323
  act(() => {
367
324
  fireEvent.click(changeButton);
368
325
  });
369
326
 
370
- // Done button should be enabled (not disabled)
371
- // We can't directly test isDisableDone, but we can verify the component renders
372
327
  expect(screen.getByTestId('inapp-html-editor')).toBeInTheDocument();
373
328
  });
374
329
  });
@@ -883,7 +883,7 @@ export const InApp = (props) => {
883
883
  // Use 'html editor template' as title for HTML editor to differentiate from BEE editor
884
884
  title: isHTMLTemplate ? 'html editor template' : titleAndroid,
885
885
  message: androidMessage,
886
- bodyType: templateLayoutType,
886
+ bodyType: bodyTypeForBackend,
887
887
  expandableDetails: {
888
888
  style: androidExpandableStyle,
889
889
  message: androidMessage,
@@ -894,5 +894,5 @@ export const deviceContentProps = {
894
894
  },
895
895
  ],
896
896
  tags: [],
897
+ isOtherDeviceSupported: true,
897
898
  };
898
-