@capillarytech/creatives-library 9.0.70-alpha.sprint-cap-213712.13 → 9.0.71
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/constants/unified.js +0 -1
- package/package.json +1 -1
- package/utils/common.js +0 -6
- package/v2Components/FormBuilder/Classic.js +3 -0
- package/v2Containers/Cap/tests/index.test.js +1 -0
- package/v2Containers/CreativesContainer/SlideBoxContent.js +39 -30
- package/v2Containers/CreativesContainer/SlideBoxFooter.js +6 -2
- package/v2Containers/CreativesContainer/constants.js +1 -0
- package/v2Containers/CreativesContainer/index.js +28 -3
- package/v2Containers/CreativesContainer/messages.js +4 -0
- package/v2Containers/CreativesContainer/tests/SlideBoxFooter.test.js +40 -12
- package/v2Containers/CreativesContainer/tests/__snapshots__/index.test.js.snap +10 -0
- package/v2Containers/CreativesContainer/tests/switchToEditAfterCreate.test.js +152 -0
- package/v2Containers/Email/index.js +41 -6
- package/v2Containers/Email/tests/index.test.js +123 -0
- package/v2Containers/EmailWrapper/components/EmailHTMLEditor.js +32 -12
- package/v2Containers/EmailWrapper/components/__tests__/EmailHTMLEditor.test.js +34 -0
- package/v2Containers/InApp/index.js +19 -23
- package/v2Containers/InApp/tests/index.test.js +52 -9
- package/v2Containers/InappAdvance/index.js +19 -24
- package/v2Containers/InappAdvance/tests/index.test.js +39 -15
- package/v2Containers/Line/Container/index.js +18 -8
- package/v2Containers/Line/Container/tests/__snapshots__/index.test.js.snap +129 -108
- package/v2Containers/Line/Container/tests/index.test.js +68 -0
- package/v2Containers/MobilePush/Create/index.js +24 -5
- package/v2Containers/MobilePush/Create/test/stayOpenOnSave.test.js +80 -0
- package/v2Containers/MobilePush/Edit/index.js +15 -9
- package/v2Containers/MobilePush/Edit/test/stayOpenOnSave.test.js +60 -0
- package/v2Containers/MobilePushNew/index.js +41 -7
- package/v2Containers/MobilePushNew/tests/index.test.js +61 -0
- package/v2Containers/Sms/Create/index.js +32 -14
- package/v2Containers/Sms/Create/test/stayOpenOnSave.test.js +92 -0
- package/v2Containers/Sms/Edit/index.js +22 -14
- package/v2Containers/Sms/Edit/tests/index.test.js +66 -0
- package/v2Containers/Templates/constants.js +1 -0
- package/v2Containers/Templates/index.js +48 -31
- package/v2Containers/Templates/messages.js +4 -0
- package/v2Containers/Templates/tests/__snapshots__/index.test.js.snap +128 -4
- package/v2Containers/Templates/tests/archival.test.js +221 -0
- package/v2Containers/Viber/index.js +15 -6
- package/v2Containers/Viber/tests/index.test.js +97 -28
- package/v2Containers/WebPush/Create/components/FormActions.js +1 -1
- package/v2Containers/WebPush/Create/components/FormActions.test.js +3 -3
- package/v2Containers/WebPush/Create/index.js +32 -6
- package/v2Containers/WebPush/Create/messages.js +0 -4
- package/v2Containers/WebPush/Create/tests/stayOpenOnSave.test.js +268 -0
|
@@ -117,7 +117,7 @@ import {
|
|
|
117
117
|
URL,
|
|
118
118
|
SITE_URL,
|
|
119
119
|
} from '../App/constants';
|
|
120
|
-
import {MAX_WHATSAPP_TEMPLATES, WARNING_WHATSAPP_TEMPLATES , ACCOUNT_MAPPING_ON_CHANNEL, noFilteredWhatsappZaloTemplatesTitle, noFilteredWhatsappZaloTemplatesDesc, noApprovedWhatsappZaloTemplatesTitle, noApprovedWhatsappTemplatesDesc, zaloDescIllustration, noApprovedRcsTemplatesTitle, noApprovedRcsTemplatesDesc, ARCHIVE_STATUS_ACTIVE, ARCHIVE_STATUS_ARCHIVED, ARCHIVE_REFRESH_TYPE_ARCHIVE, ARCHIVE_REFRESH_TYPE_UNARCHIVE} from './constants';
|
|
120
|
+
import {MAX_WHATSAPP_TEMPLATES, WARNING_WHATSAPP_TEMPLATES , ACCOUNT_MAPPING_ON_CHANNEL, noFilteredWhatsappZaloTemplatesTitle, noFilteredWhatsappZaloTemplatesDesc, noApprovedWhatsappZaloTemplatesTitle, noApprovedWhatsappTemplatesDesc, zaloDescIllustration, noApprovedRcsTemplatesTitle, noApprovedRcsTemplatesDesc, ARCHIVE_STATUS_ACTIVE, ARCHIVE_STATUS_ARCHIVED, ARCHIVE_REFRESH_TYPE_ARCHIVE, ARCHIVE_REFRESH_TYPE_UNARCHIVE, DEFAULT_ARCHIVAL_LIMIT} from './constants';
|
|
121
121
|
import { COPY_OF, EMBEDDED } from '../../constants/unified';
|
|
122
122
|
import { sanitizeFileName, exportEmailTemplateHtml } from '../../utils/downloadFile';
|
|
123
123
|
import {
|
|
@@ -152,7 +152,7 @@ import {
|
|
|
152
152
|
CAP_SPACE_16, CAP_G08, CAP_G05, CAP_SPACE_08, CAP_SPACE_12, CAP_YELLOW01,
|
|
153
153
|
} from '@capillarytech/cap-ui-library/styled/variables';
|
|
154
154
|
import { SMS_CATEGORY_TAG_STYLES, SMS_CATEGORY_LABELS } from '../../v2Components/SmsFallback/constants';
|
|
155
|
-
import { GA } from '@capillarytech/cap-ui-utils';
|
|
155
|
+
import { GA, getEnvVariable } from '@capillarytech/cap-ui-utils';
|
|
156
156
|
import { MAPP_SDK } from '../InApp/constants';
|
|
157
157
|
import injectReducer from '../../utils/injectReducer';
|
|
158
158
|
import v2TemplateReducer from './reducer';
|
|
@@ -186,6 +186,7 @@ const AccountScrollDiv = styled.div`
|
|
|
186
186
|
overflow: scroll;
|
|
187
187
|
height: calc(100vh - 18.75rem);
|
|
188
188
|
`;
|
|
189
|
+
const ARCHIVAL_LIMIT = parseInt(getEnvVariable('ARCHIVAL_LIMIT'), 10) || DEFAULT_ARCHIVAL_LIMIT;
|
|
189
190
|
const ACCOUNT_SELECTION_MODE = "accountSelectionMode";
|
|
190
191
|
const ACCOUNT_CHANGE_MODE = 'accountChangeMode';
|
|
191
192
|
const TEMPLATES_MODE = "templatesMode";
|
|
@@ -726,9 +727,12 @@ export class Templates extends React.Component { // eslint-disable-line react/pr
|
|
|
726
727
|
});
|
|
727
728
|
|
|
728
729
|
this.getAllTemplates({params, resetPage: true});
|
|
729
|
-
|
|
730
|
-
this
|
|
731
|
-
|
|
730
|
+
|
|
731
|
+
// Open form still uses this data; cleared on close (onCreateComplete).
|
|
732
|
+
if (!this.state.routeParams) {
|
|
733
|
+
this.props.mobilePushNewActions.clearData();
|
|
734
|
+
}
|
|
735
|
+
|
|
732
736
|
this.props.mobilePushNewActions.clearEditResponse();
|
|
733
737
|
|
|
734
738
|
// Reset flag after a delay to allow for next edit
|
|
@@ -1728,6 +1732,10 @@ export class Templates extends React.Component { // eslint-disable-line react/pr
|
|
|
1728
1732
|
}
|
|
1729
1733
|
|
|
1730
1734
|
onCreateComplete = (isReloadTemplates) => {
|
|
1735
|
+
// Push edit data kept for the open slidebox (see the MobilePushNew edit-response handling) is cleared on close
|
|
1736
|
+
if (this.state.channel?.toLowerCase() === MOBILE_PUSH_LOWERCASE && this.props.mobilePushNewActions?.clearData) {
|
|
1737
|
+
this.props.mobilePushNewActions.clearData();
|
|
1738
|
+
}
|
|
1731
1739
|
this.setState({routeParams: null, previewOpen: false}, () => {
|
|
1732
1740
|
if (this.isFullMode() && isReloadTemplates) {
|
|
1733
1741
|
this.getAllTemplates({params: {}}, true);
|
|
@@ -2152,10 +2160,9 @@ export class Templates extends React.Component { // eslint-disable-line react/pr
|
|
|
2152
2160
|
// Archive eligibility per template: Zalo never; WhatsApp/RCS not when pending/awaiting
|
|
2153
2161
|
const cardWhatsappStatus = get(template, `versions.base.content.${WHATSAPP_LOWERCASE}.status`, '');
|
|
2154
2162
|
const cardRcsStatus = get(template, 'versions.base.content.RCS.rcsContent.cardContent[0].Status', '');
|
|
2155
|
-
const
|
|
2156
|
-
const
|
|
2157
|
-
const
|
|
2158
|
-
const isAnyArchiveInProgress = isArchivalEnabled && !!(get(this.props, 'Templates.archiveInProgress') || get(this.props, 'Templates.unarchiveInProgress') || get(this.props, 'Templates.bulkArchiveInProgress') || get(this.props, 'Templates.bulkUnarchiveInProgress'));
|
|
2163
|
+
const isCardArchiveEligible = this.isChannelArchiveEligible(currentChannel, cardWhatsappStatus, cardRcsStatus);
|
|
2164
|
+
const isArchivedMode = get(this.props, 'Templates.isArchivedMode', false);
|
|
2165
|
+
const isAnyArchiveInProgress = !!(get(this.props, 'Templates.archiveInProgress') || get(this.props, 'Templates.unarchiveInProgress') || get(this.props, 'Templates.bulkArchiveInProgress') || get(this.props, 'Templates.bulkUnarchiveInProgress'));
|
|
2159
2166
|
const templateData = {
|
|
2160
2167
|
key: `${currentChannel}-card-${template?.name}`,
|
|
2161
2168
|
title: (
|
|
@@ -2300,7 +2307,7 @@ export class Templates extends React.Component { // eslint-disable-line react/pr
|
|
|
2300
2307
|
</CapMenu.Item>
|
|
2301
2308
|
)}
|
|
2302
2309
|
{/* Archive/Unarchive menu item (full mode only, not for Zalo, not for WhatsApp/RCS awaiting/pending) */}
|
|
2303
|
-
{
|
|
2310
|
+
{(() => {
|
|
2304
2311
|
const channelUp = this.state.channel.toUpperCase();
|
|
2305
2312
|
if (!this.isChannelArchiveEligible(channelUp, status, rcsStatus)) return null;
|
|
2306
2313
|
const canArchive = this.canPerform(PERMISSIONS.CREATIVE_DELETE);
|
|
@@ -2921,12 +2928,12 @@ export class Templates extends React.Component { // eslint-disable-line react/pr
|
|
|
2921
2928
|
|
|
2922
2929
|
const noLoaderAndSearchText = isEmpty(this.state.searchText) && !isLoading;
|
|
2923
2930
|
|
|
2924
|
-
const
|
|
2925
|
-
const isArchivedModeLocal = isArchivalEnabledLocal && get(this.props, 'Templates.isArchivedMode', false);
|
|
2931
|
+
const isArchivedModeLocal = get(this.props, 'Templates.isArchivedMode', false);
|
|
2926
2932
|
const selectedIdsLocal = get(this.props, 'Templates.selectedTemplateIds', []);
|
|
2927
2933
|
const selectedIdsArrayLocal = selectedIdsLocal && typeof selectedIdsLocal.toJS === 'function' ? selectedIdsLocal.toJS() : (Array.isArray(selectedIdsLocal) ? selectedIdsLocal : []);
|
|
2928
2934
|
const selectedCountLocal = selectedIdsArrayLocal.length;
|
|
2929
|
-
const hasSelectionLocal =
|
|
2935
|
+
const hasSelectionLocal = this.props.isFullMode && selectedCountLocal > 0;
|
|
2936
|
+
const isArchiveLimitExceeded = selectedCountLocal > ARCHIVAL_LIMIT;
|
|
2930
2937
|
|
|
2931
2938
|
return (<div>
|
|
2932
2939
|
{[WECHAT, MOBILE_PUSH, WEBPUSH, INAPP, WHATSAPP, ZALO, RCS].includes(currentChannel) && this.showAccountName()}
|
|
@@ -2938,16 +2945,21 @@ export class Templates extends React.Component { // eslint-disable-line react/pr
|
|
|
2938
2945
|
{this.props.intl.formatMessage(messages.templatesSelected, { count: selectedCountLocal })}
|
|
2939
2946
|
</CapLabel>
|
|
2940
2947
|
<NoPermissionWrapper allowed={this.canPerform(PERMISSIONS.CREATIVE_DELETE)}>
|
|
2941
|
-
<
|
|
2942
|
-
|
|
2943
|
-
|
|
2944
|
-
|
|
2945
|
-
|
|
2946
|
-
|
|
2947
|
-
|
|
2948
|
-
|
|
2948
|
+
<CapTooltip title={isArchiveLimitExceeded ? this.props.intl.formatMessage(messages.archiveLimitExceeded, { limit: ARCHIVAL_LIMIT }) : ''}>
|
|
2949
|
+
<span>
|
|
2950
|
+
<CapButton
|
|
2951
|
+
type="primary"
|
|
2952
|
+
className="archive-template-button"
|
|
2953
|
+
disabled={isArchiveLimitExceeded}
|
|
2954
|
+
prefix={<CapIcon type="archive" size="s" />}
|
|
2955
|
+
onClick={() => this.handleBulkArchiveAction({ ids: selectedIdsArrayLocal, count: selectedCountLocal, isUnarchive: isArchivedModeLocal })}
|
|
2956
|
+
>
|
|
2957
|
+
<span className="archive-btn-label">
|
|
2958
|
+
<FormattedMessage {...(isArchivedModeLocal ? messages.unarchiveButton : messages.archiveButton)} />
|
|
2959
|
+
</span>
|
|
2960
|
+
</CapButton>
|
|
2949
2961
|
</span>
|
|
2950
|
-
</
|
|
2962
|
+
</CapTooltip>
|
|
2951
2963
|
</NoPermissionWrapper>
|
|
2952
2964
|
<CapButton type="secondary" onClick={() => this.props.actions.clearTemplateSelection()}>
|
|
2953
2965
|
<FormattedMessage {...messages.archiveConfirmCancel} />
|
|
@@ -4188,7 +4200,7 @@ export class Templates extends React.Component { // eslint-disable-line react/pr
|
|
|
4188
4200
|
<span className="template-name" style={{ fontWeight: `${this.state.channel.toLowerCase() === 'wechat' ? '400' : '600'}` }}>
|
|
4189
4201
|
{ template && template.versions && template.versions.history && template.versions.history.length > 1 && this.state.channel.toLowerCase() !== 'mobilepush' && <i style={{fontSize: '16px', margin: '0 8px 0 0', verticalAlign: 'middle'}} className="material-icons">filter_none</i>}
|
|
4190
4202
|
{template?.name}
|
|
4191
|
-
{
|
|
4203
|
+
{template.isArchived && <CapColoredTag tagColor={CAP_G08} tagTextColor={CAP_G05} className="archived-tag">{this.props.intl.formatMessage(messages.archivedTag)}</CapColoredTag>}
|
|
4192
4204
|
</span>
|
|
4193
4205
|
{this.props.location.query.type !== EMBEDDED && <CapPopover
|
|
4194
4206
|
trigger="click"
|
|
@@ -4201,7 +4213,7 @@ export class Templates extends React.Component { // eslint-disable-line react/pr
|
|
|
4201
4213
|
</CapButton>
|
|
4202
4214
|
</NoPermissionWrapper>
|
|
4203
4215
|
</div>}
|
|
4204
|
-
{
|
|
4216
|
+
{this.props.isFullMode && isTemplateArchiveEligible && !template.isArchived && <div className="popover-action-container">
|
|
4205
4217
|
<NoPermissionWrapper allowed={this.canPerform(PERMISSIONS.CREATIVE_DELETE)}>
|
|
4206
4218
|
<CapButton
|
|
4207
4219
|
type="link"
|
|
@@ -4213,7 +4225,7 @@ export class Templates extends React.Component { // eslint-disable-line react/pr
|
|
|
4213
4225
|
</CapButton>
|
|
4214
4226
|
</NoPermissionWrapper>
|
|
4215
4227
|
</div>}
|
|
4216
|
-
{
|
|
4228
|
+
{this.props.isFullMode && isTemplateArchiveEligible && template.isArchived && <div className="popover-action-container">
|
|
4217
4229
|
<NoPermissionWrapper allowed={this.canPerform(PERMISSIONS.CREATIVE_DELETE)}>
|
|
4218
4230
|
<CapButton
|
|
4219
4231
|
type="link"
|
|
@@ -4860,11 +4872,10 @@ export class Templates extends React.Component { // eslint-disable-line react/pr
|
|
|
4860
4872
|
if (([WHATSAPP_LOWERCASE, ZALO_LOWERCASE, RCS_LOWERCASE].includes(this.state?.channel?.toLocaleLowerCase()) && isEmpty(this.state?.hostName))) {
|
|
4861
4873
|
isfilterContentVisisble = false;
|
|
4862
4874
|
}
|
|
4863
|
-
const
|
|
4864
|
-
const _isArchivedMode = _isArchivalEnabled && get(this.props, 'Templates.isArchivedMode', false);
|
|
4875
|
+
const _isArchivedMode = get(this.props, 'Templates.isArchivedMode', false);
|
|
4865
4876
|
const _renderSelectedIds = get(this.props, 'Templates.selectedTemplateIds', []);
|
|
4866
4877
|
const _renderSelectedIdsArray = _renderSelectedIds && typeof _renderSelectedIds.toJS === 'function' ? _renderSelectedIds.toJS() : (Array.isArray(_renderSelectedIds) ? _renderSelectedIds : []);
|
|
4867
|
-
const _renderHasSelection =
|
|
4878
|
+
const _renderHasSelection = this.props.isFullMode && _renderSelectedIdsArray.length > 0;
|
|
4868
4879
|
|
|
4869
4880
|
const useLocalTemplates = this.props.localTemplatesConfig?.useLocalTemplates;
|
|
4870
4881
|
const builtFilterContent = ((isfilterContentVisisble || [WECHAT, MOBILE_PUSH, INAPP].includes(this.state.channel.toUpperCase())) && (
|
|
@@ -5026,7 +5037,7 @@ export class Templates extends React.Component { // eslint-disable-line react/pr
|
|
|
5026
5037
|
}
|
|
5027
5038
|
<div className="template-listing-header-actions">
|
|
5028
5039
|
{/* More (⋯) menu: full mode only, not archived mode, not Zalo (no archive support), not when selection active, archive flag enabled */}
|
|
5029
|
-
{
|
|
5040
|
+
{!_isArchivedMode && !_renderHasSelection && this.props.isFullMode && this.props.location.query.type !== EMBEDDED && channelLowerCase !== ZALO_LOWERCASE && (
|
|
5030
5041
|
<CapDropdown
|
|
5031
5042
|
trigger={['click']}
|
|
5032
5043
|
overlay={
|
|
@@ -5122,7 +5133,7 @@ export class Templates extends React.Component { // eslint-disable-line react/pr
|
|
|
5122
5133
|
/>
|
|
5123
5134
|
|
|
5124
5135
|
{/* Archived mode header with back arrow (full mode only, archive flag enabled) */}
|
|
5125
|
-
{
|
|
5136
|
+
{this.props.isFullMode && get(this.props, 'Templates.isArchivedMode', false) && (
|
|
5126
5137
|
<CapRow type="flex" align="middle" className="archived-mode-header">
|
|
5127
5138
|
<CapIcon
|
|
5128
5139
|
type="back"
|
|
@@ -5205,9 +5216,15 @@ export class Templates extends React.Component { // eslint-disable-line react/pr
|
|
|
5205
5216
|
messages={messages}
|
|
5206
5217
|
isFullMode={isFullMode}
|
|
5207
5218
|
templateData={creativesParams}
|
|
5208
|
-
getCreativesData={() => {
|
|
5219
|
+
getCreativesData={(creativesData) => {
|
|
5220
|
+
// Saved but slidebox stays open: refresh the list without closing.
|
|
5221
|
+
if (creativesData?.keepSlideBoxOpen) {
|
|
5222
|
+
this.getAllTemplates({params: {}}, true);
|
|
5223
|
+
return;
|
|
5224
|
+
}
|
|
5209
5225
|
this.onCreateComplete(true);
|
|
5210
5226
|
}}
|
|
5227
|
+
switchToEditAfterCreate={this.canPerform(PERMISSIONS.CREATIVE_EDIT)}
|
|
5211
5228
|
channel={route.name.toUpperCase()}
|
|
5212
5229
|
handleCloseCreatives={this.onCreateComplete}
|
|
5213
5230
|
selectedWeChatAccount={get(
|
|
@@ -714,6 +714,10 @@ export default defineMessages({
|
|
|
714
714
|
id: `${scope}.unarchiveTemplateSuccess`,
|
|
715
715
|
defaultMessage: 'Template unarchived successfully',
|
|
716
716
|
},
|
|
717
|
+
"archiveLimitExceeded": {
|
|
718
|
+
id: `${scope}.archiveLimitExceeded`,
|
|
719
|
+
defaultMessage: 'Archive limit exceeded, it cannot be greater than {limit}.',
|
|
720
|
+
},
|
|
717
721
|
"bulkArchiveSuccess": {
|
|
718
722
|
id: `${scope}.bulkArchiveSuccess`,
|
|
719
723
|
defaultMessage: '{count} {count, plural, one {template} other {templates}} archived successfully',
|
|
@@ -813,7 +813,38 @@ exports[`Test Templates container Should render temlates when whatsapp templates
|
|
|
813
813
|
<Component />
|
|
814
814
|
<div
|
|
815
815
|
className="template-listing-header-actions"
|
|
816
|
-
|
|
816
|
+
>
|
|
817
|
+
<CapDropdownComponent
|
|
818
|
+
overlay={
|
|
819
|
+
<CapMenuComponent>
|
|
820
|
+
<withItemHOC
|
|
821
|
+
onClick={[Function]}
|
|
822
|
+
>
|
|
823
|
+
<FormattedMessage
|
|
824
|
+
defaultMessage="Archived templates"
|
|
825
|
+
id="creatives.containersV2.Templates.archivedTemplates"
|
|
826
|
+
values={Object {}}
|
|
827
|
+
/>
|
|
828
|
+
</withItemHOC>
|
|
829
|
+
</CapMenuComponent>
|
|
830
|
+
}
|
|
831
|
+
placement="bottomRight"
|
|
832
|
+
trigger={
|
|
833
|
+
Array [
|
|
834
|
+
"click",
|
|
835
|
+
]
|
|
836
|
+
}
|
|
837
|
+
>
|
|
838
|
+
<CapButton
|
|
839
|
+
className="template-listing-more-btn"
|
|
840
|
+
type="flat"
|
|
841
|
+
>
|
|
842
|
+
<CapIcon
|
|
843
|
+
type="more"
|
|
844
|
+
/>
|
|
845
|
+
</CapButton>
|
|
846
|
+
</CapDropdownComponent>
|
|
847
|
+
</div>
|
|
817
848
|
</div>
|
|
818
849
|
<div>
|
|
819
850
|
<div
|
|
@@ -1227,7 +1258,38 @@ exports[`Test Templates container Test max templates exceeded 1`] = `
|
|
|
1227
1258
|
<Component />
|
|
1228
1259
|
<div
|
|
1229
1260
|
className="template-listing-header-actions"
|
|
1230
|
-
|
|
1261
|
+
>
|
|
1262
|
+
<CapDropdownComponent
|
|
1263
|
+
overlay={
|
|
1264
|
+
<CapMenuComponent>
|
|
1265
|
+
<withItemHOC
|
|
1266
|
+
onClick={[Function]}
|
|
1267
|
+
>
|
|
1268
|
+
<FormattedMessage
|
|
1269
|
+
defaultMessage="Archived templates"
|
|
1270
|
+
id="creatives.containersV2.Templates.archivedTemplates"
|
|
1271
|
+
values={Object {}}
|
|
1272
|
+
/>
|
|
1273
|
+
</withItemHOC>
|
|
1274
|
+
</CapMenuComponent>
|
|
1275
|
+
}
|
|
1276
|
+
placement="bottomRight"
|
|
1277
|
+
trigger={
|
|
1278
|
+
Array [
|
|
1279
|
+
"click",
|
|
1280
|
+
]
|
|
1281
|
+
}
|
|
1282
|
+
>
|
|
1283
|
+
<CapButton
|
|
1284
|
+
className="template-listing-more-btn"
|
|
1285
|
+
type="flat"
|
|
1286
|
+
>
|
|
1287
|
+
<CapIcon
|
|
1288
|
+
type="more"
|
|
1289
|
+
/>
|
|
1290
|
+
</CapButton>
|
|
1291
|
+
</CapDropdownComponent>
|
|
1292
|
+
</div>
|
|
1231
1293
|
</div>
|
|
1232
1294
|
<div>
|
|
1233
1295
|
<div
|
|
@@ -1714,7 +1776,38 @@ exports[`Test Templates container Test max templates not exceeded 1`] = `
|
|
|
1714
1776
|
<Component />
|
|
1715
1777
|
<div
|
|
1716
1778
|
className="template-listing-header-actions"
|
|
1717
|
-
|
|
1779
|
+
>
|
|
1780
|
+
<CapDropdownComponent
|
|
1781
|
+
overlay={
|
|
1782
|
+
<CapMenuComponent>
|
|
1783
|
+
<withItemHOC
|
|
1784
|
+
onClick={[Function]}
|
|
1785
|
+
>
|
|
1786
|
+
<FormattedMessage
|
|
1787
|
+
defaultMessage="Archived templates"
|
|
1788
|
+
id="creatives.containersV2.Templates.archivedTemplates"
|
|
1789
|
+
values={Object {}}
|
|
1790
|
+
/>
|
|
1791
|
+
</withItemHOC>
|
|
1792
|
+
</CapMenuComponent>
|
|
1793
|
+
}
|
|
1794
|
+
placement="bottomRight"
|
|
1795
|
+
trigger={
|
|
1796
|
+
Array [
|
|
1797
|
+
"click",
|
|
1798
|
+
]
|
|
1799
|
+
}
|
|
1800
|
+
>
|
|
1801
|
+
<CapButton
|
|
1802
|
+
className="template-listing-more-btn"
|
|
1803
|
+
type="flat"
|
|
1804
|
+
>
|
|
1805
|
+
<CapIcon
|
|
1806
|
+
type="more"
|
|
1807
|
+
/>
|
|
1808
|
+
</CapButton>
|
|
1809
|
+
</CapDropdownComponent>
|
|
1810
|
+
</div>
|
|
1718
1811
|
</div>
|
|
1719
1812
|
<div>
|
|
1720
1813
|
<div
|
|
@@ -2201,7 +2294,38 @@ exports[`Test Templates container Test max templates warning 1`] = `
|
|
|
2201
2294
|
<Component />
|
|
2202
2295
|
<div
|
|
2203
2296
|
className="template-listing-header-actions"
|
|
2204
|
-
|
|
2297
|
+
>
|
|
2298
|
+
<CapDropdownComponent
|
|
2299
|
+
overlay={
|
|
2300
|
+
<CapMenuComponent>
|
|
2301
|
+
<withItemHOC
|
|
2302
|
+
onClick={[Function]}
|
|
2303
|
+
>
|
|
2304
|
+
<FormattedMessage
|
|
2305
|
+
defaultMessage="Archived templates"
|
|
2306
|
+
id="creatives.containersV2.Templates.archivedTemplates"
|
|
2307
|
+
values={Object {}}
|
|
2308
|
+
/>
|
|
2309
|
+
</withItemHOC>
|
|
2310
|
+
</CapMenuComponent>
|
|
2311
|
+
}
|
|
2312
|
+
placement="bottomRight"
|
|
2313
|
+
trigger={
|
|
2314
|
+
Array [
|
|
2315
|
+
"click",
|
|
2316
|
+
]
|
|
2317
|
+
}
|
|
2318
|
+
>
|
|
2319
|
+
<CapButton
|
|
2320
|
+
className="template-listing-more-btn"
|
|
2321
|
+
type="flat"
|
|
2322
|
+
>
|
|
2323
|
+
<CapIcon
|
|
2324
|
+
type="more"
|
|
2325
|
+
/>
|
|
2326
|
+
</CapButton>
|
|
2327
|
+
</CapDropdownComponent>
|
|
2328
|
+
</div>
|
|
2205
2329
|
</div>
|
|
2206
2330
|
<div>
|
|
2207
2331
|
<div
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { shallowWithIntl } from '../../../helpers/intl-enzym-test-helpers';
|
|
3
|
+
import { Templates } from '../index';
|
|
4
|
+
import mockdata from '../../mockdata';
|
|
5
|
+
import { DEFAULT_ARCHIVAL_LIMIT } from '../constants';
|
|
6
|
+
import { CREATIVE_CREATE, CREATIVE_DELETE, CREATIVE_EDIT } from '../../App/constants';
|
|
7
|
+
|
|
8
|
+
const { whatsappTemplates: TemplatesProp } = mockdata;
|
|
9
|
+
|
|
10
|
+
jest.mock('@capillarytech/cap-ui-library', () => ({
|
|
11
|
+
...jest.requireActual('@capillarytech/cap-ui-library'),
|
|
12
|
+
CapNotification: { error: jest.fn(), success: jest.fn(), warning: jest.fn() },
|
|
13
|
+
}));
|
|
14
|
+
|
|
15
|
+
jest.mock('../../CreativesContainer', () => ({
|
|
16
|
+
__esModule: true,
|
|
17
|
+
default: () => null,
|
|
18
|
+
}));
|
|
19
|
+
|
|
20
|
+
const buildTemplateIds = count => Array.from({ length: count }, (_, index) => `template_${index}`);
|
|
21
|
+
|
|
22
|
+
// Walks a rendered element/plain-object tree (cards are never mounted) for a node by className.
|
|
23
|
+
const findByClassName = (node, className, seen = new Set()) => {
|
|
24
|
+
if (!node || typeof node !== 'object' || seen.has(node)) return null;
|
|
25
|
+
seen.add(node);
|
|
26
|
+
if (Array.isArray(node)) {
|
|
27
|
+
return node.reduce((found, child) => found || findByClassName(child, className, seen), null);
|
|
28
|
+
}
|
|
29
|
+
if (node.props && typeof node.props.className === 'string' && node.props.className.includes(className)) {
|
|
30
|
+
return node;
|
|
31
|
+
}
|
|
32
|
+
return Object.values(node.props || node).reduce(
|
|
33
|
+
(found, value) => found || findByClassName(value, className, seen),
|
|
34
|
+
null,
|
|
35
|
+
);
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
describe('Templates archival (enabled by default)', () => {
|
|
39
|
+
const actions = {
|
|
40
|
+
getWeCrmAccounts: jest.fn(),
|
|
41
|
+
setChannelAccount: jest.fn(),
|
|
42
|
+
getAllTemplates: jest.fn(),
|
|
43
|
+
getUserList: jest.fn(),
|
|
44
|
+
getSenderDetails: jest.fn(),
|
|
45
|
+
resetTemplate: jest.fn(),
|
|
46
|
+
setArchivedMode: jest.fn(),
|
|
47
|
+
clearTemplateSelection: jest.fn(),
|
|
48
|
+
toggleTemplateSelection: jest.fn(),
|
|
49
|
+
bulkArchiveTemplates: jest.fn(),
|
|
50
|
+
bulkUnarchiveTemplates: jest.fn(),
|
|
51
|
+
archiveTemplate: jest.fn(),
|
|
52
|
+
unarchiveTemplate: jest.fn(),
|
|
53
|
+
};
|
|
54
|
+
let renderedComponent;
|
|
55
|
+
|
|
56
|
+
const renderTemplates = ({ channel = 'email', isFullMode = true, templatesProps = {} } = {}) => {
|
|
57
|
+
renderedComponent = shallowWithIntl(
|
|
58
|
+
<Templates
|
|
59
|
+
route={{ name: channel }}
|
|
60
|
+
cap={{ user: { accessiblePermissions: [CREATIVE_CREATE, CREATIVE_EDIT, CREATIVE_DELETE] } }}
|
|
61
|
+
Templates={{ ...TemplatesProp, ...templatesProps }}
|
|
62
|
+
actions={actions}
|
|
63
|
+
location={{ pathname: `/${channel}`, query: {}, search: '' }}
|
|
64
|
+
EmailCreate={{ duplicateTemplateInProgress: false }}
|
|
65
|
+
isFullMode={isFullMode}
|
|
66
|
+
intl={{ formatMessage: ({ defaultMessage }) => defaultMessage }}
|
|
67
|
+
/>,
|
|
68
|
+
);
|
|
69
|
+
return renderedComponent;
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
const getCardList = (templates, channel = 'email') =>
|
|
73
|
+
renderedComponent.instance().getTemplateDataForGrid({
|
|
74
|
+
templates,
|
|
75
|
+
handlers: { handlePreviewClick: jest.fn(), handleEditClick: jest.fn() },
|
|
76
|
+
filterContent: null,
|
|
77
|
+
channel,
|
|
78
|
+
isLoading: false,
|
|
79
|
+
isInitialLoading: false,
|
|
80
|
+
loadingTip: '',
|
|
81
|
+
previewTemplateId: null,
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
beforeEach(() => {
|
|
85
|
+
jest.clearAllMocks();
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
describe('bulk selection bar', () => {
|
|
89
|
+
const getBulkArchiveButton = () =>
|
|
90
|
+
renderedComponent
|
|
91
|
+
.find('.bulk-selection-bar')
|
|
92
|
+
.find('.archive-template-button');
|
|
93
|
+
|
|
94
|
+
it('renders without any feature flag once templates are selected', () => {
|
|
95
|
+
renderTemplates({ templatesProps: { selectedTemplateIds: buildTemplateIds(2) } });
|
|
96
|
+
expect(renderedComponent.find('.bulk-selection-bar')).toHaveLength(1);
|
|
97
|
+
expect(getBulkArchiveButton().prop('disabled')).toBe(false);
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it('is not rendered when nothing is selected', () => {
|
|
101
|
+
renderTemplates({ templatesProps: { selectedTemplateIds: [] } });
|
|
102
|
+
expect(renderedComponent.find('.bulk-selection-bar')).toHaveLength(0);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
it('is not rendered outside full mode', () => {
|
|
106
|
+
renderTemplates({ isFullMode: false, templatesProps: { selectedTemplateIds: buildTemplateIds(2) } });
|
|
107
|
+
expect(renderedComponent.find('.bulk-selection-bar')).toHaveLength(0);
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it('keeps the action enabled at exactly the archival limit', () => {
|
|
111
|
+
renderTemplates({ templatesProps: { selectedTemplateIds: buildTemplateIds(DEFAULT_ARCHIVAL_LIMIT) } });
|
|
112
|
+
expect(getBulkArchiveButton().prop('disabled')).toBe(false);
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
it('disables the action and shows the limit message above the archival limit', () => {
|
|
116
|
+
renderTemplates({ templatesProps: { selectedTemplateIds: buildTemplateIds(DEFAULT_ARCHIVAL_LIMIT + 1) } });
|
|
117
|
+
expect(getBulkArchiveButton().prop('disabled')).toBe(true);
|
|
118
|
+
const tooltip = renderedComponent.find('.bulk-selection-bar').find('CapTooltip').first();
|
|
119
|
+
expect(tooltip.prop('title')).toContain('Archive limit exceeded');
|
|
120
|
+
expect(tooltip.prop('title')).toContain(String(DEFAULT_ARCHIVAL_LIMIT));
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it('triggers bulk unarchive for the selection in archived mode', () => {
|
|
124
|
+
const selectedIds = buildTemplateIds(2);
|
|
125
|
+
renderTemplates({ templatesProps: { selectedTemplateIds: selectedIds, isArchivedMode: true } });
|
|
126
|
+
const handleBulkArchiveAction = jest
|
|
127
|
+
.spyOn(renderedComponent.instance(), 'handleBulkArchiveAction')
|
|
128
|
+
.mockImplementation(() => {});
|
|
129
|
+
getBulkArchiveButton().prop('onClick')();
|
|
130
|
+
expect(handleBulkArchiveAction).toHaveBeenCalledWith({ ids: selectedIds, count: 2, isUnarchive: true });
|
|
131
|
+
});
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
describe('handleBulkArchiveAction', () => {
|
|
135
|
+
it('opens the confirm modal and bulk archives the selected ids on confirm', () => {
|
|
136
|
+
renderTemplates();
|
|
137
|
+
const instance = renderedComponent.instance();
|
|
138
|
+
const showArchiveConfirm = jest.spyOn(instance, 'showArchiveConfirm').mockImplementation(() => {});
|
|
139
|
+
instance.handleBulkArchiveAction({ ids: ['a', 'b'], count: 2 });
|
|
140
|
+
expect(showArchiveConfirm).toHaveBeenCalledWith(expect.objectContaining({ count: 2, isUnarchive: false }));
|
|
141
|
+
showArchiveConfirm.mock.calls[0][0].onConfirm();
|
|
142
|
+
expect(actions.bulkArchiveTemplates).toHaveBeenCalledWith('Email', ['a', 'b'], expect.any(Function));
|
|
143
|
+
expect(actions.bulkUnarchiveTemplates).not.toHaveBeenCalled();
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
it('bulk unarchives when isUnarchive is set', () => {
|
|
147
|
+
renderTemplates();
|
|
148
|
+
const instance = renderedComponent.instance();
|
|
149
|
+
const showArchiveConfirm = jest.spyOn(instance, 'showArchiveConfirm').mockImplementation(() => {});
|
|
150
|
+
instance.handleBulkArchiveAction({ ids: ['a'], count: 1, isUnarchive: true });
|
|
151
|
+
showArchiveConfirm.mock.calls[0][0].onConfirm();
|
|
152
|
+
expect(actions.bulkUnarchiveTemplates).toHaveBeenCalledWith('Email', ['a'], expect.any(Function));
|
|
153
|
+
expect(actions.bulkArchiveTemplates).not.toHaveBeenCalled();
|
|
154
|
+
});
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
describe('single template card', () => {
|
|
158
|
+
const activeTemplate = { _id: 'tpl_1', name: 'Active template', versions: { base: {} } };
|
|
159
|
+
const archivedTemplate = { _id: 'tpl_2', name: 'Archived template', isArchived: true, versions: { base: {} } };
|
|
160
|
+
|
|
161
|
+
it('shows the archive menu action for an active template without any feature flag', () => {
|
|
162
|
+
renderTemplates();
|
|
163
|
+
const cardList = getCardList([activeTemplate]);
|
|
164
|
+
expect(findByClassName(cardList, 'archive-email')).not.toBeNull();
|
|
165
|
+
expect(findByClassName(cardList, 'unarchive-email')).toBeNull();
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
it('shows the unarchive menu action for an archived template', () => {
|
|
169
|
+
renderTemplates();
|
|
170
|
+
const cardList = getCardList([archivedTemplate]);
|
|
171
|
+
expect(findByClassName(cardList, 'unarchive-email')).not.toBeNull();
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
it('shows the archived tag and unarchive popover action for an archived template', () => {
|
|
175
|
+
renderTemplates({ channel: 'sms' });
|
|
176
|
+
const cardList = renderedComponent.instance().populateTemplatesList([archivedTemplate], false, false);
|
|
177
|
+
expect(findByClassName(cardList, 'archived-tag')).not.toBeNull();
|
|
178
|
+
expect(findByClassName(cardList, 'popover-archive-action')).not.toBeNull();
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
it('shows no archived tag for an active template', () => {
|
|
182
|
+
renderTemplates({ channel: 'sms' });
|
|
183
|
+
const cardList = renderedComponent.instance().populateTemplatesList([activeTemplate], false, false);
|
|
184
|
+
expect(findByClassName(cardList, 'archived-tag')).toBeNull();
|
|
185
|
+
expect(findByClassName(cardList, 'popover-archive-action')).not.toBeNull();
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
it('hides the archive actions outside full mode', () => {
|
|
189
|
+
renderTemplates({ isFullMode: false });
|
|
190
|
+
const cardList = getCardList([activeTemplate]);
|
|
191
|
+
expect(findByClassName(cardList, 'archive-email')).toBeNull();
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
it('archives a single template through the confirm modal', () => {
|
|
195
|
+
renderTemplates();
|
|
196
|
+
const instance = renderedComponent.instance();
|
|
197
|
+
const showArchiveConfirm = jest.spyOn(instance, 'showArchiveConfirm').mockImplementation(() => {});
|
|
198
|
+
instance.handleTemplateArchiveAction({ templateId: 'tpl_1', templateName: 'Active template' });
|
|
199
|
+
expect(showArchiveConfirm).toHaveBeenCalledTimes(1);
|
|
200
|
+
showArchiveConfirm.mock.calls[0][0].onConfirm();
|
|
201
|
+
expect(actions.archiveTemplate).toHaveBeenCalled();
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
it('unarchives a single template through the confirm modal', () => {
|
|
205
|
+
renderTemplates();
|
|
206
|
+
const instance = renderedComponent.instance();
|
|
207
|
+
const showArchiveConfirm = jest.spyOn(instance, 'showArchiveConfirm').mockImplementation(() => {});
|
|
208
|
+
instance.handleTemplateArchiveAction({ templateId: 'tpl_2', templateName: 'Archived template', isUnarchive: true });
|
|
209
|
+
showArchiveConfirm.mock.calls[0][0].onConfirm();
|
|
210
|
+
expect(actions.unarchiveTemplate).toHaveBeenCalled();
|
|
211
|
+
});
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
describe('isChannelArchiveEligible', () => {
|
|
215
|
+
it('does not allow archiving Zalo templates', () => {
|
|
216
|
+
renderTemplates();
|
|
217
|
+
expect(renderedComponent.instance().isChannelArchiveEligible('ZALO')).toBe(false);
|
|
218
|
+
expect(renderedComponent.instance().isChannelArchiveEligible('SMS')).toBe(true);
|
|
219
|
+
});
|
|
220
|
+
});
|
|
221
|
+
});
|