@capillarytech/creatives-library 8.0.330-alpha.0 → 8.0.330-alpha.1
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/utils/tests/tagValidations.test.js +20 -0
- package/v2Components/CapActionButton/constants.js +7 -0
- package/v2Components/CapActionButton/index.js +167 -109
- package/v2Components/CapActionButton/index.scss +157 -6
- package/v2Components/CapActionButton/messages.js +19 -3
- package/v2Components/CapActionButton/tests/index.test.js +41 -17
- package/v2Components/CapTagList/index.js +28 -23
- package/v2Components/CapTagList/style.scss +29 -0
- package/v2Components/CapTagListWithInput/__tests__/CapTagListWithInput.test.js +63 -0
- package/v2Components/CapTagListWithInput/index.js +4 -0
- package/v2Components/CapWhatsappCTA/index.js +2 -0
- package/v2Components/CommonTestAndPreview/ExistingCustomerModal.js +1 -0
- package/v2Components/CommonTestAndPreview/UnifiedPreview/RcsPreviewContent.js +160 -15
- package/v2Components/CommonTestAndPreview/UnifiedPreview/RcsPreviewContent.js.rej +18 -0
- package/v2Components/CommonTestAndPreview/UnifiedPreview/_unifiedPreview.scss +323 -77
- package/v2Components/CommonTestAndPreview/messages.js +8 -0
- package/v2Components/CommonTestAndPreview/reducer.js +3 -1
- package/v2Components/CommonTestAndPreview/sagas.js +2 -1
- package/v2Components/CommonTestAndPreview/tests/PreviewSection.test.js +8 -1
- package/v2Components/CommonTestAndPreview/tests/UnifiedPreview/RcsPreviewContent.test.js +281 -283
- package/v2Components/FormBuilder/index.js +1 -0
- package/v2Components/HtmlEditor/HTMLEditor.js +6 -1
- package/v2Components/HtmlEditor/__tests__/HTMLEditor.apiErrors.test.js +1 -0
- package/v2Components/HtmlEditor/__tests__/HTMLEditor.test.js +927 -2
- package/v2Components/HtmlEditor/components/CodeEditorPane/index.js +3 -0
- package/v2Components/TemplatePreview/_templatePreview.scss +33 -23
- package/v2Components/TemplatePreview/constants.js +2 -0
- package/v2Components/TemplatePreview/index.js +143 -28
- package/v2Components/TemplatePreview/tests/index.test.js +142 -0
- package/v2Components/mockdata.js +1 -0
- package/v2Containers/BeeEditor/index.js +19 -1
- package/v2Containers/CreativesContainer/SlideBoxContent.js +28 -1
- package/v2Containers/CreativesContainer/tests/__snapshots__/index.test.js.snap +5 -0
- package/v2Containers/Email/index.js +78 -39
- package/v2Containers/Email/reducer.js +2 -2
- package/v2Containers/Email/sagas.js +3 -1
- package/v2Containers/Email/tests/__snapshots__/reducer.test.js.snap +2 -2
- package/v2Containers/Email/tests/sagas.test.js +230 -0
- package/v2Containers/EmailWrapper/components/EmailHTMLEditor.js +6 -1
- package/v2Containers/EmailWrapper/components/EmailWrapperView.js +3 -0
- package/v2Containers/EmailWrapper/components/__tests__/EmailHTMLEditor.test.js +20 -2
- package/v2Containers/EmailWrapper/components/__tests__/EmailWrapperView.test.js +16 -1
- package/v2Containers/EmailWrapper/hooks/useEmailWrapper.js +3 -1
- package/v2Containers/EmailWrapper/index.js +4 -0
- package/v2Containers/EmailWrapper/tests/useEmailWrapper.edgeCases.test.js +1 -0
- package/v2Containers/EmailWrapper/tests/useEmailWrapper.test.js +9 -0
- package/v2Containers/InAppWrapper/hooks/__tests__/useInAppWrapper.test.js +1 -0
- package/v2Containers/MobilePush/Create/index.js +2 -0
- package/v2Containers/MobilePush/Edit/index.js +2 -0
- package/v2Containers/MobilepushWrapper/index.js +3 -1
- package/v2Containers/Rcs/constants.js +79 -5
- package/v2Containers/Rcs/index.js +1374 -73
- package/v2Containers/Rcs/index.js.rej +1336 -0
- package/v2Containers/Rcs/index.scss +191 -0
- package/v2Containers/Rcs/index.scss.rej +74 -0
- package/v2Containers/Rcs/messages.js +26 -1
- package/v2Containers/Rcs/tests/__snapshots__/index.test.js.snap +69173 -118166
- package/v2Containers/Rcs/tests/__snapshots__/utils.test.js.snap.rej +128 -0
- package/v2Containers/Rcs/tests/index.test.js +132 -94
- package/v2Containers/Rcs/tests/utils.test.js +220 -38
- package/v2Containers/Rcs/utils.js +77 -1
- package/v2Containers/Sms/Edit/index.js +2 -0
- package/v2Containers/SmsWrapper/index.js +2 -0
- package/v2Containers/TagList/index.js +73 -20
- package/v2Containers/TagList/messages.js +4 -0
- package/v2Containers/TagList/tests/TagList.test.js +124 -20
- package/v2Containers/TagList/tests/mockdata.js +17 -0
- package/v2Containers/Templates/_templates.scss +99 -0
- package/v2Containers/Templates/index.js +29 -14
- package/v2Containers/Viber/index.js +3 -0
- package/v2Containers/WebPush/Create/hooks/useTagManagement.js +0 -2
- package/v2Containers/WebPush/Create/index.js +10 -2
- package/v2Containers/Whatsapp/index.js +5 -0
- package/v2Containers/Zalo/index.js +2 -0
|
@@ -1,14 +1,27 @@
|
|
|
1
1
|
@import '~@capillarytech/cap-ui-library/styles/_variables';
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
// Horizontal RCS button type radios (Phone number | URL | Quick reply)
|
|
4
|
+
.cap-rcs-cta-type-radio.cap-radio-group-v2 {
|
|
5
|
+
display: flex;
|
|
6
|
+
flex-wrap: wrap;
|
|
7
|
+
align-items: center;
|
|
8
|
+
margin-top: 0.5rem;
|
|
9
|
+
|
|
10
|
+
.ant-radio-wrapper {
|
|
11
|
+
margin-right: $CAP_SPACE_24;
|
|
12
|
+
margin-bottom: 0;
|
|
13
|
+
|
|
14
|
+
&:last-child {
|
|
15
|
+
margin-right: 0;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
5
18
|
}
|
|
6
19
|
|
|
7
20
|
.cap-rcs-saved-cta {
|
|
8
21
|
position: relative;
|
|
9
22
|
border: solid $CAP_SPACE_01 $CAP_G06;
|
|
10
23
|
padding: 0.625rem;
|
|
11
|
-
border-radius:
|
|
24
|
+
border-radius: 0.25rem; // 4px
|
|
12
25
|
margin-bottom: $CAP_SPACE_12;
|
|
13
26
|
|
|
14
27
|
div:not(:last-child) {
|
|
@@ -39,9 +52,139 @@
|
|
|
39
52
|
}
|
|
40
53
|
}
|
|
41
54
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
55
|
+
// Button text / URL: count via CapInput `suffix` (antd affix, right-aligned inside field).
|
|
56
|
+
// Phone: react-phone-input-2 — overlay count + padding on .form-control
|
|
57
|
+
.rcs-cta-inner-char-count {
|
|
58
|
+
font-size: 0.75rem;
|
|
59
|
+
line-height: 1.25rem;
|
|
60
|
+
color: $FONT_COLOR_03;
|
|
61
|
+
white-space: nowrap;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
.rcs-cta-input-with-inner-count {
|
|
65
|
+
position: relative;
|
|
66
|
+
width: 100%;
|
|
67
|
+
|
|
68
|
+
&--phone .rcs-cta-inner-char-count {
|
|
69
|
+
position: absolute;
|
|
70
|
+
right: 0.6875rem;
|
|
71
|
+
top: 50%;
|
|
72
|
+
transform: translateY(-50%);
|
|
73
|
+
z-index: 2;
|
|
74
|
+
pointer-events: none;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// RCS phone field: Figma — one light border, 4px radius, ~antd Input large height, full width, count inside right
|
|
79
|
+
.rcs-button-cta-create-container .rcs-cta-input-with-inner-count--phone {
|
|
80
|
+
.react-tel-input.rcs-cta-phone-input {
|
|
81
|
+
width: 100%;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
.rcs-cta-phone-input .form-control {
|
|
85
|
+
width: 100% !important;
|
|
86
|
+
max-width: none;
|
|
87
|
+
height: 2.5rem;
|
|
88
|
+
min-height: 2.5rem;
|
|
89
|
+
padding-top: 0;
|
|
90
|
+
padding-bottom: 0;
|
|
91
|
+
padding-left: 3rem;
|
|
92
|
+
padding-right: 3.5rem;
|
|
93
|
+
margin: 0;
|
|
94
|
+
font-size: 0.875rem;
|
|
95
|
+
line-height: 2.5rem;
|
|
96
|
+
color: $CAP_G01;
|
|
97
|
+
background: $CAP_WHITE;
|
|
98
|
+
border: 1px solid $CAP_G06;
|
|
99
|
+
border-radius: 0.25rem;
|
|
100
|
+
box-shadow: none;
|
|
101
|
+
|
|
102
|
+
&::placeholder {
|
|
103
|
+
color: $CAP_G05;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
&:hover {
|
|
107
|
+
border-color: $CAP_G11;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
&:focus {
|
|
111
|
+
border-color: $CAP_G01;
|
|
112
|
+
box-shadow: none;
|
|
113
|
+
outline: none;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
.rcs-cta-phone-input .flag-dropdown {
|
|
118
|
+
top: 1px;
|
|
119
|
+
bottom: 1px;
|
|
120
|
+
left: 1px;
|
|
121
|
+
padding: 0;
|
|
122
|
+
background: $CAP_WHITE;
|
|
123
|
+
border: none;
|
|
124
|
+
border-radius: 0.1875rem 0 0 0.1875rem; // inset from outer 4px radius
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
.rcs-cta-phone-input .flag-dropdown.open {
|
|
128
|
+
border-radius: 0.1875rem 0 0 0;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
.rcs-cta-phone-input .selected-flag {
|
|
132
|
+
width: 2.75rem;
|
|
133
|
+
padding: 0 0 0 0.5rem;
|
|
134
|
+
background: transparent;
|
|
135
|
+
border-radius: 0.1875rem 0 0 0.1875rem;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
.rcs-cta-phone-input .selected-flag:hover,
|
|
139
|
+
.rcs-cta-phone-input .selected-flag:focus {
|
|
140
|
+
background: $CAP_G09;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
.rcs-cta-phone-input .selected-flag .arrow {
|
|
144
|
+
left: 1.375rem;
|
|
145
|
+
border-top-color: $CAP_G04;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// URL CTA: URL type (narrow) + URL field (~1:3) per Figma
|
|
150
|
+
.rcs-cta-url-fields-row {
|
|
151
|
+
.rcs-cta-url-type-col .ant-select {
|
|
152
|
+
width: 100%;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// RCS “create CTA” card: 0.5rem padding + gaps (Figma-tight; fixes excess space above first label)
|
|
157
|
+
.rcs-button-cta-create-container.cap-row-v2 {
|
|
158
|
+
display: flex;
|
|
159
|
+
flex-direction: column;
|
|
160
|
+
flex-wrap: nowrap;
|
|
161
|
+
width: 100%;
|
|
162
|
+
border: solid 1px $CAP_G06;
|
|
163
|
+
padding: 1.25rem;
|
|
164
|
+
border-radius: 0.25rem; // 4px — match .cap-rcs-saved-cta
|
|
165
|
+
|
|
166
|
+
// Stack “Button type” (radios) above “Button text”
|
|
167
|
+
.rcs-button-cta-create.cap-row-v2 {
|
|
168
|
+
display: flex;
|
|
169
|
+
flex-direction: column;
|
|
170
|
+
flex-wrap: nowrap;
|
|
171
|
+
margin: 0;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// Space after each field label (CapHeading margins are often zeroed by the library)
|
|
175
|
+
.cta-label {
|
|
176
|
+
margin-bottom: 0;
|
|
177
|
+
margin-top: 0.75rem;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
.cta-label + * {
|
|
181
|
+
margin-top: 0.5rem;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// “Button type” heading has no .cta-label — keep same label→control gap as other fields
|
|
185
|
+
.rcs-button-cta-create > .ant-col:first-of-type .cap-rcs-cta-type-radio {
|
|
186
|
+
margin-top: 0.5rem;
|
|
187
|
+
}
|
|
45
188
|
}
|
|
46
189
|
|
|
47
190
|
.disabled {
|
|
@@ -55,4 +198,12 @@
|
|
|
55
198
|
|
|
56
199
|
.button-disabled-tooltip-wrapper {
|
|
57
200
|
display: inline-block;
|
|
201
|
+
margin-top: 1.25rem;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// Space between Save and Delete on RCS / carousel CTA rows
|
|
205
|
+
.rcs-cta-save-delete-btn {
|
|
206
|
+
.rcs-cta-delete-btn {
|
|
207
|
+
margin-left: 0.75rem;
|
|
208
|
+
}
|
|
58
209
|
}
|
|
@@ -12,7 +12,7 @@ export default defineMessages({
|
|
|
12
12
|
},
|
|
13
13
|
ctaQr: {
|
|
14
14
|
id: `${prefix}.ctaQr`,
|
|
15
|
-
defaultMessage: 'Quick
|
|
15
|
+
defaultMessage: 'Quick reply',
|
|
16
16
|
},
|
|
17
17
|
ctaPhoneNo: {
|
|
18
18
|
id: `${prefix}.ctaPhoneNo`,
|
|
@@ -47,9 +47,17 @@ export default defineMessages({
|
|
|
47
47
|
id: `${prefix}.templateMessageLength`,
|
|
48
48
|
defaultMessage: 'Characters count: {currentLength}/{maxLength}',
|
|
49
49
|
},
|
|
50
|
+
ctaFieldCharCountInline: {
|
|
51
|
+
id: `${prefix}.ctaFieldCharCountInline`,
|
|
52
|
+
defaultMessage: '{current}/{max}',
|
|
53
|
+
},
|
|
50
54
|
ctaType: {
|
|
51
55
|
id: `${prefix}.ctaType`,
|
|
52
|
-
defaultMessage: '
|
|
56
|
+
defaultMessage: 'Button type',
|
|
57
|
+
},
|
|
58
|
+
ctaUrlRadio: {
|
|
59
|
+
id: `${prefix}.ctaUrlRadio`,
|
|
60
|
+
defaultMessage: 'URL',
|
|
53
61
|
},
|
|
54
62
|
templateButtonTextPlaceholder: {
|
|
55
63
|
id: `${prefix}.templateButtonTextPlaceholder`,
|
|
@@ -145,7 +153,15 @@ export default defineMessages({
|
|
|
145
153
|
},
|
|
146
154
|
ctaWebsiteType: {
|
|
147
155
|
id: `${prefix}.ctaWebsiteType`,
|
|
148
|
-
defaultMessage: 'URL
|
|
156
|
+
defaultMessage: 'URL type',
|
|
157
|
+
},
|
|
158
|
+
ctaUrlField: {
|
|
159
|
+
id: `${prefix}.ctaUrlField`,
|
|
160
|
+
defaultMessage: 'URL',
|
|
161
|
+
},
|
|
162
|
+
ctaEnterUrlPlaceholder: {
|
|
163
|
+
id: `${prefix}.ctaEnterUrlPlaceholder`,
|
|
164
|
+
defaultMessage: 'Enter URL',
|
|
149
165
|
},
|
|
150
166
|
ctaWebsiteTypeStatic: {
|
|
151
167
|
id: `${prefix}.ctaWebsiteTypeStatic`,
|
|
@@ -4,7 +4,7 @@ import '@testing-library/jest-dom';
|
|
|
4
4
|
import { render, screen, fireEvent } from '../../../utils/test-utils';
|
|
5
5
|
import { CapActionButton } from '../index';
|
|
6
6
|
import { BTN_MAX_LENGTH, PHONE_NUMBER_MAX_LENGTH, URL_MAX_LENGTH } from '../constants';
|
|
7
|
-
import { RCS_BUTTON_TYPES } from '../../../v2Containers/Rcs/constants';
|
|
7
|
+
import { RCS_BUTTON_TYPES, HOST_ICS } from '../../../v2Containers/Rcs/constants';
|
|
8
8
|
|
|
9
9
|
const updateHandler = jest.fn();
|
|
10
10
|
const deleteHandler = jest.fn();
|
|
@@ -12,6 +12,7 @@ const initializeComponent = (
|
|
|
12
12
|
data,
|
|
13
13
|
isEditFlow = false,
|
|
14
14
|
maxButtons = 3,
|
|
15
|
+
host = '',
|
|
15
16
|
) => {
|
|
16
17
|
// Normalize legacy test data shape to match component props
|
|
17
18
|
const normalizeType = (legacyType) => {
|
|
@@ -37,6 +38,7 @@ const initializeComponent = (
|
|
|
37
38
|
isEditFlow={isEditFlow}
|
|
38
39
|
maxButtons={maxButtons}
|
|
39
40
|
isFullMode={true}
|
|
41
|
+
host={host}
|
|
40
42
|
/>,
|
|
41
43
|
);
|
|
42
44
|
};
|
|
@@ -59,7 +61,7 @@ describe('CapActionButton', () => {
|
|
|
59
61
|
isSaved: false,
|
|
60
62
|
},
|
|
61
63
|
]);
|
|
62
|
-
expect(screen.getByText('
|
|
64
|
+
expect(screen.getByText('Button type')).toBeInTheDocument();
|
|
63
65
|
expect(screen.getByText('Button text')).toBeInTheDocument();
|
|
64
66
|
expect(screen.getAllByText('Phone number')[1]).toBeInTheDocument();
|
|
65
67
|
expect(screen.getByRole('button', { name: /save/i })).toBeDisabled();
|
|
@@ -122,7 +124,7 @@ describe('CapActionButton', () => {
|
|
|
122
124
|
id: 1,
|
|
123
125
|
},
|
|
124
126
|
};
|
|
125
|
-
const urlInput = await screen.getByPlaceholderText('Enter
|
|
127
|
+
const urlInput = await screen.getByPlaceholderText('Enter URL');
|
|
126
128
|
fireEvent.change(urlInput, urlEvent);
|
|
127
129
|
expect(updateHandler).toHaveBeenCalledWith(
|
|
128
130
|
{
|
|
@@ -162,6 +164,27 @@ describe('CapActionButton', () => {
|
|
|
162
164
|
);
|
|
163
165
|
});
|
|
164
166
|
|
|
167
|
+
it('should show delete for quick reply index 0 for non-ICS hosts but hide it for ICS (stop button)', () => {
|
|
168
|
+
const button = {
|
|
169
|
+
index: 0,
|
|
170
|
+
type: RCS_BUTTON_TYPES.QUICK_REPLY,
|
|
171
|
+
text: 'Reply',
|
|
172
|
+
phoneNumber: '',
|
|
173
|
+
url: '',
|
|
174
|
+
postback: 'Reply',
|
|
175
|
+
isSaved: false,
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
// Non-ICS (Infobip/others): delete should be available
|
|
179
|
+
const { unmount } = initializeComponent([button], false, 3, 'rcsinfobipbulk');
|
|
180
|
+
expect(screen.getByRole('button', { name: /delete/i })).toBeInTheDocument();
|
|
181
|
+
unmount();
|
|
182
|
+
|
|
183
|
+
// ICS: index 0 is reserved; delete should be hidden
|
|
184
|
+
initializeComponent([button], false, 3, HOST_ICS);
|
|
185
|
+
expect(screen.queryByRole('button', { name: /delete/i })).not.toBeInTheDocument();
|
|
186
|
+
});
|
|
187
|
+
|
|
165
188
|
it('should respect character length limits', async () => {
|
|
166
189
|
const initialData = {
|
|
167
190
|
index: 0,
|
|
@@ -198,7 +221,7 @@ describe('CapActionButton', () => {
|
|
|
198
221
|
isSaved: false,
|
|
199
222
|
};
|
|
200
223
|
initializeComponent([initialData]);
|
|
201
|
-
const urlInput = await screen.getByPlaceholderText('Enter
|
|
224
|
+
const urlInput = await screen.getByPlaceholderText('Enter URL');
|
|
202
225
|
const longUrl = 'https://example.com/' + 'a'.repeat(URL_MAX_LENGTH + 1);
|
|
203
226
|
fireEvent.change(urlInput, { target: { value: longUrl, id: 0 } });
|
|
204
227
|
expect(urlInput.value.length).toBeLessThanOrEqual(URL_MAX_LENGTH);
|
|
@@ -245,7 +268,8 @@ describe('CapActionButton', () => {
|
|
|
245
268
|
{ index: 0, ctaType: RCS_BUTTON_TYPES.QUICK_REPLY, displayText: 'stop', phoneNumber: '', url: '', postback: 'stop', isSaved: true },
|
|
246
269
|
{ index: 1, ctaType: RCS_BUTTON_TYPES.QUICK_REPLY, displayText: 'Saved', phoneNumber: '', url: '', postback: 'Saved', isSaved: true },
|
|
247
270
|
];
|
|
248
|
-
|
|
271
|
+
// Use ICS host so index 0 (STOP) stays non-deletable and we only get delete icon for index 1
|
|
272
|
+
const { container } = initializeComponent(suggestions, false, 3, HOST_ICS);
|
|
249
273
|
const deleteIcons = container.querySelectorAll('.rcs-saved-cta-delete-icon');
|
|
250
274
|
expect(deleteIcons.length).toBeGreaterThan(0);
|
|
251
275
|
fireEvent.click(deleteIcons[0]);
|
|
@@ -303,7 +327,8 @@ describe('CapActionButton', () => {
|
|
|
303
327
|
postback: 'stop',
|
|
304
328
|
isSaved: true,
|
|
305
329
|
};
|
|
306
|
-
|
|
330
|
+
// Only ICS treats index 0 as a mandatory STOP quick reply
|
|
331
|
+
const { container } = initializeComponent([stopButton], false, 3, HOST_ICS);
|
|
307
332
|
expect(container.querySelector('.rcs-saved-cta-delete-icon')).not.toBeInTheDocument();
|
|
308
333
|
});
|
|
309
334
|
|
|
@@ -424,7 +449,7 @@ describe('CapActionButton', () => {
|
|
|
424
449
|
isSaved: false,
|
|
425
450
|
};
|
|
426
451
|
initializeComponent([button]);
|
|
427
|
-
expect(screen.getByPlaceholderText(/enter
|
|
452
|
+
expect(screen.getByPlaceholderText(/enter url/i)).toBeInTheDocument();
|
|
428
453
|
});
|
|
429
454
|
|
|
430
455
|
it('should update both displayText and postback when button text changes (updateDisplayAndPostback)', () => {
|
|
@@ -504,7 +529,7 @@ describe('CapActionButton', () => {
|
|
|
504
529
|
isSaved: false,
|
|
505
530
|
};
|
|
506
531
|
initializeComponent([initial]);
|
|
507
|
-
const urlInput = screen.getByPlaceholderText('Enter
|
|
532
|
+
const urlInput = screen.getByPlaceholderText('Enter URL');
|
|
508
533
|
fireEvent.change(urlInput, { target: { value: 'http://localhost:3030/creatives/ui/v2', id: 1 } });
|
|
509
534
|
expect(screen.getByText(/url is not valid/i)).toBeInTheDocument();
|
|
510
535
|
});
|
|
@@ -590,7 +615,7 @@ describe('CapActionButton', () => {
|
|
|
590
615
|
isSaved: false,
|
|
591
616
|
};
|
|
592
617
|
initializeComponent([initial]);
|
|
593
|
-
const urlInput = screen.getByPlaceholderText('Enter
|
|
618
|
+
const urlInput = screen.getByPlaceholderText('Enter URL');
|
|
594
619
|
// Enter invalid URL
|
|
595
620
|
fireEvent.change(urlInput, { target: { value: 'badurl', id: 0 } });
|
|
596
621
|
expect(screen.getByText(/url is not valid/i)).toBeInTheDocument();
|
|
@@ -849,15 +874,14 @@ describe('CapActionButton function logic', () => {
|
|
|
849
874
|
expect(renderCtaOptions(label, tooltipLabel, false)).toBe('Test');
|
|
850
875
|
});
|
|
851
876
|
|
|
852
|
-
it('
|
|
853
|
-
const
|
|
854
|
-
const
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
</CapHeading>
|
|
877
|
+
it('inner char count uses current/max format', () => {
|
|
878
|
+
const formatMessage = (msg, values) => `${values.current}/${values.max}`;
|
|
879
|
+
const renderInnerCharCount = (len, max) => (
|
|
880
|
+
<span className="rcs-cta-inner-char-count">
|
|
881
|
+
{formatMessage({}, { current: len, max })}
|
|
882
|
+
</span>
|
|
859
883
|
);
|
|
860
|
-
const result =
|
|
884
|
+
const result = renderInnerCharCount(5, 25);
|
|
861
885
|
expect(result.props.children).toBe('5/25');
|
|
862
886
|
});
|
|
863
887
|
});
|
|
@@ -44,6 +44,7 @@ class CapTagList extends React.Component { // eslint-disable-line react/prefer-s
|
|
|
44
44
|
super(props);
|
|
45
45
|
this.state = {
|
|
46
46
|
tagValue: '',
|
|
47
|
+
selectedNodeKey: '',
|
|
47
48
|
expandedKeys: [],
|
|
48
49
|
searchValue: '',
|
|
49
50
|
autoExpandParent: true,
|
|
@@ -122,9 +123,11 @@ class CapTagList extends React.Component { // eslint-disable-line react/prefer-s
|
|
|
122
123
|
this.handleOnExpand(info.node.props.eventKey);
|
|
123
124
|
};
|
|
124
125
|
|
|
125
|
-
getSearchedExpandedKeys(tags, value = '') {
|
|
126
|
+
getSearchedExpandedKeys(tags, value = '', parentPath = '') {
|
|
126
127
|
let list = [];
|
|
127
128
|
_.forEach(tags, (val = {}, key) => {
|
|
129
|
+
const rawKey = val?.incentiveSeriesId ? `${key}(${val?.incentiveSeriesId})` : `${key}`;
|
|
130
|
+
const nodeKey = parentPath ? `${parentPath}.${rawKey}` : rawKey;
|
|
128
131
|
const tagName =
|
|
129
132
|
typeof val?.name === 'string'
|
|
130
133
|
? _.toLower(_.get(val, "name", ""))
|
|
@@ -137,16 +140,16 @@ class CapTagList extends React.Component { // eslint-disable-line react/prefer-s
|
|
|
137
140
|
&& (tagName.includes(searchStringLower)
|
|
138
141
|
|| tagNameWithoutUnderscore.includes(searchStringLower))
|
|
139
142
|
) {
|
|
140
|
-
list.push(
|
|
143
|
+
list.push(nodeKey);
|
|
141
144
|
}
|
|
142
|
-
const temp = this.getSearchedExpandedKeys(val?.subtags, value);
|
|
145
|
+
const temp = this.getSearchedExpandedKeys(val?.subtags, value, nodeKey);
|
|
143
146
|
list = list.concat(temp);
|
|
144
147
|
} else if (
|
|
145
148
|
val?.name
|
|
146
149
|
&& (tagName.includes(searchStringLower)
|
|
147
150
|
|| tagNameWithoutUnderscore.includes(searchStringLower))
|
|
148
151
|
) {
|
|
149
|
-
list.push(
|
|
152
|
+
list.push(nodeKey);
|
|
150
153
|
}
|
|
151
154
|
});
|
|
152
155
|
return list;
|
|
@@ -200,12 +203,17 @@ class CapTagList extends React.Component { // eslint-disable-line react/prefer-s
|
|
|
200
203
|
handleOnSelect = (selectedKeys, info) => {
|
|
201
204
|
if (selectedKeys.length > 0) {
|
|
202
205
|
if (info && info.selectedNodes && info.selectedNodes.length > 0 && info.selectedNodes[0].props.isLeaf) {
|
|
203
|
-
|
|
204
|
-
const
|
|
206
|
+
const selectedNode = info.selectedNodes[0];
|
|
207
|
+
const selectedTagValue = selectedNode?.props?.tagKey || selectedKeys[0];
|
|
208
|
+
this.setState({
|
|
209
|
+
tagValue: selectedTagValue,
|
|
210
|
+
selectedNodeKey: selectedKeys[0],
|
|
211
|
+
});
|
|
212
|
+
const ifDynamicTag = this.checkIfDynamicTag(selectedTagValue);
|
|
205
213
|
if (ifDynamicTag) {
|
|
206
214
|
this.renderDynamicTagFlow();
|
|
207
215
|
} else {
|
|
208
|
-
this.props.onSelect(
|
|
216
|
+
this.props.onSelect([selectedTagValue], info);
|
|
209
217
|
this.setState({visible: false});
|
|
210
218
|
}
|
|
211
219
|
} else if (info && info.selectedNodes && info.selectedNodes.length > 0 && !info.selectedNodes[0].props.isLeaf) {
|
|
@@ -237,7 +245,7 @@ class CapTagList extends React.Component { // eslint-disable-line react/prefer-s
|
|
|
237
245
|
this.setState({showModal: true, visible: false});
|
|
238
246
|
};
|
|
239
247
|
|
|
240
|
-
renderTags = (tags) => {
|
|
248
|
+
renderTags = (tags, parentPath = '') => {
|
|
241
249
|
const searchString = this.state.searchValue || '';
|
|
242
250
|
const {
|
|
243
251
|
disableRelatedTags, childTagsToDisable, parentTagstoDisable,
|
|
@@ -260,6 +268,8 @@ class CapTagList extends React.Component { // eslint-disable-line react/prefer-s
|
|
|
260
268
|
clonedTags = _.omit(clonedTags, CUSTOMER_BARCODE_TAG);
|
|
261
269
|
}
|
|
262
270
|
_.forEach(clonedTags, (val = '', key) => {
|
|
271
|
+
const rawKey = val?.incentiveSeriesId ? `${key}(${val?.incentiveSeriesId})` : `${key}`;
|
|
272
|
+
const nodeKey = parentPath ? `${parentPath}.${rawKey}` : rawKey;
|
|
263
273
|
let supportedTagsString = '';
|
|
264
274
|
_.forEach(val.supportedTags, (supportedTag) => {
|
|
265
275
|
supportedTagsString += `${supportedTag} ,`;
|
|
@@ -276,13 +286,14 @@ class CapTagList extends React.Component { // eslint-disable-line react/prefer-s
|
|
|
276
286
|
|| tagNameWithoutUnderscore.includes(searchStringLower));
|
|
277
287
|
if (_.has(val, 'subtags')) {
|
|
278
288
|
const disabled = disableRelatedTags ? parentTagstoDisable.includes(key) : false;
|
|
279
|
-
const temp = this.renderTags(val?.subtags);
|
|
289
|
+
const temp = this.renderTags(val?.subtags, nodeKey);
|
|
280
290
|
if (temp?.length) {
|
|
281
291
|
const tagValue = (
|
|
282
292
|
<CapTreeNode
|
|
283
293
|
title={disabled ? <CapTooltip title={loyaltyAttrDisableText}>{val?.name}</CapTooltip> : val?.name}
|
|
284
294
|
tag={val}
|
|
285
|
-
|
|
295
|
+
tagKey={rawKey}
|
|
296
|
+
key={nodeKey}
|
|
286
297
|
disabled={disabled}
|
|
287
298
|
>
|
|
288
299
|
{temp}
|
|
@@ -317,12 +328,9 @@ class CapTagList extends React.Component { // eslint-disable-line react/prefer-s
|
|
|
317
328
|
)
|
|
318
329
|
}
|
|
319
330
|
tag={val}
|
|
331
|
+
tagKey={rawKey}
|
|
320
332
|
isLeaf
|
|
321
|
-
key={
|
|
322
|
-
val?.incentiveSeriesId
|
|
323
|
-
? `${key}(${val?.incentiveSeriesId})`
|
|
324
|
-
: `${key}`
|
|
325
|
-
}
|
|
333
|
+
key={nodeKey}
|
|
326
334
|
disabled={childDisabled}
|
|
327
335
|
>
|
|
328
336
|
</CapTreeNode>
|
|
@@ -353,12 +361,9 @@ class CapTagList extends React.Component { // eslint-disable-line react/prefer-s
|
|
|
353
361
|
)
|
|
354
362
|
}
|
|
355
363
|
tag={val}
|
|
364
|
+
tagKey={rawKey}
|
|
356
365
|
isLeaf
|
|
357
|
-
key={
|
|
358
|
-
val?.incentiveSeriesId
|
|
359
|
-
? `${key}(${val?.incentiveSeriesId})`
|
|
360
|
-
: `${key}`
|
|
361
|
-
}
|
|
366
|
+
key={nodeKey}
|
|
362
367
|
disabled={childDisabled}
|
|
363
368
|
>
|
|
364
369
|
</CapTreeNode>
|
|
@@ -388,7 +393,7 @@ class CapTagList extends React.Component { // eslint-disable-line react/prefer-s
|
|
|
388
393
|
} = this.props;
|
|
389
394
|
const {formatMessage} = intl;
|
|
390
395
|
const {
|
|
391
|
-
tagValue, expandedKeys, autoExpandParent, visible, translationLang, selectedContext, isLoadingLoyaltyTags, isLoadingContextChange,
|
|
396
|
+
tagValue, selectedNodeKey, expandedKeys, autoExpandParent, visible, translationLang, selectedContext, isLoadingLoyaltyTags, isLoadingContextChange,
|
|
392
397
|
} = this.state;
|
|
393
398
|
|
|
394
399
|
// Show loading spinner if general loading OR if specifically loading loyalty tags OR if context change is in progress
|
|
@@ -407,7 +412,7 @@ class CapTagList extends React.Component { // eslint-disable-line react/prefer-s
|
|
|
407
412
|
},
|
|
408
413
|
];
|
|
409
414
|
const contentSection = (
|
|
410
|
-
<CapRow>
|
|
415
|
+
<CapRow className="cap-tag-list-popover-inner">
|
|
411
416
|
<CapSpin tip={formatMessage(messages.gettingTags)} spinning={shouldShowLoading}>
|
|
412
417
|
<Search
|
|
413
418
|
style={{ marginBottom: 8, width: '250px'}}
|
|
@@ -427,7 +432,7 @@ class CapTagList extends React.Component { // eslint-disable-line react/prefer-s
|
|
|
427
432
|
<CapTree
|
|
428
433
|
styling={{height: '350px', overflow: 'auto'}}
|
|
429
434
|
onSelect={this.handleOnSelect}
|
|
430
|
-
selectedKeys={
|
|
435
|
+
selectedKeys={selectedNodeKey ? [selectedNodeKey] : []}
|
|
431
436
|
expandedKeys={expandedKeys}
|
|
432
437
|
autoExpandParent={autoExpandParent}
|
|
433
438
|
onExpand={this.onExpand}
|
|
@@ -1,5 +1,34 @@
|
|
|
1
1
|
@import "~@capillarytech/cap-ui-library/styles/_variables";
|
|
2
2
|
|
|
3
|
+
.cap-tag-list-popover-inner {
|
|
4
|
+
max-width: 20rem;
|
|
5
|
+
min-width: 0;
|
|
6
|
+
box-sizing: border-box;
|
|
7
|
+
|
|
8
|
+
.ant-tree.cap-tree-v2.ant-tree-icon-hide {
|
|
9
|
+
width: 100%;
|
|
10
|
+
max-width: 100%;
|
|
11
|
+
|
|
12
|
+
ul {
|
|
13
|
+
max-width: 100%;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
li {
|
|
17
|
+
overflow: hidden;
|
|
18
|
+
max-width: 100%;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
li .ant-tree-node-content-wrapper {
|
|
22
|
+
width: calc(100% - 3.5rem); // leave room for switcher (~24px)
|
|
23
|
+
max-width: calc(100% - 3.5rem);
|
|
24
|
+
overflow: hidden;
|
|
25
|
+
vertical-align: top;
|
|
26
|
+
box-sizing: border-box;
|
|
27
|
+
text-overflow: ellipsis;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
3
32
|
@media (max-height: 25rem) {
|
|
4
33
|
.ant-tree.cap-tree-v2.ant-tree-icon-hide {
|
|
5
34
|
height: 8.5714rem;
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { render, screen } from '@testing-library/react';
|
|
3
|
+
import '@testing-library/jest-dom';
|
|
4
|
+
import { IntlProvider } from 'react-intl';
|
|
5
|
+
import CapTagListWithInput from '../index';
|
|
6
|
+
|
|
7
|
+
const capturedTagListProps = { current: null };
|
|
8
|
+
|
|
9
|
+
jest.mock('../../../v2Containers/TagList', () => {
|
|
10
|
+
const React = require('react');
|
|
11
|
+
const Mock = (props) => {
|
|
12
|
+
capturedTagListProps.current = props;
|
|
13
|
+
return <div data-testid="mock-tag-list">TagList</div>;
|
|
14
|
+
};
|
|
15
|
+
return Mock;
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
jest.mock('@capillarytech/cap-ui-library/CapRow', () => ({ children }) => <div>{children}</div>);
|
|
19
|
+
jest.mock('@capillarytech/cap-ui-library/CapColumn', () => ({ children }) => <div>{children}</div>);
|
|
20
|
+
jest.mock('@capillarytech/cap-ui-library/CapHeading', () => () => null);
|
|
21
|
+
jest.mock('@capillarytech/cap-ui-library/CapInput', () => () => <input data-testid="cap-input" />);
|
|
22
|
+
|
|
23
|
+
const waitMap = {
|
|
24
|
+
b1: { eventName: 'Order Placed', blockName: 'Wait', tags: [] },
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
describe('CapTagListWithInput', () => {
|
|
28
|
+
beforeEach(() => {
|
|
29
|
+
capturedTagListProps.current = null;
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it('forwards waitEventContextTags to TagList', () => {
|
|
33
|
+
render(
|
|
34
|
+
<IntlProvider locale="en" messages={{}}>
|
|
35
|
+
<CapTagListWithInput
|
|
36
|
+
inputId="test-url"
|
|
37
|
+
inputOnChange={jest.fn()}
|
|
38
|
+
waitEventContextTags={waitMap}
|
|
39
|
+
onTagSelect={jest.fn()}
|
|
40
|
+
onContextChange={jest.fn()}
|
|
41
|
+
/>
|
|
42
|
+
</IntlProvider>
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
expect(screen.getByTestId('mock-tag-list')).toBeInTheDocument();
|
|
46
|
+
expect(capturedTagListProps.current.waitEventContextTags).toEqual(waitMap);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it('uses default empty object for waitEventContextTags when omitted', () => {
|
|
50
|
+
render(
|
|
51
|
+
<IntlProvider locale="en" messages={{}}>
|
|
52
|
+
<CapTagListWithInput
|
|
53
|
+
inputId="test-url"
|
|
54
|
+
inputOnChange={jest.fn()}
|
|
55
|
+
onTagSelect={jest.fn()}
|
|
56
|
+
onContextChange={jest.fn()}
|
|
57
|
+
/>
|
|
58
|
+
</IntlProvider>
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
expect(capturedTagListProps.current.waitEventContextTags).toEqual({});
|
|
62
|
+
});
|
|
63
|
+
});
|
|
@@ -27,6 +27,7 @@ export const CapTagListWithInput = (props) => {
|
|
|
27
27
|
userLocale = 'en',
|
|
28
28
|
eventContextTags = [],
|
|
29
29
|
restrictPersonalization = false,
|
|
30
|
+
waitEventContextTags = {},
|
|
30
31
|
// CapInput props
|
|
31
32
|
inputId,
|
|
32
33
|
inputValue = '',
|
|
@@ -77,6 +78,7 @@ export const CapTagListWithInput = (props) => {
|
|
|
77
78
|
userLocale={userLocale}
|
|
78
79
|
selectedOfferDetails={selectedOfferDetails}
|
|
79
80
|
eventContextTags={eventContextTags}
|
|
81
|
+
waitEventContextTags={waitEventContextTags}
|
|
80
82
|
style={tagListStyle}
|
|
81
83
|
popoverPlacement={popoverPlacement}
|
|
82
84
|
restrictPersonalization={restrictPersonalization}
|
|
@@ -116,6 +118,7 @@ CapTagListWithInput.propTypes = {
|
|
|
116
118
|
userLocale: PropTypes.string,
|
|
117
119
|
eventContextTags: PropTypes.array,
|
|
118
120
|
restrictPersonalization: PropTypes.bool,
|
|
121
|
+
waitEventContextTags: PropTypes.object,
|
|
119
122
|
|
|
120
123
|
// CapInput props
|
|
121
124
|
inputId: PropTypes.string.isRequired,
|
|
@@ -154,6 +157,7 @@ CapTagListWithInput.defaultProps = {
|
|
|
154
157
|
userLocale: 'en',
|
|
155
158
|
eventContextTags: [],
|
|
156
159
|
restrictPersonalization: false,
|
|
160
|
+
waitEventContextTags: {},
|
|
157
161
|
inputValue: '',
|
|
158
162
|
inputSize: 'default',
|
|
159
163
|
inputRequired: false,
|
|
@@ -52,6 +52,7 @@ export const CapWhatsappCTA = (props) => {
|
|
|
52
52
|
injectedTags = {},
|
|
53
53
|
selectedOfferDetails = [],
|
|
54
54
|
eventContextTags = [],
|
|
55
|
+
waitEventContextTags = {},
|
|
55
56
|
} = props;
|
|
56
57
|
const { formatMessage } = intl;
|
|
57
58
|
const invalidVarRegex = /{{(.*?)}}/g;
|
|
@@ -283,6 +284,7 @@ export const CapWhatsappCTA = (props) => {
|
|
|
283
284
|
injectedTags={injectedTags}
|
|
284
285
|
selectedOfferDetails={selectedOfferDetails}
|
|
285
286
|
eventContextTags={eventContextTags}
|
|
287
|
+
waitEventContextTags={waitEventContextTags}
|
|
286
288
|
/>
|
|
287
289
|
</CapColumn>
|
|
288
290
|
)}
|