@capillarytech/creatives-library 8.0.325 → 8.0.327-alpha.0

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 (72) hide show
  1. package/package.json +1 -1
  2. package/services/api.js +17 -0
  3. package/services/tests/api.test.js +85 -0
  4. package/utils/commonUtils.js +10 -0
  5. package/utils/tagValidations.js +2 -3
  6. package/utils/tests/commonUtil.test.js +169 -0
  7. package/utils/tests/tagValidations.test.js +1 -35
  8. package/v2Components/CapTagList/index.js +22 -14
  9. package/v2Components/CapTagList/style.scss +0 -48
  10. package/v2Components/CapTagListWithInput/index.js +0 -4
  11. package/v2Components/CapWhatsappCTA/index.js +0 -2
  12. package/v2Components/CommonTestAndPreview/AddTestCustomer.js +42 -0
  13. package/v2Components/CommonTestAndPreview/CustomerCreationModal.js +155 -0
  14. package/v2Components/CommonTestAndPreview/ExistingCustomerModal.js +93 -0
  15. package/v2Components/CommonTestAndPreview/SendTestMessage.js +79 -51
  16. package/v2Components/CommonTestAndPreview/_commonTestAndPreview.scss +135 -36
  17. package/v2Components/CommonTestAndPreview/actions.js +10 -0
  18. package/v2Components/CommonTestAndPreview/constants.js +15 -1
  19. package/v2Components/CommonTestAndPreview/index.js +315 -15
  20. package/v2Components/CommonTestAndPreview/messages.js +106 -0
  21. package/v2Components/CommonTestAndPreview/reducer.js +10 -0
  22. package/v2Components/CommonTestAndPreview/tests/AddTestCustomer.test.js +66 -0
  23. package/v2Components/CommonTestAndPreview/tests/CommonTestAndPreview.addTestCustomer.test.js +648 -0
  24. package/v2Components/CommonTestAndPreview/tests/CustomValuesEditor.test.js +24 -0
  25. package/v2Components/CommonTestAndPreview/tests/CustomerCreationModal.test.js +174 -0
  26. package/v2Components/CommonTestAndPreview/tests/ExistingCustomerModal.test.js +114 -0
  27. package/v2Components/CommonTestAndPreview/tests/SendTestMessage.test.js +52 -0
  28. package/v2Components/CommonTestAndPreview/tests/constants.test.js +31 -1
  29. package/v2Components/CommonTestAndPreview/tests/index.test.js +36 -0
  30. package/v2Components/CommonTestAndPreview/tests/reducer.test.js +71 -0
  31. package/v2Components/CommonTestAndPreview/tests/selectors.test.js +17 -0
  32. package/v2Components/FormBuilder/index.js +0 -7
  33. package/v2Components/HtmlEditor/HTMLEditor.js +1 -6
  34. package/v2Components/HtmlEditor/__tests__/HTMLEditor.apiErrors.test.js +0 -1
  35. package/v2Components/HtmlEditor/__tests__/HTMLEditor.test.js +2 -927
  36. package/v2Components/HtmlEditor/components/CodeEditorPane/index.js +0 -3
  37. package/v2Containers/BeeEditor/index.js +0 -3
  38. package/v2Containers/CreativesContainer/SlideBoxContent.js +1 -28
  39. package/v2Containers/CreativesContainer/index.js +6 -10
  40. package/v2Containers/Email/index.js +0 -1
  41. package/v2Containers/EmailWrapper/components/EmailHTMLEditor.js +1 -7
  42. package/v2Containers/EmailWrapper/components/EmailWrapperView.js +0 -3
  43. package/v2Containers/EmailWrapper/components/__tests__/EmailHTMLEditor.test.js +2 -20
  44. package/v2Containers/EmailWrapper/components/__tests__/EmailWrapperView.test.js +1 -16
  45. package/v2Containers/EmailWrapper/hooks/useEmailWrapper.js +0 -3
  46. package/v2Containers/EmailWrapper/index.js +0 -4
  47. package/v2Containers/EmailWrapper/tests/useEmailWrapper.edgeCases.test.js +0 -1
  48. package/v2Containers/EmailWrapper/tests/useEmailWrapper.test.js +0 -9
  49. package/v2Containers/InAppWrapper/hooks/__tests__/useInAppWrapper.test.js +0 -19
  50. package/v2Containers/InAppWrapper/hooks/useInAppWrapper.js +0 -3
  51. package/v2Containers/InAppWrapper/index.js +0 -3
  52. package/v2Containers/MobilePush/Create/index.js +0 -2
  53. package/v2Containers/MobilePush/Edit/index.js +0 -2
  54. package/v2Containers/MobilepushWrapper/index.js +1 -3
  55. package/v2Containers/Rcs/index.js +1 -9
  56. package/v2Containers/Rcs/tests/__snapshots__/index.test.js.snap +1886 -1754
  57. package/v2Containers/Sms/Create/index.js +0 -2
  58. package/v2Containers/Sms/Edit/index.js +0 -2
  59. package/v2Containers/SmsTrai/Edit/index.js +0 -2
  60. package/v2Containers/SmsTrai/Edit/tests/__snapshots__/index.test.js.snap +351 -318
  61. package/v2Containers/SmsWrapper/index.js +0 -2
  62. package/v2Containers/TagList/index.js +2 -41
  63. package/v2Containers/TagList/messages.js +0 -4
  64. package/v2Containers/TagList/tests/TagList.test.js +20 -122
  65. package/v2Containers/TagList/tests/mockdata.js +0 -17
  66. package/v2Containers/Viber/index.js +0 -5
  67. package/v2Containers/WebPush/Create/hooks/useTagManagement.js +2 -0
  68. package/v2Containers/WebPush/Create/index.js +1 -9
  69. package/v2Containers/Whatsapp/index.js +0 -5
  70. package/v2Containers/Whatsapp/tests/__snapshots__/index.test.js.snap +5586 -5232
  71. package/v2Containers/Zalo/index.js +0 -2
  72. package/v2Components/CapTagListWithInput/__tests__/CapTagListWithInput.test.js +0 -63
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@capillarytech/creatives-library",
3
3
  "author": "meharaj",
