@capillarytech/creatives-library 9.0.56-betaa.0 → 9.0.56

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 (116) hide show
  1. package/constants/unified.js +9 -4
  2. package/package.json +1 -1
  3. package/services/api.js +25 -4
  4. package/utils/common.js +0 -12
  5. package/utils/templateVarUtils.js +16 -0
  6. package/utils/tests/templateVarUtils.test.js +44 -0
  7. package/v2Components/CapActionButton/index.js +73 -60
  8. package/v2Components/CapActionButton/index.scss +44 -28
  9. package/v2Components/CapActionButton/messages.js +7 -3
  10. package/v2Components/CapActionButton/tests/index.test.js +17 -1
  11. package/v2Components/CapWhatsappCTA/messages.js +4 -0
  12. package/v2Components/CapWhatsappCarouselButton/index.js +42 -33
  13. package/v2Components/CapWhatsappCarouselButton/index.scss +44 -2
  14. package/v2Components/CommonTestAndPreview/UnifiedPreview/index.js +13 -0
  15. package/v2Components/CommonTestAndPreview/actions.js +13 -0
  16. package/v2Components/CommonTestAndPreview/constants.js +8 -0
  17. package/v2Components/CommonTestAndPreview/index.js +55 -3
  18. package/v2Components/CommonTestAndPreview/reducer.js +20 -0
  19. package/v2Components/CommonTestAndPreview/sagas.js +28 -0
  20. package/v2Components/CommonTestAndPreview/selectors.js +6 -0
  21. package/v2Components/CommonTestAndPreview/tests/UnifiedPreview/index.test.js +106 -0
  22. package/v2Components/CommonTestAndPreview/tests/actions.test.js +15 -0
  23. package/v2Components/CommonTestAndPreview/tests/constants.test.js +9 -1
  24. package/v2Components/CommonTestAndPreview/tests/index.test.js +101 -0
  25. package/v2Components/CommonTestAndPreview/tests/reducer.test.js +41 -0
  26. package/v2Components/CommonTestAndPreview/tests/sagas.test.js +84 -0
  27. package/v2Components/CommonTestAndPreview/tests/selectors.test.js +12 -0
  28. package/v2Components/FormBuilder/Functional/FormBuilderShell.js +46 -320
  29. package/v2Components/FormBuilder/Functional/channels/registry.js +0 -2
  30. package/v2Components/FormBuilder/Functional/constants.js +8 -43
  31. package/v2Components/FormBuilder/Functional/core/schema/initializeFormState.js +28 -175
  32. package/v2Components/FormBuilder/Functional/core/store/formReducer.js +6 -75
  33. package/v2Components/FormBuilder/Functional/core/store/toLegacyFormData.js +20 -10
  34. package/v2Components/FormBuilder/Functional/layout/FieldSlot.js +1 -9
  35. package/v2Components/FormBuilder/Functional/layout/SchemaForm.js +3 -20
  36. package/v2Components/FormBuilder/Functional/layout/Section.js +1 -6
  37. package/v2Components/FormBuilder/Functional/tests/fieldSlot.test.js +2 -9
  38. package/v2Components/FormBuilder/Functional/tests/schemaForm.test.js +0 -17
  39. package/v2Components/FormBuilder/index.js +12 -34
  40. package/v2Components/FormBuilder/tests/entryGate.test.js +7 -94
  41. package/v2Components/TestAndPreviewSlidebox/index.js +9 -0
  42. package/v2Containers/CommunicationFlow/CommunicationFlow.js +140 -75
  43. package/v2Containers/CommunicationFlow/CommunicationFlow.scss +18 -2
  44. package/v2Containers/CommunicationFlow/CommunicationFlowCard.js +67 -28
  45. package/v2Containers/CommunicationFlow/Tests/CommunicationFlow.test.js +503 -51
  46. package/v2Containers/CommunicationFlow/Tests/CommunicationFlowCard.test.js +98 -4
  47. package/v2Containers/CommunicationFlow/Tests/constants.test.js +85 -0
  48. package/v2Containers/CommunicationFlow/constants.js +65 -4
  49. package/v2Containers/CommunicationFlow/index.js +16 -23
  50. package/v2Containers/CommunicationFlow/messages.js +11 -3
  51. package/v2Containers/CommunicationFlow/steps/ChannelSelectionStep/ChannelSelectionStep.js +27 -11
  52. package/v2Containers/CommunicationFlow/steps/ChannelSelectionStep/ChannelSelectionStep.scss +4 -0
  53. package/v2Containers/CommunicationFlow/steps/ChannelSelectionStep/Tests/ChannelSelectionStep.test.js +176 -4
  54. package/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/DeliverySettingsSection.js +22 -4
  55. package/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/SenderDetails.js +14 -4
  56. package/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/Tests/DeliverySettingsSection.test.js +11 -0
  57. package/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/Tests/SenderDetails.test.js +51 -0
  58. package/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/Tests/deliverySettingsConfig.test.js +11 -4
  59. package/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/deliverySettingsConfig.js +19 -5
  60. package/v2Containers/CreativesContainer/SlideBoxContent.js +15 -0
  61. package/v2Containers/CreativesContainer/SlideBoxFooter.js +6 -2
  62. package/v2Containers/CreativesContainer/index.js +29 -3
  63. package/v2Containers/CreativesContainer/tests/SlideBoxFooter.test.js +29 -0
  64. package/v2Containers/CreativesContainer/tests/__snapshots__/index.test.js.snap +18 -5
  65. package/v2Containers/CreativesContainer/tests/index.test.js +12 -0
  66. package/v2Containers/MobilePush/Create/index.js +18 -1
  67. package/v2Containers/MobilePush/Create/test/contentValidity.test.js +96 -0
  68. package/v2Containers/MobilePush/Edit/index.js +18 -1
  69. package/v2Containers/MobilePush/Edit/test/contentValidity.test.js +116 -0
  70. package/v2Containers/MobilePush/commonMethods.js +49 -1
  71. package/v2Containers/MobilePushNew/index.js +29 -4
  72. package/v2Containers/MobilePushNew/tests/index.test.js +119 -0
  73. package/v2Containers/MobilePushNew/tests/utils.test.js +82 -0
  74. package/v2Containers/MobilePushNew/utils.js +34 -1
  75. package/v2Containers/MobilepushWrapper/index.js +3 -1
  76. package/v2Containers/Rcs/index.js +34 -0
  77. package/v2Containers/Rcs/index.scss +15 -0
  78. package/v2Containers/Rcs/tests/__snapshots__/index.test.js.snap +115 -19
  79. package/v2Containers/Rcs/tests/index.test.js +67 -0
  80. package/v2Containers/Sms/Create/index.js +3 -1
  81. package/v2Containers/Sms/Edit/index.js +25 -0
  82. package/v2Containers/Sms/Edit/tests/index.test.js +85 -0
  83. package/v2Containers/SmsTrai/Edit/tests/__snapshots__/index.test.js.snap +27 -3
  84. package/v2Containers/Viber/index.js +24 -1
  85. package/v2Containers/Viber/tests/index.test.js +103 -0
  86. package/v2Containers/WebPush/Create/index.js +19 -0
  87. package/v2Containers/WebPush/Create/preview/WebPushPreview.js +17 -12
  88. package/v2Containers/WebPush/Create/preview/tests/WebPushPreview.test.js +5 -0
  89. package/v2Containers/WebPush/Create/tests/contentValidity.test.js +294 -0
  90. package/v2Containers/Whatsapp/index.js +25 -4
  91. package/v2Containers/Whatsapp/tests/__snapshots__/index.test.js.snap +306 -34
  92. package/v2Containers/Whatsapp/tests/index.test.js +115 -0
  93. package/v2Containers/Zalo/index.js +28 -1
  94. package/v2Containers/Zalo/tests/index.test.js +99 -0
  95. package/v2Containers/mockdata.js +25 -0
  96. package/v2Components/FormBuilder/Functional/channels/mobilepush/buildSubmitPayload.js +0 -10
  97. package/v2Components/FormBuilder/Functional/channels/mobilepush/config.js +0 -82
  98. package/v2Components/FormBuilder/Functional/channels/mobilepush/getEditorErrorDescriptor.js +0 -74
  99. package/v2Components/FormBuilder/Functional/channels/mobilepush/index.js +0 -28
  100. package/v2Components/FormBuilder/Functional/channels/mobilepush/modals.js +0 -19
  101. package/v2Components/FormBuilder/Functional/channels/mobilepush/runSubmitPipeline.js +0 -45
  102. package/v2Components/FormBuilder/Functional/channels/mobilepush/tests/getEditorErrorDescriptor.test.js +0 -162
  103. package/v2Components/FormBuilder/Functional/channels/mobilepush/tests/modals.test.js +0 -37
  104. package/v2Components/FormBuilder/Functional/channels/mobilepush/tests/runSubmitPipeline.test.js +0 -70
  105. package/v2Components/FormBuilder/Functional/channels/mobilepush/tests/validate.test.js +0 -274
  106. package/v2Components/FormBuilder/Functional/channels/mobilepush/validate.js +0 -251
  107. package/v2Components/FormBuilder/Functional/layout/TabsContainer.js +0 -89
  108. package/v2Components/FormBuilder/Functional/renderers/mpushRenderers.js +0 -440
  109. package/v2Components/FormBuilder/Functional/tests/mpush.crossFlowParity.test.js +0 -430
  110. package/v2Components/FormBuilder/Functional/tests/mpush.ctaFlows.parity.test.js +0 -282
  111. package/v2Components/FormBuilder/Functional/tests/mpush.editContainer.parity.test.js +0 -141
  112. package/v2Components/FormBuilder/Functional/tests/mpush.engine.test.js +0 -306
  113. package/v2Components/FormBuilder/Functional/tests/mpush.shellEvents.test.js +0 -151
  114. package/v2Components/FormBuilder/Functional/tests/mpushRenderers.test.js +0 -342
  115. package/v2Components/FormBuilder/Functional/tests/tabsContainer.test.js +0 -110
  116. package/v2Components/FormBuilder/tests/mpush.characterization.test.js +0 -459
