@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.
- package/package.json +1 -1
- package/services/api.js +17 -0
- package/services/tests/api.test.js +85 -0
- package/utils/commonUtils.js +10 -0
- package/utils/tagValidations.js +2 -3
- package/utils/tests/commonUtil.test.js +169 -0
- package/utils/tests/tagValidations.test.js +1 -35
- package/v2Components/CapTagList/index.js +22 -14
- package/v2Components/CapTagList/style.scss +0 -48
- package/v2Components/CapTagListWithInput/index.js +0 -4
- package/v2Components/CapWhatsappCTA/index.js +0 -2
- package/v2Components/CommonTestAndPreview/AddTestCustomer.js +42 -0
- package/v2Components/CommonTestAndPreview/CustomerCreationModal.js +155 -0
- package/v2Components/CommonTestAndPreview/ExistingCustomerModal.js +93 -0
- package/v2Components/CommonTestAndPreview/SendTestMessage.js +79 -51
- package/v2Components/CommonTestAndPreview/_commonTestAndPreview.scss +135 -36
- package/v2Components/CommonTestAndPreview/actions.js +10 -0
- package/v2Components/CommonTestAndPreview/constants.js +15 -1
- package/v2Components/CommonTestAndPreview/index.js +315 -15
- package/v2Components/CommonTestAndPreview/messages.js +106 -0
- package/v2Components/CommonTestAndPreview/reducer.js +10 -0
- package/v2Components/CommonTestAndPreview/tests/AddTestCustomer.test.js +66 -0
- package/v2Components/CommonTestAndPreview/tests/CommonTestAndPreview.addTestCustomer.test.js +648 -0
- package/v2Components/CommonTestAndPreview/tests/CustomValuesEditor.test.js +24 -0
- package/v2Components/CommonTestAndPreview/tests/CustomerCreationModal.test.js +174 -0
- package/v2Components/CommonTestAndPreview/tests/ExistingCustomerModal.test.js +114 -0
- package/v2Components/CommonTestAndPreview/tests/SendTestMessage.test.js +52 -0
- package/v2Components/CommonTestAndPreview/tests/constants.test.js +31 -1
- package/v2Components/CommonTestAndPreview/tests/index.test.js +36 -0
- package/v2Components/CommonTestAndPreview/tests/reducer.test.js +71 -0
- package/v2Components/CommonTestAndPreview/tests/selectors.test.js +17 -0
- package/v2Components/FormBuilder/index.js +0 -7
- package/v2Components/HtmlEditor/HTMLEditor.js +1 -6
- package/v2Components/HtmlEditor/__tests__/HTMLEditor.apiErrors.test.js +0 -1
- package/v2Components/HtmlEditor/__tests__/HTMLEditor.test.js +2 -927
- package/v2Components/HtmlEditor/components/CodeEditorPane/index.js +0 -3
- package/v2Containers/BeeEditor/index.js +0 -3
- package/v2Containers/CreativesContainer/SlideBoxContent.js +1 -28
- package/v2Containers/CreativesContainer/index.js +6 -10
- package/v2Containers/Email/index.js +0 -1
- package/v2Containers/EmailWrapper/components/EmailHTMLEditor.js +1 -7
- package/v2Containers/EmailWrapper/components/EmailWrapperView.js +0 -3
- package/v2Containers/EmailWrapper/components/__tests__/EmailHTMLEditor.test.js +2 -20
- package/v2Containers/EmailWrapper/components/__tests__/EmailWrapperView.test.js +1 -16
- package/v2Containers/EmailWrapper/hooks/useEmailWrapper.js +0 -3
- package/v2Containers/EmailWrapper/index.js +0 -4
- package/v2Containers/EmailWrapper/tests/useEmailWrapper.edgeCases.test.js +0 -1
- package/v2Containers/EmailWrapper/tests/useEmailWrapper.test.js +0 -9
- package/v2Containers/InAppWrapper/hooks/__tests__/useInAppWrapper.test.js +0 -19
- package/v2Containers/InAppWrapper/hooks/useInAppWrapper.js +0 -3
- package/v2Containers/InAppWrapper/index.js +0 -3
- package/v2Containers/MobilePush/Create/index.js +0 -2
- package/v2Containers/MobilePush/Edit/index.js +0 -2
- package/v2Containers/MobilepushWrapper/index.js +1 -3
- package/v2Containers/Rcs/index.js +1 -9
- package/v2Containers/Rcs/tests/__snapshots__/index.test.js.snap +1886 -1754
- package/v2Containers/Sms/Create/index.js +0 -2
- package/v2Containers/Sms/Edit/index.js +0 -2
- package/v2Containers/SmsTrai/Edit/index.js +0 -2
- package/v2Containers/SmsTrai/Edit/tests/__snapshots__/index.test.js.snap +351 -318
- package/v2Containers/SmsWrapper/index.js +0 -2
- package/v2Containers/TagList/index.js +2 -41
- package/v2Containers/TagList/messages.js +0 -4
- package/v2Containers/TagList/tests/TagList.test.js +20 -122
- package/v2Containers/TagList/tests/mockdata.js +0 -17
- package/v2Containers/Viber/index.js +0 -5
- package/v2Containers/WebPush/Create/hooks/useTagManagement.js +2 -0
- package/v2Containers/WebPush/Create/index.js +1 -9
- package/v2Containers/Whatsapp/index.js +0 -5
- package/v2Containers/Whatsapp/tests/__snapshots__/index.test.js.snap +5586 -5232
- package/v2Containers/Zalo/index.js +0 -2
- package/v2Components/CapTagListWithInput/__tests__/CapTagListWithInput.test.js +0 -63
package/package.json
CHANGED
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
|
+
});
|
package/utils/commonUtils.js
CHANGED
|
@@ -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
|
+
};
|
package/utils/tagValidations.js
CHANGED
|
@@ -207,8 +207,7 @@ export const validateIfTagClosed = (value) => {
|
|
|
207
207
|
export const preprocessHtml = (content) => {
|
|
208
208
|
const replacements = {
|
|
209
209
|
"'": "'",
|
|
210
|
-
""": "'
|
|
211
|
-
'"': "'",
|
|
210
|
+
""": '"',
|
|
212
211
|
"&": "&",
|
|
213
212
|
"<": "<",
|
|
214
213
|
">": ">",
|
|
@@ -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(/'|"|&|<|>
|
|
228
|
+
return contentWithStyleFixes?.replace(/'|"|&|<|>|\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 " with double quotes', () => {
|
|
678
644
|
const input = 'She said, "Hello, World!"';
|
|
679
|
-
const expectedOutput =
|
|
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 ?
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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;
|