@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.
@@ -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.OPEN_SITE);
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.OPEN_SITE, label: formatMessage(messages.openSite) },
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.OPEN_SITE) {
353
- setOnClickBehaviour(ON_CLICK_BEHAVIOUR_OPTIONS.OPEN_SITE);
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.OPEN_SITE);
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 withSaga = injectSaga({
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: [withSaga],
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
- setShowPreviewSlideBox(true);
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.OPEN_SITE) {
65
+ } else if (onClickBehaviour === ON_CLICK_BEHAVIOUR_OPTIONS.SITE_URL) {
65
66
  webpushContent.onClickAction = {
66
- type: ACTION_TYPES.OPEN_SITE,
67
+ type: ACTION_TYPES.SITE_URL,
68
+ url: websiteLink,
67
69
  };
68
70
  }
69
71