4
- "version": "8.0.325",
4
+ "version": "8.0.327-alpha.0",
5
5
  "description": "Capillary creatives ui",
6
6
  "main": "./index.js",
7
7
  "module": "./index.es.js",
package/services/api.js CHANGED
@@ -741,4 +741,21 @@ export const getBeePopupBuilderToken = () => {
741
741
  return request(url, getAPICallObject('GET'));
742
742
  };
743
743
 
744
+ /**
745
+ * Look up member by identifier (email or mobile) for add test customer flow.
746
+ * Uses standard API auth headers (no x-cap-ct) to avoid CORS preflight issues with node layer.
747
+ * @param {string} identifierType - 'email' or 'mobile'
748
+ * @param {string} identifierValue - email address or mobile number
749
+ * @returns {Promise} Promise resolving to { success, response: { exists, customerDetails } }
750
+ */
751
+ export const getMembersLookup = (identifierType, identifierValue) => {
752
+ const url = `${API_ENDPOINT}/members?identifierType=${encodeURIComponent(identifierType)}&identifierValue=${encodeURIComponent(identifierValue)}`;
753
+ return request(url, getAPICallObject('GET'));
754
+ };
755
+
756
+ export const createTestCustomer = (payload) => {
757
+ const url = `${API_ENDPOINT}/testCustomers`;
758
+ return request(url, getAPICallObject('POST', payload));
759
+ };
760
+
744
761
  export {request, getAPICallObject};
@@ -29,6 +29,8 @@ import {
29
29
  getAssetStatus,
30
30
  getBeePopupBuilderToken,
31
31
  getCmsAccounts,
32
+ getMembersLookup,
33
+ createTestCustomer,
32
34
  } from '../api';
33
35
  import { mockData } from './mockData';
34
36
  import getSchema from '../getSchema';
@@ -1014,3 +1016,86 @@ describe('getCmsAccounts', () => {
1014
1016
  expect(result).toBeInstanceOf(Promise);
1015
1017
  });
1016
1018
  });
