@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.
Files changed (46) hide show
  1. package/constants/unified.js +0 -1
  2. package/package.json +1 -1
  3. package/utils/common.js +0 -6
  4. package/v2Components/FormBuilder/Classic.js +3 -0
  5. package/v2Containers/Cap/tests/index.test.js +1 -0
  6. package/v2Containers/CreativesContainer/SlideBoxContent.js +39 -30
  7. package/v2Containers/CreativesContainer/SlideBoxFooter.js +6 -2
  8. package/v2Containers/CreativesContainer/constants.js +1 -0
  9. package/v2Containers/CreativesContainer/index.js +28 -3
  10. package/v2Containers/CreativesContainer/messages.js +4 -0
  11. package/v2Containers/CreativesContainer/tests/SlideBoxFooter.test.js +40 -12
  12. package/v2Containers/CreativesContainer/tests/__snapshots__/index.test.js.snap +10 -0
  13. package/v2Containers/CreativesContainer/tests/switchToEditAfterCreate.test.js +152 -0
  14. package/v2Containers/Email/index.js +41 -6
  15. package/v2Containers/Email/tests/index.test.js +123 -0
  16. package/v2Containers/EmailWrapper/components/EmailHTMLEditor.js +32 -12
  17. package/v2Containers/EmailWrapper/components/__tests__/EmailHTMLEditor.test.js +34 -0
  18. package/v2Containers/InApp/index.js +19 -23
  19. package/v2Containers/InApp/tests/index.test.js +52 -9
  20. package/v2Containers/InappAdvance/index.js +19 -24
  21. package/v2Containers/InappAdvance/tests/index.test.js +39 -15
  22. package/v2Containers/Line/Container/index.js +18 -8
  23. package/v2Containers/Line/Container/tests/__snapshots__/index.test.js.snap +129 -108
  24. package/v2Containers/Line/Container/tests/index.test.js +68 -0
  25. package/v2Containers/MobilePush/Create/index.js +24 -5
  26. package/v2Containers/MobilePush/Create/test/stayOpenOnSave.test.js +80 -0
  27. package/v2Containers/MobilePush/Edit/index.js +15 -9
  28. package/v2Containers/MobilePush/Edit/test/stayOpenOnSave.test.js +60 -0
  29. package/v2Containers/MobilePushNew/index.js +41 -7
  30. package/v2Containers/MobilePushNew/tests/index.test.js +61 -0
  31. package/v2Containers/Sms/Create/index.js +32 -14
  32. package/v2Containers/Sms/Create/test/stayOpenOnSave.test.js +92 -0
  33. package/v2Containers/Sms/Edit/index.js +22 -14
  34. package/v2Containers/Sms/Edit/tests/index.test.js +66 -0
  35. package/v2Containers/Templates/constants.js +1 -0
  36. package/v2Containers/Templates/index.js +48 -31
  37. package/v2Containers/Templates/messages.js +4 -0
  38. package/v2Containers/Templates/tests/__snapshots__/index.test.js.snap +128 -4
  39. package/v2Containers/Templates/tests/archival.test.js +221 -0
  40. package/v2Containers/Viber/index.js +15 -6
  41. package/v2Containers/Viber/tests/index.test.js +97 -28
  42. package/v2Containers/WebPush/Create/components/FormActions.js +1 -1
  43. package/v2Containers/WebPush/Create/components/FormActions.test.js +3 -3
  44. package/v2Containers/WebPush/Create/index.js +32 -6
  45. package/v2Containers/WebPush/Create/messages.js +0 -4
  46. package/v2Containers/WebPush/Create/tests/stayOpenOnSave.test.js +268 -0
