@capillarytech/creatives-library 8.0.248 → 8.0.249
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/transformerUtils.test.js +297 -0
- package/utils/transformerUtils.js +40 -0
- package/v2Containers/CreativesContainer/SlideBoxContent.js +2 -0
- package/v2Containers/CreativesContainer/constants.js +1 -0
- package/v2Containers/CreativesContainer/index.js +168 -0
- package/v2Containers/TemplatesV2/index.js +8 -7
- package/v2Containers/WebPush/Create/components/ButtonsLinksSection.test.js +4 -4
- package/v2Containers/WebPush/Create/components/__snapshots__/ButtonsLinksSection.test.js.snap +2 -2
- package/v2Containers/WebPush/Create/index.js +104 -12
- package/v2Containers/WebPush/Create/preview/WebPushPreview.js +6 -1
- package/v2Containers/WebPush/Create/preview/tests/WebPushPreview.test.js +384 -0
- package/v2Containers/WebPush/Create/utils/payloadBuilder.js +4 -2
- package/v2Containers/WebPush/Create/utils/payloadBuilder.test.js +13 -7
- package/v2Containers/WebPush/constants.js +6 -2
- package/v2Containers/WebPush/tests/selectors.test.js +117 -0
|
@@ -32,6 +32,9 @@ import { useImageUpload } from './hooks/useImageUpload';
|
|
|
32
32
|
import { useTagManagement } from './hooks/useTagManagement';
|
|
33
33
|
import isEmpty from 'lodash/isEmpty';
|
|
34
34
|
import get from 'lodash/get';
|
|
35
|
+
import * as templateActions from '../../Templates/actions';
|
|
36
|
+
import { makeSelectTemplates } from '../../Templates/selectors';
|
|
37
|
+
|
|
35
38
|
import {
|
|
36
39
|
WEBPUSH_MEDIA_TYPES,
|
|
37
40
|
BRAND_ICON_OPTIONS,
|
|
@@ -53,6 +56,7 @@ import {
|
|
|
53
56
|
} from '../selectors';
|
|
54
57
|
import webPushReducer from '../reducer';
|
|
55
58
|
import webPushSagas from '../sagas';
|
|
59
|
+
import { v2TemplateSaga } from '../../Templates/sagas';
|
|
56
60
|
import withCreatives from '../../../hoc/withCreatives';
|
|
57
61
|
import messages from './messages';
|
|
58
62
|
import { createWebPushPayload } from './utils/payloadBuilder';
|
|
@@ -67,6 +71,7 @@ import {
|
|
|
67
71
|
setInjectedTags,
|
|
68
72
|
} from '../../Cap/selectors';
|
|
69
73
|
import './index.scss';
|
|
74
|
+
import { WEBPUSH } from '../../CreativesContainer/constants';
|
|
70
75
|
|
|
71
76
|
// Memoized TagList wrapper components for better performance
|
|
72
77
|
const MemoizedTagList = memo(({
|
|
@@ -123,6 +128,8 @@ const WebPushCreate = ({
|
|
|
123
128
|
accountData,
|
|
124
129
|
webPush,
|
|
125
130
|
onCreateComplete,
|
|
131
|
+
getFormData,
|
|
132
|
+
isGetFormData,
|
|
126
133
|
templateData,
|
|
127
134
|
creativesMode,
|
|
128
135
|
params,
|
|
@@ -135,6 +142,8 @@ const WebPushCreate = ({
|
|
|
135
142
|
forwardedTags,
|
|
136
143
|
selectedOfferDetails = [],
|
|
137
144
|
eventContextTags = [],
|
|
145
|
+
templateActions: templateActionsProps,
|
|
146
|
+
Templates,
|
|
138
147
|
}) => {
|
|
139
148
|
const { formatMessage } = intl;
|
|
140
149
|
|
|
@@ -152,7 +161,7 @@ const WebPushCreate = ({
|
|
|
152
161
|
message: false,
|
|
153
162
|
});
|
|
154
163
|
const [brandIconOption, setBrandIconOption] = useState(BRAND_ICON_OPTIONS.DONT_SHOW);
|
|
155
|
-
const [onClickBehaviour, setOnClickBehaviour] = useState(ON_CLICK_BEHAVIOUR_OPTIONS.
|
|
164
|
+
const [onClickBehaviour, setOnClickBehaviour] = useState(ON_CLICK_BEHAVIOUR_OPTIONS.SITE_URL);
|
|
156
165
|
const [redirectUrl, setRedirectUrl] = useState('');
|
|
157
166
|
const [redirectUrlError, setRedirectUrlError] = useState('');
|
|
158
167
|
const [activeUploadField, setActiveUploadField] = useState(null);
|
|
@@ -213,6 +222,7 @@ const WebPushCreate = ({
|
|
|
213
222
|
eventContextTags,
|
|
214
223
|
});
|
|
215
224
|
const { tags, handleOnTagsContextChange, validationConfig } = tagState;
|
|
225
|
+
const { weCrmAccounts } = Templates;
|
|
216
226
|
|
|
217
227
|
// Edit mode detection: check creativesMode or presence of template ID
|
|
218
228
|
const isEditMode = useMemo(
|
|
@@ -239,11 +249,22 @@ const WebPushCreate = ({
|
|
|
239
249
|
templateData,
|
|
240
250
|
]);
|
|
241
251
|
|
|
252
|
+
const selectedWebPushAccount = useMemo(() =>
|
|
253
|
+
weCrmAccounts?.find(account => account?.id === accountId),
|
|
254
|
+
[weCrmAccounts, accountId]);
|
|
255
|
+
|
|
242
256
|
const websiteLink = useMemo(
|
|
243
|
-
() => accountData?.configs?.websiteLink || '',
|
|
244
|
-
[accountData?.configs?.websiteLink],
|
|
257
|
+
() => accountData?.configs?.websiteLink || selectedWebPushAccount?.configs?.websiteLink || '',
|
|
258
|
+
[accountData?.configs?.websiteLink, selectedWebPushAccount?.configs?.websiteLink],
|
|
245
259
|
);
|
|
246
260
|
|
|
261
|
+
// Fetch account details when websiteLink is missing but accountId exists
|
|
262
|
+
useEffect(() => {
|
|
263
|
+
if (!websiteLink && accountId && templateActionsProps?.getWeCrmAccounts) {
|
|
264
|
+
templateActionsProps.getWeCrmAccounts('WebPush');
|
|
265
|
+
}
|
|
266
|
+
}, [websiteLink, accountId, templateActionsProps?.getWeCrmAccounts]);
|
|
267
|
+
|
|
247
268
|
const previewUrl = useMemo(
|
|
248
269
|
() => (
|
|
249
270
|
onClickBehaviour === ON_CLICK_BEHAVIOUR_OPTIONS.REDIRECT_TO_URL
|
|
@@ -255,7 +276,7 @@ const WebPushCreate = ({
|
|
|
255
276
|
|
|
256
277
|
const onClickBehaviourOptions = useMemo(
|
|
257
278
|
() => ([
|
|
258
|
-
{ value: ON_CLICK_BEHAVIOUR_OPTIONS.
|
|
279
|
+
{ value: ON_CLICK_BEHAVIOUR_OPTIONS.SITE_URL, label: formatMessage(messages.openSite) },
|
|
259
280
|
{ value: ON_CLICK_BEHAVIOUR_OPTIONS.REDIRECT_TO_URL, label: formatMessage(messages.redirectToSpecificUrl) },
|
|
260
281
|
]),
|
|
261
282
|
[formatMessage],
|
|
@@ -349,13 +370,13 @@ const WebPushCreate = ({
|
|
|
349
370
|
setOnClickBehaviour(ON_CLICK_BEHAVIOUR_OPTIONS.REDIRECT_TO_URL);
|
|
350
371
|
setRedirectUrl(onClickAction?.url || '');
|
|
351
372
|
setRedirectUrlError('');
|
|
352
|
-
} else if (onClickAction?.type === ACTION_TYPES.
|
|
353
|
-
setOnClickBehaviour(ON_CLICK_BEHAVIOUR_OPTIONS.
|
|
354
|
-
setRedirectUrl(
|
|
373
|
+
} else if (onClickAction?.type === ACTION_TYPES.SITE_URL) {
|
|
374
|
+
setOnClickBehaviour(ON_CLICK_BEHAVIOUR_OPTIONS.SITE_URL);
|
|
375
|
+
setRedirectUrl(websiteLink);
|
|
355
376
|
setRedirectUrlError('');
|
|
356
377
|
} else {
|
|
357
|
-
setOnClickBehaviour(ON_CLICK_BEHAVIOUR_OPTIONS.
|
|
358
|
-
setRedirectUrl(
|
|
378
|
+
setOnClickBehaviour(ON_CLICK_BEHAVIOUR_OPTIONS.SITE_URL);
|
|
379
|
+
setRedirectUrl(websiteLink);
|
|
359
380
|
setRedirectUrlError('');
|
|
360
381
|
}
|
|
361
382
|
|
|
@@ -522,6 +543,15 @@ const WebPushCreate = ({
|
|
|
522
543
|
});
|
|
523
544
|
}, [isFullMode, templateName]);
|
|
524
545
|
|
|
546
|
+
// Pure validator that returns boolean without setting error state
|
|
547
|
+
const validateFormSilent = () => {
|
|
548
|
+
const templateNameInvalid = isFullMode && validateTemplateName(templateName);
|
|
549
|
+
const titleValidation = validateTitle(notificationTitle);
|
|
550
|
+
const messageValidation = validateMessageContent(message);
|
|
551
|
+
|
|
552
|
+
return !(templateNameInvalid || titleValidation || messageValidation);
|
|
553
|
+
};
|
|
554
|
+
|
|
525
555
|
const isFormValid = () => {
|
|
526
556
|
const templateNameInvalid = isFullMode && validateTemplateName(templateName);
|
|
527
557
|
const titleValidation = validateTitle(notificationTitle);
|
|
@@ -558,8 +588,24 @@ const WebPushCreate = ({
|
|
|
558
588
|
buttons,
|
|
559
589
|
onClickBehaviour,
|
|
560
590
|
redirectUrl,
|
|
591
|
+
websiteLink,
|
|
561
592
|
});
|
|
562
593
|
|
|
594
|
+
// In library mode (not full mode), use getFormData to communicate with parent
|
|
595
|
+
if (!isFullMode && getFormData) {
|
|
596
|
+
const formDataForLibrary = {
|
|
597
|
+
validity: true,
|
|
598
|
+
value: payload,
|
|
599
|
+
type: WEBPUSH,
|
|
600
|
+
};
|
|
601
|
+
getFormData(formDataForLibrary);
|
|
602
|
+
if (handleClose) {
|
|
603
|
+
handleClose();
|
|
604
|
+
}
|
|
605
|
+
return;
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
// Full mode: proceed with API calls
|
|
563
609
|
if (isEditMode) {
|
|
564
610
|
// Get template ID from params or templateData
|
|
565
611
|
const templateId = params?.id || templateData?._id;
|
|
@@ -649,6 +695,37 @@ const WebPushCreate = ({
|
|
|
649
695
|
}
|
|
650
696
|
}, [webPush?.editResponse, onCreateComplete, handleClose, isEditMode]);
|
|
651
697
|
|
|
698
|
+
// Handle getFormData request from parent (library mode)
|
|
699
|
+
useEffect(() => {
|
|
700
|
+
if (isGetFormData && getFormData && validateFormSilent()) {
|
|
701
|
+
const payload = createWebPushPayload({
|
|
702
|
+
templateName,
|
|
703
|
+
notificationTitle,
|
|
704
|
+
message,
|
|
705
|
+
mediaType,
|
|
706
|
+
accountId,
|
|
707
|
+
isFullMode,
|
|
708
|
+
imageSrc,
|
|
709
|
+
imageUrl,
|
|
710
|
+
imageUploadMethod: imageUpload?.uploadMethod,
|
|
711
|
+
brandIconOption,
|
|
712
|
+
brandIconSrc,
|
|
713
|
+
brandIconUrl,
|
|
714
|
+
buttons,
|
|
715
|
+
onClickBehaviour,
|
|
716
|
+
redirectUrl,
|
|
717
|
+
websiteLink,
|
|
718
|
+
});
|
|
719
|
+
const formDataForLibrary = {
|
|
720
|
+
validity: true,
|
|
721
|
+
value: payload,
|
|
722
|
+
type: WEBPUSH,
|
|
723
|
+
};
|
|
724
|
+
getFormData(formDataForLibrary);
|
|
725
|
+
}
|
|
726
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
727
|
+
}, [isGetFormData, getFormData]);
|
|
728
|
+
|
|
652
729
|
const isUploadingAsset = useMemo(
|
|
653
730
|
() => webPush?.assetUploading || false,
|
|
654
731
|
[webPush?.assetUploading],
|
|
@@ -872,7 +949,7 @@ const WebPushCreate = ({
|
|
|
872
949
|
() => (!accountId ? formatMessage(messages.accountRequired) : ''),
|
|
873
950
|
[accountId, formatMessage],
|
|
874
951
|
);
|
|
875
|
-
|
|
952
|
+
|
|
876
953
|
return (
|
|
877
954
|
<CapRow className="webpush-container">
|
|
878
955
|
<CapColumn className="content-section" span={14}>
|
|
@@ -972,6 +1049,8 @@ WebPushCreate.propTypes = {
|
|
|
972
1049
|
accountData: PropTypes.object,
|
|
973
1050
|
webPush: PropTypes.object,
|
|
974
1051
|
onCreateComplete: PropTypes.func,
|
|
1052
|
+
getFormData: PropTypes.func,
|
|
1053
|
+
isGetFormData: PropTypes.bool,
|
|
975
1054
|
templateData: PropTypes.object,
|
|
976
1055
|
creativesMode: PropTypes.string,
|
|
977
1056
|
params: PropTypes.object,
|
|
@@ -984,6 +1063,7 @@ WebPushCreate.propTypes = {
|
|
|
984
1063
|
forwardedTags: PropTypes.object,
|
|
985
1064
|
selectedOfferDetails: PropTypes.array,
|
|
986
1065
|
eventContextTags: PropTypes.array,
|
|
1066
|
+
templateActions: PropTypes.object,
|
|
987
1067
|
};
|
|
988
1068
|
|
|
989
1069
|
WebPushCreate.defaultProps = {
|
|
@@ -996,6 +1076,8 @@ WebPushCreate.defaultProps = {
|
|
|
996
1076
|
accountData: {},
|
|
997
1077
|
webPush: {},
|
|
998
1078
|
onCreateComplete: () => { },
|
|
1079
|
+
getFormData: null,
|
|
1080
|
+
isGetFormData: false,
|
|
999
1081
|
templateData: null,
|
|
1000
1082
|
creativesMode: 'createTemplate',
|
|
1001
1083
|
params: null,
|
|
@@ -1008,6 +1090,8 @@ WebPushCreate.defaultProps = {
|
|
|
1008
1090
|
forwardedTags: {},
|
|
1009
1091
|
selectedOfferDetails: [],
|
|
1010
1092
|
eventContextTags: [],
|
|
1093
|
+
templateActions: {},
|
|
1094
|
+
Templates: {},
|
|
1011
1095
|
};
|
|
1012
1096
|
|
|
1013
1097
|
const mapStateToProps = createStructuredSelector({
|
|
@@ -1018,6 +1102,7 @@ const mapStateToProps = createStructuredSelector({
|
|
|
1018
1102
|
editTemplateError: makeSelectEditError(),
|
|
1019
1103
|
metaEntities: makeSelectMetaEntities(),
|
|
1020
1104
|
injectedTags: setInjectedTags(),
|
|
1105
|
+
Templates: makeSelectTemplates(),
|
|
1021
1106
|
accountData: createSelector(
|
|
1022
1107
|
(state) => state.get('templates'),
|
|
1023
1108
|
(templatesState) => {
|
|
@@ -1032,14 +1117,21 @@ const mapStateToProps = createStructuredSelector({
|
|
|
1032
1117
|
|
|
1033
1118
|
const mapDispatchToProps = (dispatch) => ({
|
|
1034
1119
|
webPushActions: bindActionCreators(actions, dispatch),
|
|
1120
|
+
templateActions: bindActionCreators(templateActions, dispatch),
|
|
1035
1121
|
});
|
|
1036
1122
|
|
|
1037
|
-
const
|
|
1123
|
+
const withWebPushSaga = injectSaga({
|
|
1038
1124
|
key: 'webPush',
|
|
1039
1125
|
saga: webPushSagas,
|
|
1040
1126
|
mode: DAEMON,
|
|
1041
1127
|
});
|
|
1042
1128
|
|
|
1129
|
+
const withTemplateSaga = injectSaga({
|
|
1130
|
+
key: 'webPushTemplates',
|
|
1131
|
+
saga: v2TemplateSaga,
|
|
1132
|
+
mode: DAEMON,
|
|
1133
|
+
});
|
|
1134
|
+
|
|
1043
1135
|
const withReducer = injectReducer({
|
|
1044
1136
|
key: 'webPush',
|
|
1045
1137
|
reducer: webPushReducer,
|
|
@@ -1050,7 +1142,7 @@ export default withCreatives({
|
|
|
1050
1142
|
mapStateToProps,
|
|
1051
1143
|
mapDispatchToProps,
|
|
1052
1144
|
userAuth: true,
|
|
1053
|
-
sagas: [
|
|
1145
|
+
sagas: [withWebPushSaga, withTemplateSaga],
|
|
1054
1146
|
reducers: [withReducer],
|
|
1055
1147
|
});
|
|
1056
1148
|
|
|
@@ -32,6 +32,7 @@ const WebPushPreview = ({
|
|
|
32
32
|
imageSrc,
|
|
33
33
|
brandIconSrc,
|
|
34
34
|
buttons,
|
|
35
|
+
onClickShowInAllDevices,
|
|
35
36
|
}) => {
|
|
36
37
|
const [selectedOS, setSelectedOS] = useState(DEFAULT_OS);
|
|
37
38
|
const [selectedBrowser, setSelectedBrowser] = useState(DEFAULT_BROWSER);
|
|
@@ -56,7 +57,11 @@ const WebPushPreview = ({
|
|
|
56
57
|
}, [browserOptionsForOS, selectedBrowser]);
|
|
57
58
|
|
|
58
59
|
const handleOpenPreviewSlideBox = () => {
|
|
59
|
-
|
|
60
|
+
if (onClickShowInAllDevices) {
|
|
61
|
+
onClickShowInAllDevices();
|
|
62
|
+
} else {
|
|
63
|
+
setShowPreviewSlideBox(true);
|
|
64
|
+
}
|
|
60
65
|
};
|
|
61
66
|
|
|
62
67
|
const handleClosePreviewSlideBox = () => {
|
|
@@ -809,6 +809,390 @@ describe('WebPushPreview', () => {
|
|
|
809
809
|
});
|
|
810
810
|
});
|
|
811
811
|
|
|
812
|
+
describe('handleOpenPreviewSlideBox', () => {
|
|
813
|
+
// Helper to get the slidebox show state
|
|
814
|
+
const getSlideBoxShowState = (wrapper) => {
|
|
815
|
+
const allSlideBoxes = wrapper.find(CapSlideBox);
|
|
816
|
+
for (let i = 0; i < allSlideBoxes.length; i++) {
|
|
817
|
+
try {
|
|
818
|
+
const reactNode = allSlideBoxes.get(i);
|
|
819
|
+
if (reactNode && reactNode.props && reactNode.props.className === 'webpush-preview-slidebox') {
|
|
820
|
+
return reactNode.props.show;
|
|
821
|
+
}
|
|
822
|
+
} catch (e) {
|
|
823
|
+
continue;
|
|
824
|
+
}
|
|
825
|
+
}
|
|
826
|
+
return null;
|
|
827
|
+
};
|
|
828
|
+
|
|
829
|
+
it('should call onClickShowInAllDevices when provided', () => {
|
|
830
|
+
const mockOnClickShowInAllDevices = jest.fn();
|
|
831
|
+
const wrapper = mountWithIntl(
|
|
832
|
+
<WebPushPreview
|
|
833
|
+
{...defaultProps}
|
|
834
|
+
onClickShowInAllDevices={mockOnClickShowInAllDevices}
|
|
835
|
+
/>
|
|
836
|
+
);
|
|
837
|
+
const button = wrapper.find('[data-test-id="webpush-preview-toggle"]').first();
|
|
838
|
+
|
|
839
|
+
expect(mockOnClickShowInAllDevices).not.toHaveBeenCalled();
|
|
840
|
+
|
|
841
|
+
act(() => {
|
|
842
|
+
button.prop('onClick')();
|
|
843
|
+
});
|
|
844
|
+
wrapper.update();
|
|
845
|
+
|
|
846
|
+
expect(mockOnClickShowInAllDevices).toHaveBeenCalledTimes(1);
|
|
847
|
+
expect(mockOnClickShowInAllDevices).toHaveBeenCalledWith();
|
|
848
|
+
});
|
|
849
|
+
|
|
850
|
+
it('should not open slidebox when onClickShowInAllDevices is provided', () => {
|
|
851
|
+
const mockOnClickShowInAllDevices = jest.fn();
|
|
852
|
+
const wrapper = mountWithIntl(
|
|
853
|
+
<WebPushPreview
|
|
854
|
+
{...defaultProps}
|
|
855
|
+
onClickShowInAllDevices={mockOnClickShowInAllDevices}
|
|
856
|
+
/>
|
|
857
|
+
);
|
|
858
|
+
const button = wrapper.find('[data-test-id="webpush-preview-toggle"]').first();
|
|
859
|
+
|
|
860
|
+
expect(getSlideBoxShowState(wrapper)).toBe(false);
|
|
861
|
+
|
|
862
|
+
act(() => {
|
|
863
|
+
button.prop('onClick')();
|
|
864
|
+
});
|
|
865
|
+
wrapper.update();
|
|
866
|
+
|
|
867
|
+
// Slidebox should remain closed when onClickShowInAllDevices is provided
|
|
868
|
+
expect(getSlideBoxShowState(wrapper)).toBe(false);
|
|
869
|
+
expect(mockOnClickShowInAllDevices).toHaveBeenCalledTimes(1);
|
|
870
|
+
});
|
|
871
|
+
|
|
872
|
+
it('should open slidebox when onClickShowInAllDevices is not provided', () => {
|
|
873
|
+
const wrapper = mountWithIntl(<WebPushPreview {...defaultProps} />);
|
|
874
|
+
const button = wrapper.find('[data-test-id="webpush-preview-toggle"]').first();
|
|
875
|
+
|
|
876
|
+
expect(getSlideBoxShowState(wrapper)).toBe(false);
|
|
877
|
+
|
|
878
|
+
act(() => {
|
|
879
|
+
button.prop('onClick')();
|
|
880
|
+
});
|
|
881
|
+
wrapper.update();
|
|
882
|
+
|
|
883
|
+
expect(getSlideBoxShowState(wrapper)).toBe(true);
|
|
884
|
+
});
|
|
885
|
+
|
|
886
|
+
it('should handle multiple calls when onClickShowInAllDevices is provided', () => {
|
|
887
|
+
const mockOnClickShowInAllDevices = jest.fn();
|
|
888
|
+
const wrapper = mountWithIntl(
|
|
889
|
+
<WebPushPreview
|
|
890
|
+
{...defaultProps}
|
|
891
|
+
onClickShowInAllDevices={mockOnClickShowInAllDevices}
|
|
892
|
+
/>
|
|
893
|
+
);
|
|
894
|
+
const button = wrapper.find('[data-test-id="webpush-preview-toggle"]').first();
|
|
895
|
+
|
|
896
|
+
act(() => {
|
|
897
|
+
button.prop('onClick')();
|
|
898
|
+
});
|
|
899
|
+
wrapper.update();
|
|
900
|
+
|
|
901
|
+
act(() => {
|
|
902
|
+
button.prop('onClick')();
|
|
903
|
+
});
|
|
904
|
+
wrapper.update();
|
|
905
|
+
|
|
906
|
+
act(() => {
|
|
907
|
+
button.prop('onClick')();
|
|
908
|
+
});
|
|
909
|
+
wrapper.update();
|
|
910
|
+
|
|
911
|
+
expect(mockOnClickShowInAllDevices).toHaveBeenCalledTimes(3);
|
|
912
|
+
expect(getSlideBoxShowState(wrapper)).toBe(false);
|
|
913
|
+
});
|
|
914
|
+
|
|
915
|
+
it('should handle multiple calls when onClickShowInAllDevices is not provided', () => {
|
|
916
|
+
const wrapper = mountWithIntl(<WebPushPreview {...defaultProps} />);
|
|
917
|
+
const button = wrapper.find('[data-test-id="webpush-preview-toggle"]').first();
|
|
918
|
+
|
|
919
|
+
// First call - should open
|
|
920
|
+
act(() => {
|
|
921
|
+
button.prop('onClick')();
|
|
922
|
+
});
|
|
923
|
+
wrapper.update();
|
|
924
|
+
expect(getSlideBoxShowState(wrapper)).toBe(true);
|
|
925
|
+
|
|
926
|
+
// Second call - should remain open (state doesn't change)
|
|
927
|
+
act(() => {
|
|
928
|
+
button.prop('onClick')();
|
|
929
|
+
});
|
|
930
|
+
wrapper.update();
|
|
931
|
+
expect(getSlideBoxShowState(wrapper)).toBe(true);
|
|
932
|
+
|
|
933
|
+
// Third call - should remain open
|
|
934
|
+
act(() => {
|
|
935
|
+
button.prop('onClick')();
|
|
936
|
+
});
|
|
937
|
+
wrapper.update();
|
|
938
|
+
expect(getSlideBoxShowState(wrapper)).toBe(true);
|
|
939
|
+
});
|
|
940
|
+
|
|
941
|
+
it('should handle onClickShowInAllDevices being undefined', () => {
|
|
942
|
+
const wrapper = mountWithIntl(
|
|
943
|
+
<WebPushPreview
|
|
944
|
+
{...defaultProps}
|
|
945
|
+
onClickShowInAllDevices={undefined}
|
|
946
|
+
/>
|
|
947
|
+
);
|
|
948
|
+
const button = wrapper.find('[data-test-id="webpush-preview-toggle"]').first();
|
|
949
|
+
|
|
950
|
+
expect(getSlideBoxShowState(wrapper)).toBe(false);
|
|
951
|
+
|
|
952
|
+
act(() => {
|
|
953
|
+
button.prop('onClick')();
|
|
954
|
+
});
|
|
955
|
+
wrapper.update();
|
|
956
|
+
|
|
957
|
+
// Should open slidebox when prop is undefined
|
|
958
|
+
expect(getSlideBoxShowState(wrapper)).toBe(true);
|
|
959
|
+
});
|
|
960
|
+
|
|
961
|
+
it('should handle onClickShowInAllDevices being null', () => {
|
|
962
|
+
const wrapper = mountWithIntl(
|
|
963
|
+
<WebPushPreview
|
|
964
|
+
{...defaultProps}
|
|
965
|
+
onClickShowInAllDevices={null}
|
|
966
|
+
/>
|
|
967
|
+
);
|
|
968
|
+
const button = wrapper.find('[data-test-id="webpush-preview-toggle"]').first();
|
|
969
|
+
|
|
970
|
+
expect(getSlideBoxShowState(wrapper)).toBe(false);
|
|
971
|
+
|
|
972
|
+
act(() => {
|
|
973
|
+
button.prop('onClick')();
|
|
974
|
+
});
|
|
975
|
+
wrapper.update();
|
|
976
|
+
|
|
977
|
+
// Should open slidebox when prop is null (falsy)
|
|
978
|
+
expect(getSlideBoxShowState(wrapper)).toBe(true);
|
|
979
|
+
});
|
|
980
|
+
|
|
981
|
+
it('should handle onClickShowInAllDevices being an empty function', () => {
|
|
982
|
+
const mockOnClickShowInAllDevices = jest.fn(() => {});
|
|
983
|
+
const wrapper = mountWithIntl(
|
|
984
|
+
<WebPushPreview
|
|
985
|
+
{...defaultProps}
|
|
986
|
+
onClickShowInAllDevices={mockOnClickShowInAllDevices}
|
|
987
|
+
/>
|
|
988
|
+
);
|
|
989
|
+
const button = wrapper.find('[data-test-id="webpush-preview-toggle"]').first();
|
|
990
|
+
|
|
991
|
+
expect(getSlideBoxShowState(wrapper)).toBe(false);
|
|
992
|
+
|
|
993
|
+
act(() => {
|
|
994
|
+
button.prop('onClick')();
|
|
995
|
+
});
|
|
996
|
+
wrapper.update();
|
|
997
|
+
|
|
998
|
+
expect(mockOnClickShowInAllDevices).toHaveBeenCalledTimes(1);
|
|
999
|
+
expect(getSlideBoxShowState(wrapper)).toBe(false);
|
|
1000
|
+
});
|
|
1001
|
+
|
|
1002
|
+
it('should work correctly with slidebox close after opening without onClickShowInAllDevices', () => {
|
|
1003
|
+
const wrapper = mountWithIntl(<WebPushPreview {...defaultProps} />);
|
|
1004
|
+
const button = wrapper.find('[data-test-id="webpush-preview-toggle"]').first();
|
|
1005
|
+
|
|
1006
|
+
// Open slidebox
|
|
1007
|
+
act(() => {
|
|
1008
|
+
button.prop('onClick')();
|
|
1009
|
+
});
|
|
1010
|
+
wrapper.update();
|
|
1011
|
+
expect(getSlideBoxShowState(wrapper)).toBe(true);
|
|
1012
|
+
|
|
1013
|
+
// Close slidebox
|
|
1014
|
+
const allSlideBoxes = wrapper.find(CapSlideBox);
|
|
1015
|
+
let handleClose = null;
|
|
1016
|
+
for (let i = 0; i < allSlideBoxes.length; i++) {
|
|
1017
|
+
try {
|
|
1018
|
+
const reactNode = allSlideBoxes.get(i);
|
|
1019
|
+
if (reactNode && reactNode.props && reactNode.props.className === 'webpush-preview-slidebox') {
|
|
1020
|
+
handleClose = reactNode.props.handleClose;
|
|
1021
|
+
break;
|
|
1022
|
+
}
|
|
1023
|
+
} catch (e) {
|
|
1024
|
+
continue;
|
|
1025
|
+
}
|
|
1026
|
+
}
|
|
1027
|
+
|
|
1028
|
+
act(() => {
|
|
1029
|
+
handleClose();
|
|
1030
|
+
});
|
|
1031
|
+
wrapper.update();
|
|
1032
|
+
expect(getSlideBoxShowState(wrapper)).toBe(false);
|
|
1033
|
+
|
|
1034
|
+
// Open again
|
|
1035
|
+
act(() => {
|
|
1036
|
+
button.prop('onClick')();
|
|
1037
|
+
});
|
|
1038
|
+
wrapper.update();
|
|
1039
|
+
expect(getSlideBoxShowState(wrapper)).toBe(true);
|
|
1040
|
+
});
|
|
1041
|
+
|
|
1042
|
+
it('should handle onClickShowInAllDevices that throws an error gracefully', () => {
|
|
1043
|
+
const mockOnClickShowInAllDevices = jest.fn(() => {
|
|
1044
|
+
throw new Error('Test error');
|
|
1045
|
+
});
|
|
1046
|
+
const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
|
|
1047
|
+
|
|
1048
|
+
const wrapper = mountWithIntl(
|
|
1049
|
+
<WebPushPreview
|
|
1050
|
+
{...defaultProps}
|
|
1051
|
+
onClickShowInAllDevices={mockOnClickShowInAllDevices}
|
|
1052
|
+
/>
|
|
1053
|
+
);
|
|
1054
|
+
const button = wrapper.find('[data-test-id="webpush-preview-toggle"]').first();
|
|
1055
|
+
|
|
1056
|
+
expect(() => {
|
|
1057
|
+
act(() => {
|
|
1058
|
+
button.prop('onClick')();
|
|
1059
|
+
});
|
|
1060
|
+
}).toThrow('Test error');
|
|
1061
|
+
wrapper.update();
|
|
1062
|
+
|
|
1063
|
+
expect(mockOnClickShowInAllDevices).toHaveBeenCalledTimes(1);
|
|
1064
|
+
expect(getSlideBoxShowState(wrapper)).toBe(false);
|
|
1065
|
+
|
|
1066
|
+
consoleErrorSpy.mockRestore();
|
|
1067
|
+
});
|
|
1068
|
+
|
|
1069
|
+
it('should handle rapid clicks when onClickShowInAllDevices is provided', () => {
|
|
1070
|
+
const mockOnClickShowInAllDevices = jest.fn();
|
|
1071
|
+
const wrapper = mountWithIntl(
|
|
1072
|
+
<WebPushPreview
|
|
1073
|
+
{...defaultProps}
|
|
1074
|
+
onClickShowInAllDevices={mockOnClickShowInAllDevices}
|
|
1075
|
+
/>
|
|
1076
|
+
);
|
|
1077
|
+
const button = wrapper.find('[data-test-id="webpush-preview-toggle"]').first();
|
|
1078
|
+
|
|
1079
|
+
// Rapid clicks
|
|
1080
|
+
act(() => {
|
|
1081
|
+
button.prop('onClick')();
|
|
1082
|
+
button.prop('onClick')();
|
|
1083
|
+
button.prop('onClick')();
|
|
1084
|
+
});
|
|
1085
|
+
wrapper.update();
|
|
1086
|
+
|
|
1087
|
+
expect(mockOnClickShowInAllDevices).toHaveBeenCalledTimes(3);
|
|
1088
|
+
expect(getSlideBoxShowState(wrapper)).toBe(false);
|
|
1089
|
+
});
|
|
1090
|
+
|
|
1091
|
+
it('should handle rapid clicks when onClickShowInAllDevices is not provided', () => {
|
|
1092
|
+
const wrapper = mountWithIntl(<WebPushPreview {...defaultProps} />);
|
|
1093
|
+
const button = wrapper.find('[data-test-id="webpush-preview-toggle"]').first();
|
|
1094
|
+
|
|
1095
|
+
// Rapid clicks
|
|
1096
|
+
act(() => {
|
|
1097
|
+
button.prop('onClick')();
|
|
1098
|
+
button.prop('onClick')();
|
|
1099
|
+
button.prop('onClick')();
|
|
1100
|
+
});
|
|
1101
|
+
wrapper.update();
|
|
1102
|
+
|
|
1103
|
+
// Should be open after rapid clicks
|
|
1104
|
+
expect(getSlideBoxShowState(wrapper)).toBe(true);
|
|
1105
|
+
});
|
|
1106
|
+
|
|
1107
|
+
it('should maintain correct behavior when props change from provided to not provided', () => {
|
|
1108
|
+
const mockOnClickShowInAllDevices = jest.fn();
|
|
1109
|
+
const wrapper = mountWithIntl(
|
|
1110
|
+
<WebPushPreview
|
|
1111
|
+
{...defaultProps}
|
|
1112
|
+
onClickShowInAllDevices={mockOnClickShowInAllDevices}
|
|
1113
|
+
/>
|
|
1114
|
+
);
|
|
1115
|
+
const button = wrapper.find('[data-test-id="webpush-preview-toggle"]').first();
|
|
1116
|
+
|
|
1117
|
+
// First call with onClickShowInAllDevices provided
|
|
1118
|
+
act(() => {
|
|
1119
|
+
button.prop('onClick')();
|
|
1120
|
+
});
|
|
1121
|
+
wrapper.update();
|
|
1122
|
+
expect(mockOnClickShowInAllDevices).toHaveBeenCalledTimes(1);
|
|
1123
|
+
expect(getSlideBoxShowState(wrapper)).toBe(false);
|
|
1124
|
+
|
|
1125
|
+
// Remove onClickShowInAllDevices prop
|
|
1126
|
+
act(() => {
|
|
1127
|
+
wrapper.setProps({ onClickShowInAllDevices: undefined });
|
|
1128
|
+
});
|
|
1129
|
+
wrapper.update();
|
|
1130
|
+
|
|
1131
|
+
// Get the button again after props change
|
|
1132
|
+
const updatedButton = wrapper.find('[data-test-id="webpush-preview-toggle"]').first();
|
|
1133
|
+
|
|
1134
|
+
// Second call without onClickShowInAllDevices
|
|
1135
|
+
act(() => {
|
|
1136
|
+
updatedButton.prop('onClick')();
|
|
1137
|
+
});
|
|
1138
|
+
wrapper.update();
|
|
1139
|
+
// The mock should still have been called only once (from the first call)
|
|
1140
|
+
expect(mockOnClickShowInAllDevices).toHaveBeenCalledTimes(1);
|
|
1141
|
+
expect(getSlideBoxShowState(wrapper)).toBe(true); // Should open slidebox
|
|
1142
|
+
});
|
|
1143
|
+
|
|
1144
|
+
it('should maintain correct behavior when props change from not provided to provided', () => {
|
|
1145
|
+
const wrapper = mountWithIntl(<WebPushPreview {...defaultProps} />);
|
|
1146
|
+
const button = wrapper.find('[data-test-id="webpush-preview-toggle"]').first();
|
|
1147
|
+
|
|
1148
|
+
// First call without onClickShowInAllDevices
|
|
1149
|
+
act(() => {
|
|
1150
|
+
button.prop('onClick')();
|
|
1151
|
+
});
|
|
1152
|
+
wrapper.update();
|
|
1153
|
+
expect(getSlideBoxShowState(wrapper)).toBe(true);
|
|
1154
|
+
|
|
1155
|
+
// Close slidebox
|
|
1156
|
+
const allSlideBoxes = wrapper.find(CapSlideBox);
|
|
1157
|
+
let handleClose = null;
|
|
1158
|
+
for (let i = 0; i < allSlideBoxes.length; i++) {
|
|
1159
|
+
try {
|
|
1160
|
+
const reactNode = allSlideBoxes.get(i);
|
|
1161
|
+
if (reactNode && reactNode.props && reactNode.props.className === 'webpush-preview-slidebox') {
|
|
1162
|
+
handleClose = reactNode.props.handleClose;
|
|
1163
|
+
break;
|
|
1164
|
+
}
|
|
1165
|
+
} catch (e) {
|
|
1166
|
+
continue;
|
|
1167
|
+
}
|
|
1168
|
+
}
|
|
1169
|
+
|
|
1170
|
+
act(() => {
|
|
1171
|
+
handleClose();
|
|
1172
|
+
});
|
|
1173
|
+
wrapper.update();
|
|
1174
|
+
expect(getSlideBoxShowState(wrapper)).toBe(false);
|
|
1175
|
+
|
|
1176
|
+
// Add onClickShowInAllDevices prop
|
|
1177
|
+
const mockOnClickShowInAllDevices = jest.fn();
|
|
1178
|
+
act(() => {
|
|
1179
|
+
wrapper.setProps({ onClickShowInAllDevices: mockOnClickShowInAllDevices });
|
|
1180
|
+
});
|
|
1181
|
+
wrapper.update();
|
|
1182
|
+
|
|
1183
|
+
// Get the button again after props change
|
|
1184
|
+
const updatedButton = wrapper.find('[data-test-id="webpush-preview-toggle"]').first();
|
|
1185
|
+
|
|
1186
|
+
// Second call with onClickShowInAllDevices
|
|
1187
|
+
act(() => {
|
|
1188
|
+
updatedButton.prop('onClick')();
|
|
1189
|
+
});
|
|
1190
|
+
wrapper.update();
|
|
1191
|
+
expect(mockOnClickShowInAllDevices).toHaveBeenCalledTimes(1);
|
|
1192
|
+
expect(getSlideBoxShowState(wrapper)).toBe(false); // Should not open slidebox
|
|
1193
|
+
});
|
|
1194
|
+
});
|
|
1195
|
+
|
|
812
1196
|
describe('Component Integration', () => {
|
|
813
1197
|
it('should update all child components when OS changes', () => {
|
|
814
1198
|
const wrapper = mountWithIntl(<WebPushPreview {...defaultProps} />);
|
|
@@ -28,6 +28,7 @@ export const createWebPushPayload = ({
|
|
|
28
28
|
buttons,
|
|
29
29
|
onClickBehaviour,
|
|
30
30
|
redirectUrl,
|
|
31
|
+
websiteLink
|
|
31
32
|
}) => {
|
|
32
33
|
const trimmedTemplateName = (templateName || '').trim();
|
|
33
34
|
const trimmedTitle = (notificationTitle || '').trim();
|
|
@@ -61,9 +62,10 @@ export const createWebPushPayload = ({
|
|
|
61
62
|
type: ACTION_TYPES.URL,
|
|
62
63
|
url: redirectUrl.trim(),
|
|
63
64
|
};
|
|
64
|
-
} else if (onClickBehaviour === ON_CLICK_BEHAVIOUR_OPTIONS.
|
|
65
|
+
} else if (onClickBehaviour === ON_CLICK_BEHAVIOUR_OPTIONS.SITE_URL) {
|
|
65
66
|
webpushContent.onClickAction = {
|
|
66
|
-
type: ACTION_TYPES.
|
|
67
|
+
type: ACTION_TYPES.SITE_URL,
|
|
68
|
+
url: websiteLink,
|
|
67
69
|
};
|
|
68
70
|
}
|
|
69
71
|
|