1019
+
1020
+ describe('getMembersLookup', () => {
1021
+ beforeEach(() => {
1022
+ global.fetch = jest.fn();
1023
+ global.fetch.mockReturnValue(Promise.resolve({
1024
+ status: 200,
1025
+ json: () => Promise.resolve({ success: true, response: { exists: false, customerDetails: [] } }),
1026
+ }));
1027
+ });
1028
+
1029
+ it('should return a Promise', () => {
1030
+ const result = getMembersLookup('email', 'user@example.com');
1031
+ expect(result).toBeInstanceOf(Promise);
1032
+ });
1033
+
1034
+ it('should be callable with identifierType and identifierValue', () => {
1035
+ expect(typeof getMembersLookup).toBe('function');
1036
+ const result = getMembersLookup('mobile', '9123456789');
1037
+ expect(result).toBeInstanceOf(Promise);
1038
+ });
1039
+
1040
+ it('should call fetch with correct URL encoding and GET method', () => {
1041
+ global.fetch.mockClear();
1042
+ getMembersLookup('email', 'user+test@example.com');
1043
+ expect(global.fetch).toHaveBeenCalled();
1044
+ const calls = global.fetch.mock.calls;
1045
+ const withEncoding = calls.find(
1046
+ (c) =>
1047
+ c[0] &&
1048
+ String(c[0]).includes('members') &&
1049
+ String(c[0]).includes('identifierType=email') &&
1050
+ String(c[0]).includes('user%2Btest%40example.com')
1051
+ );
1052
+ if (withEncoding) {
1053
+ const [url, options] = withEncoding;
1054
+ expect(url).toContain('identifierType=email');
1055
+ expect(url).toContain('identifierValue=user%2Btest%40example.com');
1056
+ expect(options?.method || 'GET').toBe('GET');
1057
+ }
1058
+ const anyMembersCall = calls.find((c) => c[0] && String(c[0]).includes('members'));
1059
+ expect(anyMembersCall).toBeDefined();
1060
+ expect(anyMembersCall[0]).toContain('identifierType=');
1061
+ expect(anyMembersCall[0]).toContain('identifierValue=');
1062
+ expect(anyMembersCall[1]?.method || 'GET').toBe('GET');
1063
+ });
1064
+ });
1065
+
1066
+ describe('createTestCustomer', () => {
1067
+ beforeEach(() => {
1068
+ global.fetch = jest.fn();
1069
+ global.fetch.mockReturnValue(Promise.resolve({
1070
+ status: 200,
1071
+ json: () => Promise.resolve({ success: true }),
1072
+ }));
1073
+ });
1074
+
1075
+ it('should return a Promise', () => {
1076
+ const payload = { customer: { email: 'test@example.com', firstName: 'Test' } };
1077
+ const result = createTestCustomer(payload);
1078
+ expect(result).toBeInstanceOf(Promise);
1079
+ });
1080
+
1081
+ it('should accept customerId payload for existing customer', () => {
1082
+ const payload = { customerId: 'cust-123' };
1083
+ const result = createTestCustomer(payload);
1084
+ expect(result).toBeInstanceOf(Promise);
1085
+ expect(global.fetch).toHaveBeenCalled();
1086
+ const lastCall = global.fetch.mock.calls[global.fetch.mock.calls.length - 1];
1087
+ expect(lastCall[0]).toContain('testCustomers');
1088
+ expect(lastCall[1].method).toBe('POST');
1089
+ expect(lastCall[1].body).toBe(JSON.stringify(payload));
1090
+ });
1091
+
1092
+ it('should call fetch with /testCustomers URL and POST method', () => {
1093
+ const payload = { customer: { email: 'n@b.co', firstName: 'N' } };
1094
+ createTestCustomer(payload);
1095
+ expect(global.fetch).toHaveBeenCalled();
1096
+ const lastCall = global.fetch.mock.calls[global.fetch.mock.calls.length - 1];
1097
+ expect(lastCall[0]).toContain('testCustomers');
1098
+ expect(lastCall[1].method).toBe('POST');
1099
+ expect(lastCall[1].body).toBe(JSON.stringify(payload));
1100
+ });
1101
+ });
@@ -17,6 +17,8 @@ import {
17
17
  } from "../v2Containers/CreativesContainer/constants";
18
18
  import { GLOBAL_CONVERT_OPTIONS } from "../v2Components/FormBuilder/constants";
19
19
  import { SMS_TRAI_VAR } from '../v2Containers/SmsTrai/Edit/constants';
20
+ import { EMAIL_REGEX, PHONE_REGEX } from '../v2Components/CommonTestAndPreview/constants';
21
+
20
22
  export const apiMessageFormatHandler = (id, fallback) => (
21
23
  <FormattedMessage id={id} defaultMessage={fallback} />
22
24
  );
@@ -530,3 +532,11 @@ export const checkForPersonalizationTokens = (formData) => {
530
532
  }
531
533
  return false;
532
534
  };
