@capillarytech/creatives-library 8.0.318 → 8.0.320
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/constants/unified.js +15 -0
- package/package.json +1 -1
- package/services/api.js +6 -0
- package/services/tests/api.test.js +7 -0
- package/utils/common.js +6 -1
- package/utils/templateVarUtils.js +172 -0
- package/utils/tests/templateVarUtils.test.js +160 -0
- package/v2Components/CapTagList/index.js +10 -0
- package/v2Components/CommonTestAndPreview/CustomValuesEditor.js +70 -49
- package/v2Components/CommonTestAndPreview/DeliverySettings/DeliverySettings.scss +8 -2
- package/v2Components/CommonTestAndPreview/DeliverySettings/ModifyDeliverySettings.js +207 -21
- package/v2Components/CommonTestAndPreview/DeliverySettings/constants.js +16 -0
- package/v2Components/CommonTestAndPreview/DeliverySettings/index.js +85 -10
- package/v2Components/CommonTestAndPreview/DeliverySettings/messages.js +30 -0
- package/v2Components/CommonTestAndPreview/DeliverySettings/utils/parseSenderDetailsResponse.js +79 -11
- package/v2Components/CommonTestAndPreview/SendTestMessage.js +11 -5
- package/v2Components/CommonTestAndPreview/UnifiedPreview/_unifiedPreview.scss +20 -1
- package/v2Components/CommonTestAndPreview/UnifiedPreview/index.js +133 -4
- package/v2Components/CommonTestAndPreview/_commonTestAndPreview.scss +12 -0
- package/v2Components/CommonTestAndPreview/constants.js +38 -0
- package/v2Components/CommonTestAndPreview/index.js +693 -155
- package/v2Components/CommonTestAndPreview/messages.js +41 -3
- package/v2Components/CommonTestAndPreview/previewApiUtils.js +59 -0
- package/v2Components/CommonTestAndPreview/sagas.js +15 -6
- package/v2Components/CommonTestAndPreview/tests/CustomValuesEditor.test.js +352 -0
- package/v2Components/CommonTestAndPreview/tests/DeliverySettings/ModifyDeliverySettings.test.js +269 -1
- package/v2Components/CommonTestAndPreview/tests/DeliverySettings/index.test.js +118 -5
- package/v2Components/CommonTestAndPreview/tests/DeliverySettings/utils/parseSenderDetailsResponse.test.js +341 -0
- package/v2Components/CommonTestAndPreview/tests/SendTestMessage.test.js +25 -4
- package/v2Components/CommonTestAndPreview/tests/UnifiedPreview/index.test.js +199 -1
- package/v2Components/CommonTestAndPreview/tests/index.test.js +132 -4
- package/v2Components/CommonTestAndPreview/tests/previewApiUtils.test.js +67 -0
- package/v2Components/CommonTestAndPreview/tests/sagas.test.js +2 -2
- package/v2Components/FormBuilder/index.js +7 -1
- package/v2Components/SmsFallback/SmsFallbackLocalSelector.js +87 -0
- package/v2Components/SmsFallback/constants.js +73 -0
- package/v2Components/SmsFallback/index.js +956 -0
- package/v2Components/SmsFallback/index.scss +265 -0
- package/v2Components/SmsFallback/messages.js +78 -0
- package/v2Components/SmsFallback/smsFallbackUtils.js +107 -0
- package/v2Components/SmsFallback/tests/SmsFallbackLocalSelector.test.js +50 -0
- package/v2Components/SmsFallback/tests/rcsSmsFallback.acceptance.test.js +147 -0
- package/v2Components/SmsFallback/tests/smsFallbackHandlers.test.js +304 -0
- package/v2Components/SmsFallback/tests/smsFallbackUi.test.js +197 -0
- package/v2Components/SmsFallback/tests/smsFallbackUtils.test.js +261 -0
- package/v2Components/SmsFallback/tests/useLocalTemplateList.test.js +422 -0
- package/v2Components/SmsFallback/useLocalTemplateList.js +92 -0
- package/v2Components/TestAndPreviewSlidebox/index.js +8 -1
- package/v2Components/TestAndPreviewSlidebox/sagas.js +11 -4
- package/v2Components/TestAndPreviewSlidebox/tests/saga.test.js +3 -1
- package/v2Components/VarSegmentMessageEditor/constants.js +2 -0
- package/v2Components/VarSegmentMessageEditor/index.js +125 -0
- package/v2Components/VarSegmentMessageEditor/index.scss +46 -0
- package/v2Containers/CommunicationFlow/CommunicationFlow.js +291 -0
- package/v2Containers/CommunicationFlow/CommunicationFlow.scss +25 -0
- package/v2Containers/CommunicationFlow/Tests/CommunicationFlow.test.js +255 -0
- package/v2Containers/CommunicationFlow/constants.js +200 -0
- package/v2Containers/CommunicationFlow/index.js +102 -0
- package/v2Containers/CommunicationFlow/messages.js +346 -0
- package/v2Containers/CommunicationFlow/steps/ChannelSelectionStep/ChannelSelectionStep.js +522 -0
- package/v2Containers/CommunicationFlow/steps/ChannelSelectionStep/ChannelSelectionStep.scss +170 -0
- package/v2Containers/CommunicationFlow/steps/ChannelSelectionStep/Tests/ChannelSelectionStep.test.js +796 -0
- package/v2Containers/CommunicationFlow/steps/ChannelSelectionStep/index.js +5 -0
- package/v2Containers/CommunicationFlow/steps/CommunicationStrategyStep/CommunicationStrategyStep.js +95 -0
- package/v2Containers/CommunicationFlow/steps/CommunicationStrategyStep/Tests/CommunicationStrategyStep.test.js +133 -0
- package/v2Containers/CommunicationFlow/steps/CommunicationStrategyStep/index.js +5 -0
- package/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/DeliverySettingsSection.js +289 -0
- package/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/DeliverySettingsSection.scss +70 -0
- package/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/SenderDetails.js +319 -0
- package/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/SenderDetails.scss +69 -0
- package/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/Tests/DeliverySettingsSection.test.js +616 -0
- package/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/Tests/SenderDetails.test.js +577 -0
- package/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/Tests/deliverySettingsConfig.test.js +1111 -0
- package/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/deliverySettingsConfig.js +696 -0
- package/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/index.js +7 -0
- package/v2Containers/CommunicationFlow/steps/DynamicControlsStep/DynamicControlsStep.js +102 -0
- package/v2Containers/CommunicationFlow/steps/DynamicControlsStep/DynamicControlsStep.scss +36 -0
- package/v2Containers/CommunicationFlow/steps/DynamicControlsStep/Tests/DynamicControlsStep.test.js +91 -0
- package/v2Containers/CommunicationFlow/steps/DynamicControlsStep/index.js +5 -0
- package/v2Containers/CommunicationFlow/steps/MessageTypeStep/MessageTypeStep.js +86 -0
- package/v2Containers/CommunicationFlow/steps/MessageTypeStep/Tests/MessageTypeStep.test.js +100 -0
- package/v2Containers/CommunicationFlow/steps/MessageTypeStep/index.js +5 -0
- package/v2Containers/CommunicationFlow/utils/getEnabledSteps.js +30 -0
- package/v2Containers/CreativesContainer/CreativesSlideBoxWrapper.js +43 -0
- package/v2Containers/CreativesContainer/SlideBoxContent.js +36 -4
- package/v2Containers/CreativesContainer/SlideBoxFooter.js +10 -1
- package/v2Containers/CreativesContainer/SlideBoxHeader.js +29 -4
- package/v2Containers/CreativesContainer/constants.js +12 -0
- package/v2Containers/CreativesContainer/embeddedSlideboxUtils.js +67 -0
- package/v2Containers/CreativesContainer/index.js +289 -99
- package/v2Containers/CreativesContainer/index.scss +51 -1
- package/v2Containers/CreativesContainer/tests/SlideBoxContent.localTemplates.test.js +90 -0
- package/v2Containers/CreativesContainer/tests/SlideBoxFooter.test.js +104 -0
- package/v2Containers/CreativesContainer/tests/SlideBoxHeader.test.js +110 -0
- package/v2Containers/CreativesContainer/tests/__snapshots__/SlideBoxContent.test.js.snap +8 -0
- package/v2Containers/CreativesContainer/tests/__snapshots__/SlideBoxHeader.test.js.snap +363 -0
- package/v2Containers/CreativesContainer/tests/__snapshots__/index.test.js.snap +20 -10
- package/v2Containers/CreativesContainer/tests/embeddedSlideboxUtils.test.js +258 -0
- package/v2Containers/CreativesContainer/tests/index.test.js +71 -9
- package/v2Containers/CreativesContainer/tests/useLocalTemplatesProp.test.js +125 -0
- package/v2Containers/Rcs/constants.js +32 -1
- package/v2Containers/Rcs/index.js +950 -873
- package/v2Containers/Rcs/index.scss +85 -6
- package/v2Containers/Rcs/messages.js +10 -1
- package/v2Containers/Rcs/rcsLibraryHydrationUtils.js +205 -0
- package/v2Containers/Rcs/tests/__snapshots__/index.test.js.snap +40834 -1963
- package/v2Containers/Rcs/tests/__snapshots__/utils.test.js.snap +0 -5
- package/v2Containers/Rcs/tests/index.test.js +41 -38
- package/v2Containers/Rcs/tests/mockData.js +38 -0
- package/v2Containers/Rcs/tests/rcsLibraryHydrationUtils.test.js +251 -0
- package/v2Containers/Rcs/tests/utils.test.js +379 -1
- package/v2Containers/Rcs/utils.js +358 -10
- package/v2Containers/Sms/Create/index.js +81 -36
- package/v2Containers/Sms/smsFormDataHelpers.js +67 -0
- package/v2Containers/Sms/tests/smsFormDataHelpers.test.js +253 -0
- package/v2Containers/SmsTrai/Create/index.js +9 -4
- package/v2Containers/SmsTrai/Edit/constants.js +2 -0
- package/v2Containers/SmsTrai/Edit/index.js +609 -128
- package/v2Containers/SmsTrai/Edit/index.scss +121 -0
- package/v2Containers/SmsTrai/Edit/messages.js +9 -4
- package/v2Containers/SmsTrai/Edit/tests/__snapshots__/index.test.js.snap +4327 -2374
- package/v2Containers/SmsWrapper/index.js +37 -8
- package/v2Containers/TagList/index.js +6 -0
- package/v2Containers/Templates/TemplatesActionBar.js +101 -0
- package/v2Containers/Templates/_templates.scss +61 -2
- package/v2Containers/Templates/actions.js +11 -0
- package/v2Containers/Templates/constants.js +2 -0
- package/v2Containers/Templates/index.js +90 -40
- package/v2Containers/Templates/sagas.js +57 -12
- package/v2Containers/Templates/tests/TemplatesActionBar.test.js +120 -0
- package/v2Containers/Templates/tests/__snapshots__/index.test.js.snap +1043 -1079
- package/v2Containers/Templates/tests/sagas.test.js +193 -12
- package/v2Containers/Templates/tests/smsTemplatesListApi.test.js +180 -0
- package/v2Containers/Templates/utils/smsTemplatesListApi.js +79 -0
- package/v2Containers/TemplatesV2/TemplatesV2.style.js +72 -1
- package/v2Containers/TemplatesV2/index.js +86 -23
- package/v2Containers/TemplatesV2/tests/TemplatesV2.localTemplates.test.js +131 -0
- package/v2Containers/Whatsapp/index.js +3 -20
- package/v2Containers/Whatsapp/tests/__snapshots__/index.test.js.snap +578 -34
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
|
|
3
|
+
jest.mock('../../CreativesContainer', () => function MockCreativesContainer({
|
|
4
|
+
getCreativesData,
|
|
5
|
+
handleCloseCreatives,
|
|
6
|
+
creativesMode,
|
|
7
|
+
channel,
|
|
8
|
+
}) {
|
|
9
|
+
return (
|
|
10
|
+
<div data-testid="creatives-mock" data-creatives-mode={creativesMode} data-creatives-channel={channel}>
|
|
11
|
+
<button
|
|
12
|
+
type="button"
|
|
13
|
+
data-testid="creatives-save"
|
|
14
|
+
onClick={() => getCreativesData({ smsBody: 'Saved' })}
|
|
15
|
+
>
|
|
16
|
+
Save creative
|
|
17
|
+
</button>
|
|
18
|
+
<button type="button" data-testid="creatives-close" onClick={handleCloseCreatives}>
|
|
19
|
+
Close creative
|
|
20
|
+
</button>
|
|
21
|
+
</div>
|
|
22
|
+
);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
import { Provider } from 'react-redux';
|
|
26
|
+
import { configureStore } from '@capillarytech/vulcan-react-sdk/utils';
|
|
27
|
+
import {
|
|
28
|
+
render as rtlRender, screen, waitFor, within,
|
|
29
|
+
} from '@testing-library/react';
|
|
30
|
+
import userEvent from '@testing-library/user-event';
|
|
31
|
+
import '@testing-library/jest-dom';
|
|
32
|
+
import { IntlProvider } from 'react-intl';
|
|
33
|
+
import history from '../../../utils/history';
|
|
34
|
+
import { initialReducer } from '../../../initialReducer';
|
|
35
|
+
import CommunicationFlow from '../CommunicationFlow';
|
|
36
|
+
import { getEnabledSteps } from '../utils/getEnabledSteps';
|
|
37
|
+
import {
|
|
38
|
+
CHANNELS,
|
|
39
|
+
CHANNEL_PRIORITY,
|
|
40
|
+
SINGLE_TEMPLATE,
|
|
41
|
+
STEPS,
|
|
42
|
+
} from '../constants';
|
|
43
|
+
|
|
44
|
+
let store;
|
|
45
|
+
|
|
46
|
+
beforeAll(() => {
|
|
47
|
+
store = configureStore({}, initialReducer, history);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
/** Shared option lists for feature config */
|
|
51
|
+
const MESSAGE_OPTIONS = [
|
|
52
|
+
{ value: 'promotional', label: 'Promotional' },
|
|
53
|
+
{ value: 'transactional', label: 'Transactional' },
|
|
54
|
+
];
|
|
55
|
+
|
|
56
|
+
const STRATEGY_OPTIONS = [
|
|
57
|
+
{ value: SINGLE_TEMPLATE, label: 'Single template', isMultiChannel: false },
|
|
58
|
+
{ value: CHANNEL_PRIORITY, label: 'Channel priority', isMultiChannel: true },
|
|
59
|
+
];
|
|
60
|
+
|
|
61
|
+
const DYNAMIC_CONTROLS = [
|
|
62
|
+
{ key: 'sendToControl', label: 'Send to control', description: 'Help text' },
|
|
63
|
+
];
|
|
64
|
+
|
|
65
|
+
/** Base config for all CommunicationFlow tests */
|
|
66
|
+
const baseConfig = {
|
|
67
|
+
consumer: 'campaigns',
|
|
68
|
+
mode: 'create',
|
|
69
|
+
features: {},
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
const CAP_DATA = {};
|
|
73
|
+
|
|
74
|
+
/** Reusable feature presets merged into config.features */
|
|
75
|
+
const FEATURES = {
|
|
76
|
+
messageType: {
|
|
77
|
+
messageTypeData: {
|
|
78
|
+
required: true,
|
|
79
|
+
options: MESSAGE_OPTIONS,
|
|
80
|
+
defaultOption: { value: 'promotional', label: 'Promotional' },
|
|
81
|
+
},
|
|
82
|
+
},
|
|
83
|
+
strategyContent: {
|
|
84
|
+
communicationStrategyData: { required: true, options: STRATEGY_OPTIONS, disabled: false },
|
|
85
|
+
contentTemplateData: { required: true, channels: CHANNELS },
|
|
86
|
+
},
|
|
87
|
+
strategyContentDynamic: {
|
|
88
|
+
communicationStrategyData: { required: true, options: STRATEGY_OPTIONS, disabled: false },
|
|
89
|
+
contentTemplateData: { required: true, channels: CHANNELS },
|
|
90
|
+
dynamicControlsData: { required: true, controls: DYNAMIC_CONTROLS },
|
|
91
|
+
},
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
function renderFlow(ui) {
|
|
95
|
+
return rtlRender(
|
|
96
|
+
<Provider store={store}>
|
|
97
|
+
<IntlProvider locale="en" messages={{}} defaultLocale="en">
|
|
98
|
+
{ui}
|
|
99
|
+
</IntlProvider>
|
|
100
|
+
</Provider>,
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Renders connected CommunicationFlow with shared defaults.
|
|
106
|
+
* @param {object} [options]
|
|
107
|
+
* @param {object} [options.features] — config.features (use FEATURES.* or custom)
|
|
108
|
+
* @param {object|null} [options.initialData]
|
|
109
|
+
* @param {object} [options.capData]
|
|
110
|
+
* @param {jest.Mock} [options.onSave] — defaults to jest.fn()
|
|
111
|
+
* @param {jest.Mock} [options.onCancel] — defaults to jest.fn()
|
|
112
|
+
* @param {jest.Mock} [options.onChange]
|
|
113
|
+
*/
|
|
114
|
+
function renderWithFlow({
|
|
115
|
+
features = {},
|
|
116
|
+
initialData = null,
|
|
117
|
+
capData = CAP_DATA,
|
|
118
|
+
onSave,
|
|
119
|
+
onCancel,
|
|
120
|
+
onChange,
|
|
121
|
+
...rest
|
|
122
|
+
} = {}) {
|
|
123
|
+
return renderFlow(
|
|
124
|
+
<CommunicationFlow
|
|
125
|
+
config={{ ...baseConfig, features }}
|
|
126
|
+
initialData={initialData}
|
|
127
|
+
onSave={onSave ?? jest.fn()}
|
|
128
|
+
onCancel={onCancel ?? jest.fn()}
|
|
129
|
+
onChange={onChange}
|
|
130
|
+
capData={capData}
|
|
131
|
+
{...rest}
|
|
132
|
+
/>,
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
async function selectCommunicationStrategy(label) {
|
|
137
|
+
const combo = screen.getByRole('combobox');
|
|
138
|
+
await userEvent.click(combo);
|
|
139
|
+
await waitFor(() => {
|
|
140
|
+
expect(screen.getByRole('listbox')).toBeInTheDocument();
|
|
141
|
+
});
|
|
142
|
+
await userEvent.click(within(screen.getByRole('listbox')).getByText(label));
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
describe('getEnabledSteps', () => {
|
|
146
|
+
it('includes delivery settings when deliverySettingsData.required is true', () => {
|
|
147
|
+
const steps = getEnabledSteps({
|
|
148
|
+
...baseConfig,
|
|
149
|
+
features: {
|
|
150
|
+
deliverySettingsData: { required: true },
|
|
151
|
+
},
|
|
152
|
+
});
|
|
153
|
+
expect(steps).toContain(STEPS.DELIVERY_SETTINGS);
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
it('omits delivery settings when deliverySettingsData.required is absent or false', () => {
|
|
157
|
+
expect(getEnabledSteps({ ...baseConfig, features: {} })).not.toContain(
|
|
158
|
+
STEPS.DELIVERY_SETTINGS,
|
|
159
|
+
);
|
|
160
|
+
expect(
|
|
161
|
+
getEnabledSteps({
|
|
162
|
+
...baseConfig,
|
|
163
|
+
features: { deliverySettingsData: { required: false } },
|
|
164
|
+
}),
|
|
165
|
+
).not.toContain(STEPS.DELIVERY_SETTINGS);
|
|
166
|
+
});
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
describe('CommunicationFlow', () => {
|
|
170
|
+
it('renders only steps whose features are required', () => {
|
|
171
|
+
renderWithFlow({ features: FEATURES.messageType });
|
|
172
|
+
expect(screen.getByText(/message type/i)).toBeInTheDocument();
|
|
173
|
+
expect(screen.queryByText(/communication strategy/i)).not.toBeInTheDocument();
|
|
174
|
+
expect(screen.queryByText(/content template/i)).not.toBeInTheDocument();
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
it('hides channel selection and dynamic controls until a communication strategy is set', async () => {
|
|
178
|
+
renderWithFlow({ features: FEATURES.strategyContentDynamic });
|
|
179
|
+
|
|
180
|
+
expect(screen.queryByText(/content template/i)).not.toBeInTheDocument();
|
|
181
|
+
expect(screen.queryByText(/other controls/i)).not.toBeInTheDocument();
|
|
182
|
+
|
|
183
|
+
await selectCommunicationStrategy('Single template');
|
|
184
|
+
|
|
185
|
+
await waitFor(() => {
|
|
186
|
+
expect(screen.getAllByText(/content template/i).length).toBeGreaterThan(0);
|
|
187
|
+
});
|
|
188
|
+
expect(screen.getByText(/other controls/i)).toBeInTheDocument();
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
it('calls onChange with aggregated data when step data updates', async () => {
|
|
192
|
+
const onChange = jest.fn();
|
|
193
|
+
renderWithFlow({ features: FEATURES.messageType, onChange });
|
|
194
|
+
|
|
195
|
+
onChange.mockClear();
|
|
196
|
+
await userEvent.click(screen.getByRole('radio', { name: /transactional/i }));
|
|
197
|
+
|
|
198
|
+
expect(onChange).toHaveBeenCalled();
|
|
199
|
+
const payload = onChange.mock.calls[onChange.mock.calls.length - 1][0];
|
|
200
|
+
expect(payload.messageType).toBe('transactional');
|
|
201
|
+
expect(payload.communicationStrategy).toBeNull();
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
it('passes single-channel aggregate to onSave', async () => {
|
|
205
|
+
const onSave = jest.fn();
|
|
206
|
+
renderWithFlow({ features: FEATURES.strategyContent, onSave });
|
|
207
|
+
|
|
208
|
+
await selectCommunicationStrategy('Single template');
|
|
209
|
+
|
|
210
|
+
await userEvent.click(screen.getByRole('button', { name: /content template/i }));
|
|
211
|
+
await waitFor(() => expect(screen.getByRole('menu')).toBeInTheDocument());
|
|
212
|
+
await userEvent.click(within(screen.getByRole('menu')).getByText('SMS'));
|
|
213
|
+
|
|
214
|
+
await waitFor(() => expect(screen.getByTestId('creatives-mock')).toBeInTheDocument());
|
|
215
|
+
await userEvent.click(screen.getByRole('button', { name: /save creative/i }));
|
|
216
|
+
|
|
217
|
+
await waitFor(() => expect(screen.queryByTestId('creatives-mock')).not.toBeInTheDocument());
|
|
218
|
+
|
|
219
|
+
await userEvent.click(screen.getByRole('button', { name: /^save$/i }));
|
|
220
|
+
|
|
221
|
+
expect(onSave).toHaveBeenCalledTimes(1);
|
|
222
|
+
const saved = onSave.mock.calls[0][0];
|
|
223
|
+
expect(saved.communicationStrategy).toBe(SINGLE_TEMPLATE);
|
|
224
|
+
expect(saved.channel).toBe('SMS');
|
|
225
|
+
expect(saved.channels).toEqual([]);
|
|
226
|
+
expect(saved.contentItems.length).toBeGreaterThanOrEqual(1);
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
it('clears channel and keeps channels list in aggregate for multi-channel strategy', async () => {
|
|
230
|
+
const onSave = jest.fn();
|
|
231
|
+
renderWithFlow({
|
|
232
|
+
features: FEATURES.strategyContent,
|
|
233
|
+
initialData: {
|
|
234
|
+
communicationStrategy: CHANNEL_PRIORITY,
|
|
235
|
+
channels: ['SMS', 'EMAIL'],
|
|
236
|
+
},
|
|
237
|
+
onSave,
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
await userEvent.click(screen.getByRole('button', { name: /^save$/i }));
|
|
241
|
+
|
|
242
|
+
const saved = onSave.mock.calls[0][0];
|
|
243
|
+
expect(saved.communicationStrategy).toBe(CHANNEL_PRIORITY);
|
|
244
|
+
expect(saved.channel).toBeNull();
|
|
245
|
+
expect(saved.channels).toEqual(['SMS', 'EMAIL']);
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
it('initializes message type from initialData when provided', () => {
|
|
249
|
+
renderWithFlow({
|
|
250
|
+
features: FEATURES.messageType,
|
|
251
|
+
initialData: { messageType: 'transactional' },
|
|
252
|
+
});
|
|
253
|
+
expect(screen.getByRole('radio', { name: /transactional/i })).toBeChecked();
|
|
254
|
+
});
|
|
255
|
+
});
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CommunicationFlow Constants
|
|
3
|
+
*/
|
|
4
|
+
import React from 'react';
|
|
5
|
+
import { FormattedMessage } from 'react-intl';
|
|
6
|
+
import messages from './messages';
|
|
7
|
+
|
|
8
|
+
// Step identifiers
|
|
9
|
+
export const STEPS = {
|
|
10
|
+
MESSAGE_TYPE: 'messageType',
|
|
11
|
+
COMMUNICATION_STRATEGY: 'communicationStrategy',
|
|
12
|
+
CHANNEL_SELECTION: 'channelSelection',
|
|
13
|
+
INCENTIVES: 'incentives',
|
|
14
|
+
DELIVERY_SETTINGS: 'deliverySettings',
|
|
15
|
+
DYNAMIC_CONTROLS: 'dynamicControls',
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
// Message Type values
|
|
19
|
+
export const MESSAGE_TYPES_OPTIONS = [
|
|
20
|
+
{ value: 'promotional', label: <FormattedMessage {...messages.promotional} />, disabled: false },
|
|
21
|
+
{ value: 'transactional', label: <FormattedMessage {...messages.transactional} />, disabled: false },
|
|
22
|
+
];
|
|
23
|
+
|
|
24
|
+
// Communication Strategy values
|
|
25
|
+
export const COMMUNICATION_STRATEGIES_OPTIONS = [
|
|
26
|
+
{
|
|
27
|
+
value: 'SINGLE_TEMPLATE', label: <FormattedMessage {...messages.singleTemplate} />, disabled: false, isMultiChannel: false,
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
value: 'CHANNEL_PRIORITY', label: <FormattedMessage {...messages.channelPriority} />, disabled: false, isMultiChannel: true,
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
value: 'AB_TEST', label: <FormattedMessage {...messages.abTest} />, disabled: false, isMultiChannel: true,
|
|
34
|
+
},
|
|
35
|
+
];
|
|
36
|
+
|
|
37
|
+
export const CHANNEL_PRIORITY = 'CHANNEL_PRIORITY';
|
|
38
|
+
export const AB_TEST = 'AB_TEST';
|
|
39
|
+
export const SINGLE_TEMPLATE = 'SINGLE_TEMPLATE';
|
|
40
|
+
|
|
41
|
+
// Channel config - single source of truth for CreativesContainer and TemplatesV2
|
|
42
|
+
// paneKey: TemplatesV2 defaultPanes object key (for channelsToHide)
|
|
43
|
+
// channelProp: CreativesContainer channel prop (must match pane.key for tab to be active)
|
|
44
|
+
export const CHANNELS = [
|
|
45
|
+
{
|
|
46
|
+
value: 'SMS',
|
|
47
|
+
label: <FormattedMessage {...messages.sms} />,
|
|
48
|
+
iconType: 'sms',
|
|
49
|
+
isActive: true,
|
|
50
|
+
paneKey: 'sms',
|
|
51
|
+
channelProp: 'sms',
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
value: 'EMAIL',
|
|
55
|
+
label: <FormattedMessage {...messages.email} />,
|
|
56
|
+
iconType: 'email',
|
|
57
|
+
isActive: true,
|
|
58
|
+
paneKey: 'email',
|
|
59
|
+
channelProp: 'email',
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
value: 'MOBILEPUSH',
|
|
63
|
+
label: <FormattedMessage {...messages.mobilePush} />,
|
|
64
|
+
iconType: 'mpush',
|
|
65
|
+
isActive: true,
|
|
66
|
+
paneKey: 'mPush',
|
|
67
|
+
channelProp: 'mobilepush',
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
value: 'INAPP',
|
|
71
|
+
label: <FormattedMessage {...messages.inApp} />,
|
|
72
|
+
iconType: 'mpush',
|
|
73
|
+
isActive: true,
|
|
74
|
+
paneKey: 'inApp',
|
|
75
|
+
channelProp: 'inapp',
|
|
76
|
+
requiresEnableNewChannels: true,
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
value: 'WHATSAPP',
|
|
80
|
+
label: <FormattedMessage {...messages.whatsApp} />,
|
|
81
|
+
iconType: 'whatsapp',
|
|
82
|
+
isActive: true,
|
|
83
|
+
paneKey: 'whatsapp',
|
|
84
|
+
channelProp: 'whatsapp',
|
|
85
|
+
requiresEnableNewChannels: true,
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
value: 'WECHAT',
|
|
89
|
+
label: <FormattedMessage {...messages.weChat} />,
|
|
90
|
+
iconType: 'wechat',
|
|
91
|
+
isActive: false,
|
|
92
|
+
paneKey: 'weChat',
|
|
93
|
+
channelProp: 'wechat',
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
value: 'LINE',
|
|
97
|
+
label: <FormattedMessage {...messages.line} />,
|
|
98
|
+
iconType: 'line',
|
|
99
|
+
isActive: true,
|
|
100
|
+
paneKey: 'line',
|
|
101
|
+
channelProp: 'line',
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
value: 'VIBER',
|
|
105
|
+
label: <FormattedMessage {...messages.viber} />,
|
|
106
|
+
iconType: 'viber',
|
|
107
|
+
isActive: true,
|
|
108
|
+
paneKey: 'viber',
|
|
109
|
+
channelProp: 'viber',
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
value: 'FACEBOOK',
|
|
113
|
+
label: <FormattedMessage {...messages.facebook} />,
|
|
114
|
+
iconType: 'facebook',
|
|
115
|
+
isActive: true,
|
|
116
|
+
paneKey: 'facebook',
|
|
117
|
+
channelProp: 'facebook',
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
value: 'RCS',
|
|
121
|
+
label: <FormattedMessage {...messages.rcs} />,
|
|
122
|
+
iconType: 'rcs',
|
|
123
|
+
isActive: true,
|
|
124
|
+
paneKey: 'rcs',
|
|
125
|
+
channelProp: 'rcs',
|
|
126
|
+
requiresEnableNewChannels: true,
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
value: 'ZALO',
|
|
130
|
+
label: <FormattedMessage {...messages.zalo} />,
|
|
131
|
+
iconType: 'zalo',
|
|
132
|
+
isActive: true,
|
|
133
|
+
paneKey: 'zalo',
|
|
134
|
+
channelProp: 'zalo',
|
|
135
|
+
requiresEnableNewChannels: true,
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
value: 'WEBPUSH',
|
|
139
|
+
label: <FormattedMessage {...messages.webPush} />,
|
|
140
|
+
iconType: 'webpush',
|
|
141
|
+
isActive: true,
|
|
142
|
+
paneKey: 'webpush',
|
|
143
|
+
channelProp: 'webpush',
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
value: 'CALLTASK',
|
|
147
|
+
label: <FormattedMessage {...messages.callTask} />,
|
|
148
|
+
iconType: 'call',
|
|
149
|
+
isActive: true,
|
|
150
|
+
paneKey: 'callTask',
|
|
151
|
+
channelProp: 'call_task', // Must match TemplatesV2 pane.key
|
|
152
|
+
},
|
|
153
|
+
];
|
|
154
|
+
|
|
155
|
+
// Delivery settings - channels that show/hide sender details
|
|
156
|
+
export const CHANNELS_WITHOUT_DELIVERY = ['MPUSH', 'MOBILEPUSH', 'INAPP', 'WEBPUSH'];
|
|
157
|
+
export const SENDER_ID_CHANNELS = ['SMS', 'EMAIL', 'VIBER', 'ZALO', 'LINE'];
|
|
158
|
+
export const SENDER_NUMBER_CHANNELS = ['WHATSAPP', 'RCS'];
|
|
159
|
+
|
|
160
|
+
// Dynamic Controls — default config for the Other Controls section.
|
|
161
|
+
// label and description are resolved via FormattedMessage so consumers
|
|
162
|
+
// get translated strings without needing to hardcode copy in their config.
|
|
163
|
+
export const DYNAMIC_CONTROLS_CONFIG = [
|
|
164
|
+
{
|
|
165
|
+
key: 'sendToControlCustomers',
|
|
166
|
+
label: <FormattedMessage {...messages.sendToControlCustomers} />,
|
|
167
|
+
},
|
|
168
|
+
{
|
|
169
|
+
key: 'sendToBrandPocs',
|
|
170
|
+
label: <FormattedMessage {...messages.sendToBrandPocs} />,
|
|
171
|
+
},
|
|
172
|
+
{
|
|
173
|
+
key: 'useTinyUrl',
|
|
174
|
+
label: <FormattedMessage {...messages.useTinyUrl} />,
|
|
175
|
+
},
|
|
176
|
+
{
|
|
177
|
+
key: 'overrideDailyLimit',
|
|
178
|
+
label: <FormattedMessage {...messages.overrideDailyLimit} />,
|
|
179
|
+
description: <FormattedMessage {...messages.overrideDailyLimitDesc} />,
|
|
180
|
+
},
|
|
181
|
+
];
|
|
182
|
+
|
|
183
|
+
// Incentive types
|
|
184
|
+
export const INCENTIVE_TYPES = [
|
|
185
|
+
{
|
|
186
|
+
value: 'coupons', label: <FormattedMessage {...messages.coupons} />, isActive: true, disabled: false,
|
|
187
|
+
},
|
|
188
|
+
{
|
|
189
|
+
value: 'points', label: <FormattedMessage {...messages.points} />, isActive: true, disabled: false,
|
|
190
|
+
},
|
|
191
|
+
{
|
|
192
|
+
value: 'promotions', label: <FormattedMessage {...messages.promotions} />, isActive: true, disabled: false,
|
|
193
|
+
},
|
|
194
|
+
{
|
|
195
|
+
value: 'giftVouchers', label: <FormattedMessage {...messages.giftVouchers} />, isActive: false, disabled: false,
|
|
196
|
+
},
|
|
197
|
+
{
|
|
198
|
+
value: 'badges', label: <FormattedMessage {...messages.badges} />, isActive: false, disabled: false,
|
|
199
|
+
},
|
|
200
|
+
];
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CommunicationFlow - Public Entry Point
|
|
3
|
+
*
|
|
4
|
+
* Single entry for consumers. Wires config + callbacks and renders
|
|
5
|
+
* the CommunicationFlow orchestrator component.
|
|
6
|
+
*
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import React from 'react';
|
|
10
|
+
import PropTypes from 'prop-types';
|
|
11
|
+
import CommunicationFlow from './CommunicationFlow';
|
|
12
|
+
import { hasSupportEngagementModule } from '../../utils/common';
|
|
13
|
+
|
|
14
|
+
const CommunicationFlowContainer = ({
|
|
15
|
+
config,
|
|
16
|
+
initialData,
|
|
17
|
+
onSave,
|
|
18
|
+
onCancel,
|
|
19
|
+
onChange,
|
|
20
|
+
...otherProps
|
|
21
|
+
}) => {
|
|
22
|
+
if (!hasSupportEngagementModule()) {
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return (
|
|
27
|
+
<CommunicationFlow
|
|
28
|
+
config={config}
|
|
29
|
+
initialData={initialData}
|
|
30
|
+
onSave={onSave}
|
|
31
|
+
onCancel={onCancel}
|
|
32
|
+
onChange={onChange}
|
|
33
|
+
{...otherProps}
|
|
34
|
+
/>
|
|
35
|
+
);
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
CommunicationFlowContainer.propTypes = {
|
|
39
|
+
config: PropTypes.shape({
|
|
40
|
+
consumer: PropTypes.oneOf(['campaigns', 'loyalty', 'adiona']).isRequired,
|
|
41
|
+
channel: PropTypes.string, // optional if channel step is skipped
|
|
42
|
+
mode: PropTypes.oneOf(['create', 'edit', 'preview']).isRequired,
|
|
43
|
+
channelsToHide: PropTypes.arrayOf(PropTypes.string),
|
|
44
|
+
channelsToDisable: PropTypes.arrayOf(PropTypes.string),
|
|
45
|
+
features: PropTypes.shape({
|
|
46
|
+
messageTypeData: PropTypes.shape({
|
|
47
|
+
options: PropTypes.arrayOf(PropTypes.shape({
|
|
48
|
+
value: PropTypes.string.isRequired,
|
|
49
|
+
label: PropTypes.string.isRequired,
|
|
50
|
+
})),
|
|
51
|
+
defaultOption: PropTypes.shape({
|
|
52
|
+
value: PropTypes.string.isRequired,
|
|
53
|
+
label: PropTypes.string.isRequired,
|
|
54
|
+
}),
|
|
55
|
+
required: PropTypes.bool,
|
|
56
|
+
}),
|
|
57
|
+
communicationStrategyData: PropTypes.shape({
|
|
58
|
+
options: PropTypes.arrayOf(PropTypes.shape({
|
|
59
|
+
value: PropTypes.string.isRequired,
|
|
60
|
+
label: PropTypes.string.isRequired,
|
|
61
|
+
})),
|
|
62
|
+
defaultOption: PropTypes.shape({
|
|
63
|
+
value: PropTypes.string.isRequired,
|
|
64
|
+
label: PropTypes.string.isRequired,
|
|
65
|
+
}),
|
|
66
|
+
required: PropTypes.bool,
|
|
67
|
+
disabled: PropTypes.bool,
|
|
68
|
+
}),
|
|
69
|
+
contentTemplateData: PropTypes.shape({
|
|
70
|
+
required: PropTypes.bool,
|
|
71
|
+
channels: PropTypes.array,
|
|
72
|
+
channelsToHide: PropTypes.arrayOf(PropTypes.string),
|
|
73
|
+
channelsToDisable: PropTypes.arrayOf(PropTypes.string),
|
|
74
|
+
}),
|
|
75
|
+
incentivesData: PropTypes.shape({
|
|
76
|
+
required: PropTypes.bool,
|
|
77
|
+
types: PropTypes.arrayOf(PropTypes.shape({
|
|
78
|
+
value: PropTypes.string.isRequired,
|
|
79
|
+
label: PropTypes.string.isRequired,
|
|
80
|
+
isActive: PropTypes.bool,
|
|
81
|
+
})),
|
|
82
|
+
}),
|
|
83
|
+
deliverySettingsData: PropTypes.object,
|
|
84
|
+
dynamicControlsData: PropTypes.shape({
|
|
85
|
+
required: PropTypes.bool,
|
|
86
|
+
controls: PropTypes.array,
|
|
87
|
+
}),
|
|
88
|
+
}),
|
|
89
|
+
context: PropTypes.object, // ouId, campaignId, programId, etc.
|
|
90
|
+
}).isRequired,
|
|
91
|
+
initialData: PropTypes.object, // for edit/preview mode
|
|
92
|
+
onSave: PropTypes.func.isRequired, // (data) => void - called when user saves
|
|
93
|
+
onCancel: PropTypes.func.isRequired, // () => void - called when user cancels
|
|
94
|
+
onChange: PropTypes.func, // (data) => void - optional, called on data changes
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
CommunicationFlowContainer.defaultProps = {
|
|
98
|
+
initialData: null,
|
|
99
|
+
onChange: null,
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
export default CommunicationFlowContainer;
|