@@ -0,0 +1,268 @@
1
+ /**
2
+ * Full mode keeps the slidebox open after saving: a create continues in place (the next save
3
+ * updates the created template); edits stay open. Mocks mirror contentValidity.test.js, with the
4
+ * template name and save action wired to real handlers.
5
+ */
6
+
7
+ import React from 'react';
8
+ import { render, screen, fireEvent } from '@testing-library/react';
9
+ import '@testing-library/jest-dom';
10
+ import { IntlProvider } from 'react-intl';
11
+
12
+ // ── Infrastructure / HOC mocks ──────────────────────────────────────────────
13
+ jest.mock('../../../../hoc/withCreatives', () => ({
14
+ __esModule: true,
15
+ default: ({ WrappedComponent }) => WrappedComponent,
16
+ }));
17
+
18
+ jest.mock('@capillarytech/vulcan-react-sdk/utils', () => ({
19
+ injectReducer: () => (Component) => Component,
20
+ injectSaga: () => (Component) => Component,
21
+ }));
22
+ jest.mock('@capillarytech/vulcan-react-sdk/utils/sagaInjectorTypes', () => ({
23
+ DAEMON: 'DAEMON',
24
+ }));
25
+
26
+ // ── Redux slices (paths relative to this test file) ─────────────────────────
27
+ jest.mock('../../../Templates/selectors', () => ({
28
+ makeSelectTemplates: () => () => {},
29
+ }));
30
+ jest.mock('../../../Templates/actions', () => ({}));
31
+ jest.mock('../../selectors', () => ({
32
+ makeSelectWebPush: () => () => ({}),
33
+ makeSelectCreateError: () => () => null,
34
+ makeSelectCreateTemplateInProgress: () => () => false,
35
+ makeSelectEditTemplateInProgress: () => () => false,
36
+ makeSelectEditError: () => () => null,
37
+ }));
38
+ jest.mock('../../actions', () => ({}));
39
+ jest.mock('../../reducer', () => () => ({}));
40
+ jest.mock('../../sagas', () => ({}));
41
+ jest.mock('../../../Templates/sagas', () => ({ v2TemplateSaga: {} }));
42
+ jest.mock('../../../Cap/selectors', () => ({
43
+ makeSelectMetaEntities: () => () => [],
44
+ setInjectedTags: () => () => [],
45
+ }));
46
+ jest.mock('../../../CreativesContainer/constants', () => ({
47
+ WEBPUSH: 'WEBPUSH',
48
+ }));
49
+ jest.mock('../../../CreativesContainer/messages', () => ({
50
+ testAndPreview: { id: 'testAndPreview', defaultMessage: 'Test & Preview' },
51
+ }));
52
+ jest.mock('../../../../utils/common', () => ({
53
+ isAiContentBotDisabled: () => false,
54
+ }));
55
+
56
+ // ── Form section components ──────────────────────────────────────────────────
57
+ // TemplateNameSection stays a stub — it is only relevant in isFullMode, which these tests
58
+ // keep off (mirrors baseProps below) to focus purely on title/message emptiness.
59
+ jest.mock('../components/TemplateNameSection', () => ({
60
+ __esModule: true,
61
+ default: ({ value, onChange }) => (
62
+ <input data-testid="template-name-input" value={value} onChange={onChange} />
63
+ ),
64
+ }));
65
+ // NotificationTitleSection and MessageSection are given lightweight controlled-input stand-ins
66
+ // so tests can drive real onChange handlers (handleNotificationTitleChange / handleMessageChange)
67
+ // inside WebPushCreate, exercising the actual state that feeds the isContentEmpty computation.
68
+ jest.mock('../components/NotificationTitleSection', () => ({
69
+ __esModule: true,
70
+ default: ({ value, onChange }) => (
71
+ <input data-testid="notification-title-input" value={value} onChange={onChange} />
72
+ ),
73
+ }));
74
+ jest.mock('../components/MessageSection', () => ({
75
+ __esModule: true,
76
+ default: ({ value, onChange }) => (
77
+ <textarea data-testid="message-input" value={value} onChange={onChange} />
78
+ ),
79
+ }));
80
+ jest.mock('../components/MediaSection', () => ({
81
+ __esModule: true,
82
+ default: () => <div data-testid="media-section" />,
83
+ }));
84
+ jest.mock('../components/BrandIconSection', () => ({
85
+ __esModule: true,
86
+ default: () => <div data-testid="brand-icon-section" />,
87
+ }));
88
+ jest.mock('../components/ButtonsLinksSection', () => ({
89
+ __esModule: true,
90
+ default: () => <div data-testid="buttons-links-section" />,
91
+ }));
92
+ jest.mock('../components/FormActions', () => ({
93
+ __esModule: true,
94
+ default: ({ onSave }) => <button data-testid="save-button" onClick={onSave}>Save</button>,
95
+ }));
96
+ jest.mock('../utils/payloadBuilder', () => ({
97
+ createWebPushPayload: ({ templateName }) => ({ name: templateName, type: 'WEBPUSH' }),
98
+ }));
99
+ jest.mock('../preview/WebPushPreview', () => ({
100
+ __esModule: true,
101
+ default: () => <div data-testid="webpush-preview" />,
102
+ }));
103
+ jest.mock('../../../TagList', () => ({
104
+ __esModule: true,
105
+ default: () => <div data-testid="tag-list" />,
106
+ }));
107
+
108
+ // ── Hooks ────────────────────────────────────────────────────────────────────
109
+ jest.mock('../hooks/useCharacterCount', () => ({
110
+ useCharacterCount: () => ({ updateCharacterCount: jest.fn() }),
111
+ }));
112
+ jest.mock('../hooks/useButtonManagement', () => ({
113
+ useButtonManagement: () => ({ buttons: [], setButtons: jest.fn(), isAddingButton: false }),
114
+ }));
115
+ jest.mock('../hooks/useImageUpload', () => ({
116
+ useImageUpload: () => ({
117
+ imageSrc: '',
118
+ imageUrl: '',
119
+ isValidating: false,
120
+ isUploading: false,
121
+ setImageSrc: jest.fn(),
122
+ setImageUrl: jest.fn(),
123
+ uploadMethod: 'UPLOAD_IMAGE',
124
+ }),
125
+ }));
126
+ jest.mock('../hooks/useTagManagement', () => ({
127
+ useTagManagement: () => ({ tags: [], extractTags: jest.fn(), handleOnTagsContextChange: jest.fn(), validationConfig: {} }),
128
+ }));
129
+
130
+ jest.mock('../../../../v2Components/TestAndPreviewSlidebox', () => ({
131
+ __esModule: true,
132
+ default: () => <div data-testid="test-and-preview-slidebox" />,
133
+ }));
134
+
135
+ // ── CapUI library ─────────────────────────────────────────────────────────────
136
+ jest.mock('@capillarytech/cap-ui-library/CapRow', () => ({
137
+ __esModule: true,
138
+ default: ({ children, className }) => <div className={className}>{children}</div>,
139
+ }));
140
+ jest.mock('@capillarytech/cap-ui-library/CapColumn', () => ({
141
+ __esModule: true,
142
+ default: ({ children, className }) => <div className={className}>{children}</div>,
143
+ }));
144
+ jest.mock('@capillarytech/cap-ui-library/CapButton', () => ({
145
+ __esModule: true,
146
+ default: ({ children, onClick, className, type }) => (
147
+ <button className={className} onClick={onClick} data-type={type}>
148
+ {children}
149
+ </button>
150
+ ),
151
+ }));
152
+
153
+ // ── Import component AFTER mocks ─────────────────────────────────────────────
154
+ // eslint-disable-next-line import/first
155
+ import WebPushCreateDefault from '../index';
156
+
157
+ const baseProps = (overrides = {}) => ({
158
+ isFullMode: true,
159
+ canStayOpenAfterCreate: true,
160
+ onCreateContinuedAsEdit: jest.fn(),
161
+ handleClose: jest.fn(),
162
+ webPushActions: {
163
+ createTemplate: jest.fn(),
164
+ editTemplate: jest.fn(),
165
+ uploadImage: jest.fn(),
166
+ clearCreateError: jest.fn(),
167
+ clearEditError: jest.fn(),
168
+ clearCreateResponse: jest.fn(),
169
+ clearEditResponse: jest.fn(),
170
+ setSelectedAccount: jest.fn(),
171
+ clearState: jest.fn(),
172
+ },
173
+ createTemplateInProgress: false,
174
+ createTemplateError: null,
175
+ editTemplateInProgress: false,
176
+ editTemplateError: null,
177
+ accountData: { accountId: 'acc-1' },
178
+ webPush: {},
179
+ onCreateComplete: jest.fn(),
180
+ getFormData: jest.fn(),
181
+ isGetFormData: false,
182
+ templateData: null,
183
+ creativesMode: 'create',
184
+ params: {},
185
+ globalActions: { getMetaEntities: jest.fn() },
186
+ location: { pathname: '/webpush/create', query: {}, search: '', state: {} },
187
+ metaEntities: [],
188
+ injectedTags: [],
189
+ getDefaultTags: jest.fn(),
190
+ supportedTags: [],
191
+ forwardedTags: [],
192
+ selectedOfferDetails: [],
193
+ eventContextTags: [],
194
+ waitEventContextTags: {},
195
+ templateActions: {},
196
+ Templates: { templates: [] },
197
+ restrictPersonalization: false,
198
+ ...overrides,
199
+ });
200
+
201
+ const renderCreate = (props) => {
202
+ const ui = (p) => (
203
+ <IntlProvider locale="en" messages={{}}>
204
+ <WebPushCreateDefault {...p} />
205
+ </IntlProvider>
206
+ );
207
+ const utils = render(ui(props));
208
+ return { ...utils, rerenderWith: (p) => utils.rerender(ui(p)) };
209
+ };
210
+
211
+ const fillForm = () => {
212
+ fireEvent.change(screen.getByTestId('template-name-input'), { target: { value: 'Promo' } });
213
+ fireEvent.change(screen.getByTestId('notification-title-input'), { target: { value: 'Hello' } });
214
+ fireEvent.change(screen.getByTestId('message-input'), { target: { value: 'World' } });
215
+ };
216
+
217
+ describe('WebPushCreate — full mode keeps the slidebox open after saving', () => {
218
+ it('create: stays open with the form as is, and the next save updates the created template', () => {
219
+ const props = baseProps();
220
+ const { rerenderWith } = renderCreate(props);
221
+ fillForm();
222
+
223
+ fireEvent.click(screen.getByTestId('save-button'));
224
+ expect(props.webPushActions.createTemplate).toHaveBeenCalledTimes(1);
225
+
226
+ rerenderWith({ ...props, webPush: { response: { templateId: 'wp-1' } } });
227
+ expect(props.handleClose).not.toHaveBeenCalled();
228
+ expect(props.onCreateComplete).not.toHaveBeenCalled();
229
+ expect(screen.getByTestId('notification-title-input')).toHaveValue('Hello');
230
+ expect(props.onCreateContinuedAsEdit).toHaveBeenCalledTimes(1); // host shows the edit header
231
+
232
+ fireEvent.click(screen.getByTestId('save-button'));
233
+ expect(props.webPushActions.createTemplate).toHaveBeenCalledTimes(1);
234
+ expect(props.webPushActions.editTemplate).toHaveBeenCalledTimes(1);
235
+ expect(props.webPushActions.editTemplate.mock.calls[0][0]._id).toBe('wp-1');
236
+
237
+ const onSaved = props.webPushActions.editTemplate.mock.calls[0][1];
238
+ onSaved({ templateId: 'wp-1' }, undefined);
239
+ expect(props.handleClose).not.toHaveBeenCalled();
240
+ expect(props.onCreateComplete).not.toHaveBeenCalled();
241
+ });
242
+
243
+ it('create without edit permission closes as before', () => {
244
+ const props = baseProps({ canStayOpenAfterCreate: false });
245
+ const { rerenderWith } = renderCreate(props);
246
+ fillForm();
247
+
248
+ fireEvent.click(screen.getByTestId('save-button'));
249
+ rerenderWith({ ...props, webPush: { response: { templateId: 'wp-1' } } });
250
+
251
+ expect(props.onCreateComplete).toHaveBeenCalledWith(true);
252
+ expect(props.handleClose).toHaveBeenCalled();
253
+ expect(props.onCreateContinuedAsEdit).not.toHaveBeenCalled();
254
+ });
255
+
256
+ it('edit: stays open after a successful update', () => {
257
+ const props = baseProps({ creativesMode: 'edit', params: { id: 'wp-9' } });
258
+ renderCreate(props);
259
+ fillForm();
260
+
261
+ fireEvent.click(screen.getByTestId('save-button'));
262
+ expect(props.webPushActions.editTemplate.mock.calls[0][0]._id).toBe('wp-9');
263
+ props.webPushActions.editTemplate.mock.calls[0][1]({ templateId: 'wp-9' }, undefined);
264
+
265
+ expect(props.handleClose).not.toHaveBeenCalled();
266
+ expect(props.onCreateComplete).not.toHaveBeenCalled();
267
+ });
268
+ });