535
+
536
+ export const isValidEmail = (email) => EMAIL_REGEX.test(email);
537
+ export const isValidMobile = (mobile) => PHONE_REGEX.test(mobile);
538
+
539
+ export const formatPhoneNumber = (phone) => {
540
+ if (!phone) return '';
541
+ return String(phone).replace(/[^\d]/g, '');
542
+ };
@@ -207,8 +207,7 @@ export const validateIfTagClosed = (value) => {
207
207
  export const preprocessHtml = (content) => {
208
208
  const replacements = {
209
209
  "&#39;": "'",
210
- "&quot;": "'",
211
- '"': "'",
210
+ "&quot;": '"',
212
211
  "&amp;": "&",
213
212
  "&lt;": "<",
214
213
  "&gt;": ">",
@@ -226,7 +225,7 @@ export const preprocessHtml = (content) => {
226
225
  });
227
226
 
228
227
  // Step 2: Perform the standard replacements on the entire content
229
- return contentWithStyleFixes?.replace(/&#39;|&quot;|&amp;|&lt;|&gt;|"|\n/g, (match) => replacements[match]);
228
+ return contentWithStyleFixes?.replace(/&#39;|&quot;|&amp;|&lt;|&gt;|\n/g, (match) => replacements[match]);
230
229
  };
231
230
 
232
231
  //this is used to get the subtags from custom or extended tags
@@ -8,6 +8,11 @@ import {
8
8
  validateCarouselCards,
9
9
  hasPersonalizationTags,
10
10
  checkForPersonalizationTokens,
11
+ isValidEmail,
12
+ isValidMobile,
13
+ formatPhoneNumber,
14
+ getMessageForDevice,
15
+ getTitleForDevice,
11
16
  } from "../commonUtils";
12
17
  import { skipTags } from "../tagValidations";
13
18
  import { SMS_TRAI_VAR } from '../../v2Containers/SmsTrai/Edit/constants';
@@ -629,6 +634,170 @@ describe("validateLiquidTemplateContent", () => {
629
634
  });
630
635
  });
631
636
 
637
+ describe("isValidEmail", () => {
638
+ it("returns true for valid email addresses", () => {
639
+ expect(isValidEmail("user@example.com")).toBe(true);
640
+ expect(isValidEmail("test.user@domain.co")).toBe(true);
641
+ expect(isValidEmail("a@b.co")).toBe(true);
642
+ });
643
+
644
+ it("returns false for invalid email addresses", () => {
645
+ expect(isValidEmail("")).toBe(false);
646
+ expect(isValidEmail("invalid")).toBe(false);
647
+ expect(isValidEmail("@nodomain.com")).toBe(false);
648
+ expect(isValidEmail("noatsign.com")).toBe(false);
649
+ expect(isValidEmail("missingtld@domain")).toBe(false);
650
+ expect(isValidEmail(" user@example.com ")).toBe(false);
651
+ });
652
+
653
+ it("returns true for edge case single-char local part", () => {
654
+ expect(isValidEmail("x@y.co")).toBe(true);
655
+ });
656
+ });
657
+
658
+ describe("isValidMobile", () => {
659
+ it("returns true for valid mobile numbers (8-15 digits, no leading zero)", () => {
660
+ expect(isValidMobile("12345678")).toBe(true);
661
+ expect(isValidMobile("9123456789")).toBe(true);
662
+ expect(isValidMobile("123456789012345")).toBe(true);
663
+ });
664
+
665
+ it("returns false for invalid mobile numbers", () => {
666
+ expect(isValidMobile("")).toBe(false);
667
+ expect(isValidMobile("01234567")).toBe(false);
668
+ expect(isValidMobile("1234567")).toBe(false);
669
+ expect(isValidMobile("1234567890123456")).toBe(false);
670
+ expect(isValidMobile("abc12345678")).toBe(false);
671
+ expect(isValidMobile("12345 67890")).toBe(false);
672
+ });
673
+
674
+ it("returns true for exactly 8 and exactly 15 digits", () => {
675
+ expect(isValidMobile("12345678")).toBe(true);
676
+ expect(isValidMobile("123456789012345")).toBe(true);
677
+ });
678
+ });
679
+
680
+ describe("formatPhoneNumber", () => {
681
+ it("returns empty string for falsy input", () => {
682
+ expect(formatPhoneNumber("")).toBe("");
683
+ expect(formatPhoneNumber(null)).toBe("");
684
+ expect(formatPhoneNumber(undefined)).toBe("");
685
+ });
686
+
687
+ it("strips non-digit characters", () => {
688
+ expect(formatPhoneNumber("91 234 567 890")).toBe("91234567890");
689
+ expect(formatPhoneNumber("+91-234567890")).toBe("91234567890");
690
+ expect(formatPhoneNumber("(123) 456-7890")).toBe("1234567890");
691
+ });
692
+
693
+ it("returns digits-only string unchanged", () => {
694
+ expect(formatPhoneNumber("9123456789")).toBe("9123456789");
695
+ });
696
+
697
+ it("returns empty string for whitespace-only input", () => {
698
+ expect(formatPhoneNumber(" ")).toBe("");
699
+ });
700
+ });
701
+
702
+ describe("hasPersonalizationTags", () => {
703
+ it("returns true when text has liquid tags {{ }}", () => {
704
+ expect(hasPersonalizationTags("Hello {{name}}")).toBe(true);
705
+ expect(hasPersonalizationTags("{{foo}}")).toBe(true);
706
+ });
707
+
708
+ it("returns true when text has bracket tags [ ]", () => {
709
+ expect(hasPersonalizationTags("Hello [event.name]")).toBe(true);
710
+ expect(hasPersonalizationTags("[tag]")).toBe(true);
711
+ });
712
+
713
+ it("returns false for empty or no tags", () => {
714
+ expect(hasPersonalizationTags("")).toBeFalsy();
715
+ expect(hasPersonalizationTags()).toBeFalsy();
716
+ expect(hasPersonalizationTags("plain text")).toBe(false);
717
+ expect(hasPersonalizationTags("only {{ open")).toBe(false);
718
+ });
719
+
720
+ it("returns false when only [ or only ] present without matching pair", () => {
721
+ expect(hasPersonalizationTags("only [ open")).toBe(false);
722
+ expect(hasPersonalizationTags("only ] close")).toBe(false);
723
+ });
724
+
725
+ it("returns true when liquid tags have content with spaces", () => {
726
+ expect(hasPersonalizationTags("Hello {{ customer.name }}")).toBe(true);
727
+ });
728
+ });
729
+
730
+ describe("getMessageForDevice", () => {
731
+ it("returns message for device from templateData", () => {
732
+ const templateData = {
733
+ versions: {
734
+ android: { base: { expandableDetails: { message: "Android msg" } } },
735
+ ios: { base: { expandableDetails: { message: "iOS msg" } } },
736
+ },
737
+ };
738
+ expect(getMessageForDevice(templateData, "android")).toBe("Android msg");
739
+ expect(getMessageForDevice(templateData, "ios")).toBe("iOS msg");
740
+ });
741
+
742
+ it("returns undefined for missing path", () => {
743
+ expect(getMessageForDevice(null, "android")).toBeUndefined();
744
+ expect(getMessageForDevice({}, "android")).toBeUndefined();
745
+ expect(getMessageForDevice({ versions: {} }, "android")).toBeUndefined();
746
+ });
747
+
748
+ it("returns undefined when base exists but expandableDetails is missing", () => {
749
+ expect(getMessageForDevice({ versions: { android: { base: {} } } }, "android")).toBeUndefined();
750
+ });
751
+ });
752
+
753
+ describe("getTitleForDevice", () => {
754
+ it("returns title for device from templateData", () => {
755
+ const templateData = {
756
+ versions: {
757
+ android: { base: { title: "Android title" } },
758
+ ios: { base: { title: "iOS title" } },
759
+ },
760
+ };
761
+ expect(getTitleForDevice(templateData, "android")).toBe("Android title");
762
+ expect(getTitleForDevice(templateData, "ios")).toBe("iOS title");
763
+ });
764
+
765
+ it("returns empty string for missing title", () => {
766
+ expect(getTitleForDevice(null, "android")).toBe("");
767
+ expect(getTitleForDevice({}, "android")).toBe("");
768
+ expect(getTitleForDevice({ versions: { android: { base: {} } } }, "android")).toBe("");
769
+ });
770
+
771
+ it("returns empty string when base.title is undefined", () => {
772
+ expect(getTitleForDevice({ versions: { android: { base: { title: undefined } } } }, "android")).toBe("");
773
+ });
774
+ });
775
+
776
+ describe("checkForPersonalizationTokens", () => {
777
+ it("returns true when formData contains liquid or bracket tokens", () => {
778
+ expect(checkForPersonalizationTokens({ 0: { content: "Hi {{name}}" } })).toBe(true);
779
+ expect(checkForPersonalizationTokens({ tab1: { message: "Hello [event.id]" } })).toBe(true);
780
+ });
781
+
782
+ it("returns false for empty or no tokens", () => {
783
+ expect(checkForPersonalizationTokens(null)).toBe(false);
784
+ expect(checkForPersonalizationTokens({})).toBe(false);
785
+ expect(checkForPersonalizationTokens({ 0: { content: "plain" } })).toBe(false);
786
+ });
787
+
788
+ it("returns false for non-object formData", () => {
789
+ expect(checkForPersonalizationTokens("string")).toBe(false);
790
+ });
791
+
792
+ it("returns true for two-level nested object containing token", () => {
793
+ expect(checkForPersonalizationTokens({ tab: { body: "Hi {{name}}" } })).toBe(true);
794
+ });
795
+
796
+ it("returns false for formData with array value (no string tokens)", () => {
797
+ expect(checkForPersonalizationTokens({ 0: { items: ["a", "b"] } })).toBe(false);
798
+ });
799
+ });
800
+
632
801
  describe("validateMobilePushContent", () => {
633
802
  const formatMessage = jest.fn(msg => msg.id);
634
803
  const messages = {
@@ -242,7 +242,6 @@ describe("validateTags", () => {
242
242
  tagsParam,
243
243
  location,
244
244
  tagModule,
245
- waitEventContextTags: {},
246
245
  });
247
246
 
248
247
  expect(result.valid).toEqual(true);
@@ -274,7 +273,6 @@ describe("validateTags", () => {
274
273
  tagsParam: tagsParamLocal,
275
274
  location,
276
275
  tagModule,
277
- waitEventContextTags: {},
278
276
  });
279
277
 
280
278
  expect(result.valid).toEqual(true);
@@ -312,7 +310,6 @@ describe("validateTags", () => {
312
310
  tagsParam: tagsParamLocal,
313
311
  location,
314
312
  tagModule,
315
- waitEventContextTags: {},
316
313
  });
317
314
 
318
315
  expect(result.valid).toEqual(false);
@@ -338,7 +335,6 @@ describe("validateTags", () => {
338
335
  tagsParam: tagsParamUnsubscribe,
339
336
  location,
340
337
  tagModule,
341
- waitEventContextTags: {},
342
338
  });
343
339
  expect(resultMissing.missingTags).toContain("unsubscribe");
344
340
  expect(resultMissing.valid).toBe(false);
@@ -349,7 +345,6 @@ describe("validateTags", () => {
349
345
  tagsParam: tagsParamUnsubscribe,
350
346
  location,
351
347
  tagModule,
352
- waitEventContextTags: {},
353
348
  });
354
349
  expect(resultSkipped.missingTags).not.toContain("unsubscribe");
355
350
  expect(resultSkipped.valid).toBe(true);
@@ -365,35 +360,6 @@ describe("validateTags", () => {
365
360
  expect(resultWhitespace.valid).toBe(true);
366
361
  expect(resultWhitespace.unsupportedTags ?? []).toEqual([]);
367
362
  });
368
-
369
- it('should treat tags from waitEventContextTags as supported', () => {
370
- const content = 'Hello {{waitEvent.orderId}}';
371
- const tagsParam = [];
372
- const injectedTagsParams = [];
373
- const location = { query: { module: 'DEFAULT' } };
374
- const tagModule = null;
375
- const waitEventContextTags = {
376
- block1: {
377
- eventName: 'Order Placed',
378
- blockName: 'Wait Block',
379
- tags: [{ tagName: 'waitEvent.orderId', label: 'Order ID' }],
380
- },
381
- };
382
-
383
- const result = validateTags({
384
- content,
385
- tagsParam,
386
- injectedTagsParams,
387
- location,
388
- tagModule,
389
- eventContextTags: [],
390
- waitEventContextTags,
391
- });
392
-
393
- expect(result.valid).toEqual(true);
394
- expect(result.missingTags).toEqual([]);
395
- expect(result.isBraceError).toEqual(false);
396
- });
397
363
  });
398
364
 
399
365
  describe('validateTags wrapper (v2 consumers)', () => {
@@ -676,7 +642,7 @@ describe('preprocessHtml', () => {
676
642
 
677
643
  it('should replace &quot; with double quotes', () => {
678
644
  const input = 'She said, &quot;Hello, World!&quot;';
679
- const expectedOutput = "She said, 'Hello, World!'";
645
+ const expectedOutput = 'She said, "Hello, World!"';
680
646
  expect(preprocessHtml(input)).toEqual(expectedOutput);
681
647
  });
682
648
 
@@ -207,7 +207,6 @@ class CapTagList extends React.Component { // eslint-disable-line react/prefer-s
207
207
  } else {
208
208
  this.props.onSelect(selectedKeys, info);
209
209
  this.setState({visible: false});
210
- this.setState({expandedKeys: []})
211
210
  }
212
211
  } else if (info && info.selectedNodes && info.selectedNodes.length > 0 && !info.selectedNodes[0].props.isLeaf) {
213
212
  this.handleOnExpand(selectedKeys[0]);
@@ -234,13 +233,6 @@ class CapTagList extends React.Component { // eslint-disable-line react/prefer-s
234
233
  }
235
234
  };
236
235
 
237
- /** Single-line ellipsis within popover width; full label on hover via CapTooltip. */
238
- wrapTreeTitle = (displayNode, text) => (
239
- <CapTooltip title={displayNode}>
240
- {text || displayNode}
241
- </CapTooltip>
242
- );
243
-
244
236
  renderDynamicTagFlow = () => {
245
237
  this.setState({showModal: true, visible: false});
246
238
  };
@@ -288,7 +280,7 @@ class CapTagList extends React.Component { // eslint-disable-line react/prefer-s
288
280
  if (temp?.length) {
289
281
  const tagValue = (
290
282
  <CapTreeNode
291
- title={disabled ? this.wrapTreeTitle(loyaltyAttrDisableText, val?.name) : this.wrapTreeTitle(val?.name)}
283
+ title={disabled ? <CapTooltip title={loyaltyAttrDisableText}>{val?.name}</CapTooltip> : val?.name}
292
284
  tag={val}
293
285
  key={val?.incentiveSeriesId ? `${key}(${val?.incentiveSeriesId})` : `${key}`}
294
286
  disabled={disabled}
@@ -311,9 +303,17 @@ class CapTagList extends React.Component { // eslint-disable-line react/prefer-s
311
303
  <CapTreeNode
312
304
  title={
313
305
  childDisabled ? (
314
- this.wrapTreeTitle(key === CUSTOMER_BARCODE_TAG ? customerBarcodeDisableText : loyaltyAttrDisableText, val?.desc || val?.name)
306
+ <CapTooltip
307
+ title={
308
+ key === CUSTOMER_BARCODE_TAG
309
+ ? customerBarcodeDisableText
310
+ : loyaltyAttrDisableText
311
+ }
312
+ >
313
+ {val?.desc || val?.name}
314
+ </CapTooltip>
315
315
  ) : (
316
- this.wrapTreeTitle(val?.desc || val?.name)
316
+ val?.desc || val?.name
317
317
  )
318
318
  }
319
319
  tag={val}
@@ -339,9 +339,17 @@ class CapTagList extends React.Component { // eslint-disable-line react/prefer-s
339
339
  <CapTreeNode
340
340
  title={
341
341
  childDisabled ? (
342
- this.wrapTreeTitle(key === CUSTOMER_BARCODE_TAG ? customerBarcodeDisableText : loyaltyAttrDisableText, val?.desc || val?.name)
342
+ <CapTooltip
343
+ title={
344
+ key === CUSTOMER_BARCODE_TAG
345
+ ? customerBarcodeDisableText
346
+ : loyaltyAttrDisableText
347
+ }
348
+ >
349
+ {val?.desc || val?.name}
350
+ </CapTooltip>
343
351
  ) : (
344
- this.wrapTreeTitle(val?.desc || val?.name)
352
+ val?.desc || val?.name
345
353
  )
346
354
  }
347
355
  tag={val}
@@ -399,7 +407,7 @@ class CapTagList extends React.Component { // eslint-disable-line react/prefer-s
399
407
  },
400
408
  ];
401
409
  const contentSection = (
402
- <CapRow className="cap-tag-list-popover-inner">
410
+ <CapRow>
403
411
  <CapSpin tip={formatMessage(messages.gettingTags)} spinning={shouldShowLoading}>
404
412
  <Search
405
413
  style={{ marginBottom: 8, width: '250px'}}
@@ -1,53 +1,5 @@
1
1
  @import "~@capillarytech/cap-ui-library/styles/_variables";
2
2
 
3
- // Tag list popover: keep overlay width aligned with search (250px); tree rows ellipsis + tooltip for full text
4
- .cap-tag-list-popover-overlay.ant-popover {
5
- .ant-popover-inner-content {
6
- max-width: 20rem;
7
- box-sizing: border-box;
8
- overflow: hidden;
9
- }
10
- }
11
-
12
- .cap-tag-list-popover-inner {
13
- max-width: 20rem;
14
- min-width: 0;
15
- box-sizing: border-box;
16
-
17
- .ant-tree.cap-tree-v2.ant-tree-icon-hide {
18
- width: 100%;
19
- max-width: 100%;
20
-
21
- ul {
22
- max-width: 100%;
23
- }
24
-
25
- li {
26
- overflow: hidden;
27
- max-width: 100%;
28
- }
29
-
30
- li .ant-tree-node-content-wrapper {
31
- width: calc(100% - 3.5rem); // leave room for switcher (~24px)
32
- max-width: calc(100% - 3.5rem);
33
- overflow: hidden;
34
- vertical-align: top;
35
- box-sizing: border-box;
36
- }
37
-
38
- .cap-tag-list-tree-title-wrap {
39
- display: inline-block;
40
- width: 100%;
41
- overflow: hidden;
42
- text-overflow: ellipsis;
43
- white-space: nowrap;
44
- max-width: 100%;
45
- vertical-align: top;
46
- margin-top: 0.5rem;
47
- }
48
- }
49
- }
50
-
51
3
  @media (max-height: 25rem) {
52
4
  .ant-tree.cap-tree-v2.ant-tree-icon-hide {
53
5
  height: 8.5714rem;
@@ -27,7 +27,6 @@ export const CapTagListWithInput = (props) => {
27
27
  userLocale = 'en',
28
28
  eventContextTags = [],
29
29
  restrictPersonalization = false,
30
- waitEventContextTags = {},
31
30
  // CapInput props
32
31
  inputId,
33
32
  inputValue = '',
@@ -78,7 +77,6 @@ export const CapTagListWithInput = (props) => {
78
77
  userLocale={userLocale}
79
78
  selectedOfferDetails={selectedOfferDetails}
80
79
  eventContextTags={eventContextTags}
81
- waitEventContextTags={waitEventContextTags}
82
80
  style={tagListStyle}
83
81
  popoverPlacement={popoverPlacement}
84
82
  restrictPersonalization={restrictPersonalization}
@@ -118,7 +116,6 @@ CapTagListWithInput.propTypes = {
118
116
  userLocale: PropTypes.string,
119
117
  eventContextTags: PropTypes.array,
120
118
  restrictPersonalization: PropTypes.bool,
121
- waitEventContextTags: PropTypes.object,
122
119
 
123
120
  // CapInput props
124
121
  inputId: PropTypes.string.isRequired,
@@ -157,7 +154,6 @@ CapTagListWithInput.defaultProps = {
157
154
  userLocale: 'en',
158
155
  eventContextTags: [],
159
156
  restrictPersonalization: false,
160
- waitEventContextTags: {},
161
157
  inputValue: '',
162
158
  inputSize: 'default',
163
159
  inputRequired: false,
@@ -52,7 +52,6 @@ export const CapWhatsappCTA = (props) => {
52
52
  injectedTags = {},
53
53
  selectedOfferDetails = [],
54
54
  eventContextTags = [],
55
- waitEventContextTags = {},
56
55
  } = props;
57
56
  const { formatMessage } = intl;
58
57
  const invalidVarRegex = /{{(.*?)}}/g;
@@ -284,7 +283,6 @@ export const CapWhatsappCTA = (props) => {
284
283
  injectedTags={injectedTags}
285
284
  selectedOfferDetails={selectedOfferDetails}
286
285
  eventContextTags={eventContextTags}
287
- waitEventContextTags={waitEventContextTags}
288
286
  />
289
287
  </CapColumn>
290
288
  )}
@@ -0,0 +1,42 @@
1
+ import PropTypes from "prop-types";
2
+ import { FormattedMessage } from "react-intl";
3
+ import CapButton from "@capillarytech/cap-ui-library/CapButton";
4
+ import CapIcon from "@capillarytech/cap-ui-library/CapIcon";
5
+ import messages from "./messages";
6
+ import React from "react";
7
+
8
+ const AddTestCustomerButton = ({
9
+ searchValue,
10
+ handleAddTestCustomer
11
+ }) => (
12
+ <CapButton
13
+ onClick={handleAddTestCustomer}
14
+ type="flat"
15
+ size="small"
16
+ className="test-customer-add-btn"
17
+ prefix={
18
+ <CapIcon
19
+ type="add-profile"
20
+ className="add-test-customer-icon"
21
+ />
22
+ }
23
+ >
24
+ <FormattedMessage
25
+ {...messages.addTestCustomerWithValue}
26
+ values={{
27
+ searchValue: (
28
+ <span className="test-customer-add-btn-value" title={searchValue || ""}>
29
+ "{searchValue}"
30
+ </span>
31
+ ),
32
+ }}
33
+ />
34
+ </CapButton>
35
+ );
36
+
37
+ AddTestCustomerButton.propTypes = {
38
+ searchValue: PropTypes.string.isRequired,
39
+ handleAddTestCustomer: PropTypes.func.isRequired
40
+ };
41
+
42
+ export default AddTestCustomerButton;