@@ -1,282 +0,0 @@
1
- /**
2
- * MPUSH CTA flows parity — the REAL (unconnected) MobilePush Create container
3
- * over the REAL gated FormBuilder, driving the CTA surface end-to-end through
4
- * the DOM the way a user does:
5
- *
6
- * fill both platforms -> "Add action link to content" (add-pri-cta)
7
- * -> deeplink option with/without params | External Link radio switch
8
- * -> press Create (isGetFormData transition, full mode)
9
- *
10
- * Pins manual-QA fix #5: switching the link-type radio Deeplink <-> External
11
- * Link REPLACES the `cta-deeplink-select` schema row with `cta-deeplink-text`.
12
- * Classic's initialiseForm PRUNES formData keys that left the schema walk
13
- * (Classic.js:1889-1897); before reconcileFormState ported that prune, the
14
- * orphaned seeded-'' `cta-deeplink-select` failed validateMobilePush's empty
15
- * rule and the container toasted "Validation error / Validation failed for
16
- * android" on a form Classic saves.
17
- *
18
- * Outcome contract per flow: NO validation stop (onValidationFail), and the
19
- * save proceeds — createTemplate fires, or the single-platform confirm modal
20
- * opens (a legitimate Classic outcome when the iOS tab stayed empty).
21
- */
22
- import React from 'react';
23
- import '@testing-library/jest-dom';
24
- import _ from 'lodash';
25
- import { Router } from 'react-router-dom';
26
- import { render, act, fireEvent } from '../../../../utils/test-utils';
27
- import history from '../../../../utils/history';
28
- import { response as mpushSchemaResponse } from '../../../../v2Containers/MobilePush/initialSchema';
29
- import { Create } from '../../../../v2Containers/MobilePush/Create';
30
-
31
- jest.mock('redux-auth-wrapper/history4/redirect', () => ({
32
- connectedRouterRedirect: jest.fn(() => (Component) => Component),
33
- }));
34
- jest.mock('../../../../services/api', () => ({
35
- ...jest.requireActual('../../../../services/api'),
36
- getUnsubscribeUrl: () => Promise.resolve({ response: { response: '' } }),
37
- }));
38
-
39
- const fullDefinition = () => _.cloneDeep(mpushSchemaResponse.metaEntities[0].definition);
40
-
41
- const intl = {
42
- formatMessage: (descriptor) => (descriptor && (descriptor.defaultMessage || descriptor.id)) || '',
43
- locale: 'en',
44
- };
45
-
46
- const DEEPLINKS = JSON.stringify([
47
- { name: 'Home Page', link: 'app://home', keys: ['user_id'] },
48
- { name: 'Offers Page', link: 'app://offers', keys: [] },
49
- ]);
50
-
51
- const selectedAccount = {
52
- id: 'acc-1',
53
- name: 'Acc',
54
- sourceTypeName: 'SOME_SDK',
55
- sourceAccountIdentifier: 'lic-1',
56
- configs: { android: '1', ios: '1', deeplink: DEEPLINKS },
57
- };
58
-
59
- const buildProps = (overrides = {}) => ({
60
- intl,
61
- params: { mode: 'text' },
62
- route: { name: 'create' },
63
- location: { pathname: '/mobilepush/create/text', query: { module: 'default' } },
64
- router: { push: jest.fn() },
65
- isFullMode: true,
66
- isGetFormData: false,
67
- isLoadingMetaEntities: false,
68
- metaEntities: {},
69
- Create: { createTemplateInProgress: false },
70
- Edit: {},
71
- Templates: { selectedWeChatAccount: selectedAccount },
72
- // Any missing action becomes a jest.fn on demand (the container calls several).
73
- actions: new Proxy({}, { get: (target, name) => {
74
- if (!target[name]) target[name] = jest.fn(); // eslint-disable-line no-param-reassign
75
- return target[name];
76
- } }),
77
- globalActions: {
78
- fetchSchemaForEntity: jest.fn(),
79
- addMessageToQueue: jest.fn(),
80
- setInjectedTags: jest.fn(),
81
- },
82
- showTemplateName: jest.fn(),
83
- getFormLibraryData: jest.fn(),
84
- onValidationFail: jest.fn(),
85
- showLiquidErrorInFooter: jest.fn(),
86
- onPersonalizationTokensChange: jest.fn(),
87
- injectedTags: {},
88
- defaultData: { 'template-name': 'cta_test_1' },
89
- ...overrides,
90
- });
91
-
92
- const setNativeValue = (el, value) => {
93
- const proto = Object.getPrototypeOf(el);
94
- const setter = Object.getOwnPropertyDescriptor(proto, 'value').set;
95
- setter.call(el, value);
96
- };
97
-
98
- const typeInto = (id, value) => {
99
- const el = document.getElementById(id);
100
- if (!el) throw new Error(`no #${id} in DOM`);
101
- setNativeValue(el, value);
102
- fireEvent.change(el, { target: { value } });
103
- fireEvent.blur(el);
104
- };
105
-
106
- const runCreateFlow = ({
107
- optionLabel = 'Offers Page', fillCustomValue = false, useCopy = false, iosCta = false,
108
- externalLink = false,
109
- } = {}) => {
110
- const props = buildProps();
111
- let containerRef = null;
112
- const ui = (p) => (
113
- <Router history={history}>
114
- <Create ref={(r) => { if (r) containerRef = r; }} {...p} />
115
- </Router>
116
- );
117
- const utils = render(ui(props));
118
- const meta = { layouts: [{ definition: fullDefinition() }], tags: { standard: [] } };
119
- act(() => {
120
- utils.rerender(ui({ ...props, metaEntities: meta }));
121
- });
122
-
123
- // 1. fill mandatory fields (template-name comes via defaultData in full mode).
124
- act(() => { typeInto('message-title', 'Hello title'); });
125
- act(() => { typeInto('message-editor', 'Hello body'); });
126
- const tabHeaders = () => Array.from(document.querySelectorAll('.ant-tabs-tab'));
127
- if (useCopy) {
128
- // the user's habitual step: switch to iOS, "copy content from Android"
129
- act(() => { fireEvent.click(tabHeaders()[1]); });
130
- const copyLink = document.getElementById('copy-android-content2')
131
- || document.getElementById('copy-android-content');
132
- if (!copyLink) throw new Error('no copy-android-content link found');
133
- act(() => { fireEvent.click(copyLink); });
134
- act(() => { fireEvent.click(tabHeaders()[0]); });
135
- } else {
136
- act(() => { fireEvent.click(tabHeaders()[1]); }); // switch to iOS
137
- act(() => { typeInto('message-title2', 'Hello ios title'); });
138
- act(() => { typeInto('message-editor2', 'Hello ios body'); });
139
- act(() => { fireEvent.click(tabHeaders()[0]); }); // back to Android
140
- }
141
-
142
- if (iosCta) {
143
- // run the whole CTA flow on the iOS pane instead (suffixed ids)
144
- act(() => { fireEvent.click(tabHeaders()[1]); });
145
- }
146
- const ctaCheckboxId = iosCta ? 'add-pri-cta-ios' : 'add-pri-cta';
147
- const selectId = iosCta ? 'cta-deeplink2-select' : 'cta-deeplink-select';
148
- const customValueId = iosCta
149
- ? 'custom-value-user_id-cta-deeplink2-select'
150
- : 'custom-value-user_id-cta-deeplink-select';
151
-
152
- // 2. click "Add action link to content" — the checkbox carries no DOM id in
153
- // either flow, so find it by label text inside the right platform pane.
154
- const pane = document.querySelectorAll('.ant-tabs-tabpane')[iosCta ? 1 : 0] || document;
155
- const label = Array.from(pane.querySelectorAll('label'))
156
- .find((l) => /action link/i.test(l.textContent || ''));
157
- const cb = (label && label.querySelector('input[type="checkbox"]'))
158
- || pane.querySelector('input[type="checkbox"]');
159
- if (!cb) throw new Error(`no ${ctaCheckboxId} checkbox found`);
160
- act(() => { fireEvent.click(cb); });
161
-
162
- // the deeplink dropdown must have been spliced in
163
- let selectHost = document.getElementById(selectId);
164
- const info = { selectRendered: Boolean(selectHost) };
165
-
166
- if (externalLink) {
167
- // switch the link-type radio to "External Link": the dropdown row must be
168
- // REPLACED by a text input (container onLinkTypeChange splice)
169
- const radios = Array.from(pane.querySelectorAll('input[type="radio"]'));
170
- const external = radios.find((rd) => rd.value === 'External Link');
171
- if (!external) throw new Error(`no External Link radio (found ${radios.map((rd) => rd.value)})`);
172
- act(() => { fireEvent.click(external); });
173
- selectHost = document.getElementById(selectId);
174
- info.selectStillRendered = Boolean(selectHost);
175
- const textId = iosCta ? 'cta-deeplink2-text' : 'cta-deeplink-text';
176
- info.externalInputRendered = Boolean(document.getElementById(textId));
177
- if (info.externalInputRendered) {
178
- act(() => { typeInto(textId, 'https://example.com/offer'); });
179
- }
180
- }
181
-
182
- // 3. open the CapSelect and choose the requested option
183
- if (selectHost && !externalLink) {
184
- const trigger = selectHost.querySelector('.ant-select-selector')
185
- || selectHost.closest('.ant-select')?.querySelector('.ant-select-selector')
186
- || selectHost;
187
- act(() => { fireEvent.mouseDown(trigger); });
188
- const options = Array.from(document.querySelectorAll('.ant-select-item-option'));
189
- const wanted = options.find((o) => o.textContent.includes(optionLabel));
190
- if (wanted) act(() => { fireEvent.click(wanted); });
191
- info.pickedOption = Boolean(wanted);
192
- }
193
-
194
- // For a deeplink with params, the containers splice in a custom-value input.
195
- const customInput = document.getElementById(customValueId);
196
- info.customInputRendered = Boolean(customInput);
197
- if (customInput && fillCustomValue) {
198
- act(() => { typeInto(customValueId, '42'); });
199
- }
200
-
201
- // 4. press Create: isGetFormData transition in full mode -> startValidation
202
- act(() => {
203
- utils.rerender(ui({ ...props, metaEntities: meta, isGetFormData: true }));
204
- });
205
-
206
- const result = {
207
- ...info,
208
- createCalled: props.actions.createTemplate.mock.calls.length,
209
- validationFailCalled: props.onValidationFail.mock.calls.length,
210
- modalShown: containerRef ? containerRef.state.showModal : null,
211
- errorTab0: containerRef
212
- ? Object.entries(containerRef.state.errorData?.[0] || {}).filter(([, v]) => v === true).map(([k]) => k)
213
- : null,
214
- };
215
- utils.unmount();
216
- return result;
217
- };
218
-
219
- // Success = the save flow proceeds: either createTemplate fired, or the
220
- // single-platform confirm modal opened. A validation STOP (onValidationFail)
221
- // is the bug class this suite pins.
222
- const expectSaveProceeds = (r) => {
223
- expect(r.validationFailCalled).toBe(0);
224
- expect(r.createCalled + (r.modalShown ? 1 : 0)).toBeGreaterThanOrEqual(1);
225
- };
226
-
227
- const expectDeeplinkPicked = (r) => {
228
- expect(r.selectRendered).toBe(true);
229
- expect(r.pickedOption).toBe(true);
230
- };
231
-
232
- describe.each([
233
- ['Classic', 'false'],
234
- ['Functional', 'true'],
235
- ])('CTA flows parity — %s', (flow, flagValue) => {
236
- beforeEach(() => {
237
- window.localStorage.setItem('ENABLE_NEW_FORMBUILDER_MPUSH', flagValue);
238
- });
239
- afterEach(() => {
240
- window.localStorage.removeItem('ENABLE_NEW_FORMBUILDER_MPUSH');
241
- });
242
-
243
- it('deeplink option WITHOUT params: no extra input, save proceeds', () => {
244
- const r = runCreateFlow({ optionLabel: 'Offers Page' });
245
- expectDeeplinkPicked(r);
246
- expect(r.customInputRendered).toBe(false);
247
- expectSaveProceeds(r);
248
- });
249
-
250
- it('deeplink option WITH params: custom-value input renders, save proceeds', () => {
251
- const r = runCreateFlow({ optionLabel: 'Home Page', fillCustomValue: true });
252
- expectDeeplinkPicked(r);
253
- expect(r.customInputRendered).toBe(true);
254
- expectSaveProceeds(r);
255
- });
256
-
257
- it('copy-content flow + CTA: save proceeds', () => {
258
- const r = runCreateFlow({ optionLabel: 'Offers Page', useCopy: true });
259
- expectDeeplinkPicked(r);
260
- expectSaveProceeds(r);
261
- });
262
-
263
- it('External Link radio: dropdown replaced by text input, orphaned select key does not fail validation', () => {
264
- const r = runCreateFlow({ externalLink: true });
265
- expect(r.selectRendered).toBe(true);
266
- expect(r.selectStillRendered).toBe(false);
267
- expect(r.externalInputRendered).toBe(true);
268
- expectSaveProceeds(r);
269
- });
270
-
271
- // iOS-pane CTA runs only on the Functional flow: Classic's tab switching scans
272
- // the real DOM (Classic.js:2526-2551), which a jsdom tab-header click does not
273
- // drive, so its container-side splice targets the wrong pane in this harness
274
- // (artifact, not a product behavior).
275
- if (flow === 'Functional') {
276
- it('iOS-pane CTA (suffixed ids): save proceeds', () => {
277
- const r = runCreateFlow({ optionLabel: 'Offers Page', iosCta: true });
278
- expectDeeplinkPicked(r);
279
- expectSaveProceeds(r);
280
- });
281
- }
282
- });
@@ -1,141 +0,0 @@
1
- /**
2
- * MPUSH edit parity — the REAL (unconnected) MobilePush Edit container driving
3
- * the REAL gated FormBuilder, with prop transitions exactly like production:
4
- * mount (account selected, no metaEntities yet)
5
- * -> metaEntities arrive (fullSchema + schema set, Edit CWRP 227-249)
6
- * -> templateDetails arrive (handleEditSchemaOnPropsChange -> setEditState
7
- * -> getSchemaForFormData -> injectEvents -> formData/tabCount setStates)
8
- *
9
- * This pins the bug the synthetic shell-level harnesses could NOT see: the
10
- * container's internal setState cascade commits the shell BEFORE a prior
11
- * dispatch has rendered, so a shell-side reconcile computed from stateRef was
12
- * STALE and clobbered the just-hydrated template (fields rendered but empty).
13
- * Fixed by running reconciliation inside the reducer (reconcileSchema action)
14
- * and emitting background validity from the settled state.
15
- */
16
- import React from 'react';
17
- import '@testing-library/jest-dom';
18
- import _ from 'lodash';
19
- import { Router } from 'react-router-dom';
20
- import { render, act } from '../../../../utils/test-utils';
21
- import history from '../../../../utils/history';
22
- import { response as mpushSchemaResponse } from '../../../../v2Containers/MobilePush/initialSchema';
23
- import { Edit } from '../../../../v2Containers/MobilePush/Edit';
24
-
25
- jest.mock('redux-auth-wrapper/history4/redirect', () => ({
26
- connectedRouterRedirect: jest.fn(() => (Component) => Component),
27
- }));
28
- jest.mock('../../../../services/api', () => ({
29
- ...jest.requireActual('../../../../services/api'),
30
- getUnsubscribeUrl: () => Promise.resolve({ response: { response: '' } }),
31
- }));
32
-
33
- const fullDefinition = () => _.cloneDeep(mpushSchemaResponse.metaEntities[0].definition);
34
-
35
- const intl = {
36
- formatMessage: (descriptor) => (descriptor && (descriptor.defaultMessage || descriptor.id)) || '',
37
- locale: 'en',
38
- };
39
-
40
- const templateDetails = () => ({
41
- name: 'test_push_12',
42
- mode: 'text',
43
- definition: { mode: 'text' },
44
- versions: {
45
- base: {
46
- ANDROID: { title: 'Saved A title', message: 'Saved A body' },
47
- IOS: { title: 'Saved I title', message: 'Saved I body' },
48
- },
49
- },
50
- });
51
-
52
- const selectedAccount = {
53
- id: 'acc-1',
54
- name: 'Acc',
55
- configs: { android: '1', ios: '1', deeplink: '[]' },
56
- };
57
-
58
- const buildProps = (overrides = {}) => ({
59
- intl,
60
- params: { id: '123' },
61
- location: { pathname: '/mobilepush/edit/123', query: { module: 'default' } },
62
- router: { push: jest.fn() },
63
- isFullMode: true,
64
- isLoadingMetaEntities: false,
65
- metaEntities: {},
66
- templateDetails: null,
67
- Edit: { weCrmAccounts: [] },
68
- Create: {},
69
- Templates: { selectedWeChatAccount: selectedAccount },
70
- // Any missing action becomes a jest.fn on demand (the container calls several).
71
- actions: new Proxy({}, { get: (target, name) => {
72
- if (!target[name]) target[name] = jest.fn(); // eslint-disable-line no-param-reassign
73
- return target[name];
74
- } }),
75
- globalActions: {
76
- fetchSchemaForEntity: jest.fn(),
77
- addMessageToQueue: jest.fn(),
78
- setInjectedTags: jest.fn(),
79
- },
80
- showTemplateName: jest.fn(),
81
- getFormLibraryData: jest.fn(),
82
- onValidationFail: jest.fn(),
83
- showLiquidErrorInFooter: jest.fn(),
84
- onPersonalizationTokensChange: jest.fn(),
85
- injectedTags: {},
86
- ...overrides,
87
- });
88
-
89
- const read = () => {
90
- const editor = document.getElementById('message-editor');
91
- const title = document.getElementById('message-title');
92
- return {
93
- titleValue: title ? title.value : '(no #message-title in DOM)',
94
- editorValue: editor ? editor.value : '(no #message-editor in DOM)',
95
- };
96
- };
97
-
98
- const runEditContainer = () => {
99
- const props = buildProps();
100
- const ui = (p) => (
101
- <Router history={history}>
102
- <Edit {...p} />
103
- </Router>
104
- );
105
- const utils = render(ui(props));
106
- // metaEntities arrive (bundled schema)
107
- act(() => {
108
- utils.rerender(ui({ ...props, metaEntities: { layouts: [{ definition: fullDefinition() }], tags: { standard: [] } } }));
109
- });
110
- // template details arrive
111
- act(() => {
112
- utils.rerender(ui({
113
- ...props,
114
- metaEntities: { layouts: [{ definition: fullDefinition() }], tags: { standard: [] } },
115
- templateDetails: templateDetails(),
116
- }));
117
- });
118
- const result = read();
119
- utils.unmount();
120
- return result;
121
- };
122
-
123
- describe('SCRATCH v3: REAL Edit container over the gated FormBuilder', () => {
124
- afterEach(() => {
125
- window.localStorage.removeItem('ENABLE_NEW_FORMBUILDER_MPUSH');
126
- });
127
-
128
- it('Functional path (flag on) populates the fields', () => {
129
- window.localStorage.setItem('ENABLE_NEW_FORMBUILDER_MPUSH', 'true');
130
- const r = runEditContainer();
131
- expect(r.titleValue).toBe('Saved A title');
132
- expect(r.editorValue).toBe('Saved A body');
133
- });
134
-
135
- it('Classic path (flag off) populates the fields', () => {
136
- window.localStorage.setItem('ENABLE_NEW_FORMBUILDER_MPUSH', 'false');
137
- const r = runEditContainer();
138
- expect(r.titleValue).toBe('Saved A title');
139
- expect(r.editorValue).toBe('Saved A body');
140
- });
141
- });