@capillarytech/creatives-library 9.0.14-beta.0 → 9.0.14
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 +0 -3
- package/package.json +1 -1
- package/services/api.js +10 -0
- package/services/tests/api.test.js +83 -0
- package/utils/common.js +0 -8
- package/v2Components/CommonTestAndPreview/UnifiedPreview/WhatsAppPreviewContent.js +5 -3
- package/v2Components/CommonTestAndPreview/index.js +7 -0
- package/v2Components/FormBuilder/_formBuilder.scss +0 -5
- package/v2Components/FormBuilder/index.js +4479 -41
- package/v2Components/NavigationBar/index.js +27 -0
- package/v2Components/NavigationBar/messages.js +4 -0
- package/v2Components/NavigationBar/tests/index.test.js +19 -0
- package/v2Components/NewCallTask/index.js +6 -1
- package/v2Components/TemplatePreview/index.js +4 -2
- package/v2Containers/Cap/index.js +3 -1
- package/v2Containers/CommunicationFlow/CommunicationFlow.js +130 -20
- package/v2Containers/CommunicationFlow/CommunicationFlow.scss +154 -0
- package/v2Containers/CommunicationFlow/CommunicationFlowCard.js +240 -0
- package/v2Containers/CommunicationFlow/DemoPage.js +47 -0
- package/v2Containers/CommunicationFlow/Tests/CommunicationFlow.test.js +369 -2
- package/v2Containers/CommunicationFlow/Tests/CommunicationFlowCard.test.js +619 -0
- package/v2Containers/CommunicationFlow/Tests/DemoPage.test.js +77 -0
- package/v2Containers/CommunicationFlow/Tests/getContentBody.test.js +933 -0
- package/v2Containers/CommunicationFlow/constants.js +45 -10
- package/v2Containers/CommunicationFlow/index.js +5 -2
- package/v2Containers/CommunicationFlow/messages.js +20 -0
- package/v2Containers/CommunicationFlow/steps/ChannelSelectionStep/ChannelSelectionStep.js +94 -31
- package/v2Containers/CommunicationFlow/steps/ChannelSelectionStep/ChannelSelectionStep.scss +14 -11
- package/v2Containers/CommunicationFlow/steps/ChannelSelectionStep/Tests/ChannelSelectionStep.test.js +1144 -32
- package/v2Containers/CommunicationFlow/steps/ChannelSelectionStep/extractContentForPreview.js +183 -0
- package/v2Containers/CommunicationFlow/steps/CommunicationStrategyStep/CommunicationStrategyStep.js +3 -0
- package/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/DeliverySettingsSection.js +39 -0
- package/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/Tests/DeliverySettingsSection.test.js +6 -2
- package/v2Containers/CommunicationFlow/utils/getContentBody.js +369 -0
- package/v2Containers/CommunicationFlow/utils/getContentBody.scss +19 -0
- package/v2Containers/CommunicationFlow/utils/getEnabledSteps.js +1 -1
- package/v2Containers/CreativesContainer/constants.js +6 -0
- package/v2Containers/CreativesContainer/index.js +68 -1
- package/v2Containers/CreativesContainer/tests/__snapshots__/SlideBoxContent.test.js.snap +2 -2
- package/v2Containers/Templates/index.js +2 -2
- package/v2Containers/TemplatesV2/index.js +9 -1
- package/v2Containers/Whatsapp/tests/__snapshots__/index.test.js.snap +41 -34
- package/v2Components/FormBuilder/Classic.js +0 -4487
- package/v2Components/FormBuilder/Functional/FormBuilderShell.js +0 -371
- package/v2Components/FormBuilder/Functional/channels/registry.js +0 -17
- package/v2Components/FormBuilder/Functional/channels/sms/buildSubmitPayload.js +0 -9
- package/v2Components/FormBuilder/Functional/channels/sms/config.js +0 -30
- package/v2Components/FormBuilder/Functional/channels/sms/getEditorErrorDescriptor.js +0 -46
- package/v2Components/FormBuilder/Functional/channels/sms/getLiquidContent.js +0 -13
- package/v2Components/FormBuilder/Functional/channels/sms/index.js +0 -22
- package/v2Components/FormBuilder/Functional/channels/sms/tests/getEditorErrorDescriptor.test.js +0 -52
- package/v2Components/FormBuilder/Functional/channels/sms/tests/getLiquidContent.test.js +0 -25
- package/v2Components/FormBuilder/Functional/channels/sms/tests/validate.test.js +0 -87
- package/v2Components/FormBuilder/Functional/channels/sms/validate.js +0 -89
- package/v2Components/FormBuilder/Functional/constants.js +0 -42
- package/v2Components/FormBuilder/Functional/core/schema/fieldRegistry.js +0 -38
- package/v2Components/FormBuilder/Functional/core/schema/initializeFormState.js +0 -85
- package/v2Components/FormBuilder/Functional/core/store/formReducer.js +0 -81
- package/v2Components/FormBuilder/Functional/core/store/selectors.js +0 -30
- package/v2Components/FormBuilder/Functional/core/store/toLegacyFormData.js +0 -91
- package/v2Components/FormBuilder/Functional/index.js +0 -26
- package/v2Components/FormBuilder/Functional/layout/FieldSlot.js +0 -59
- package/v2Components/FormBuilder/Functional/layout/SchemaForm.js +0 -31
- package/v2Components/FormBuilder/Functional/layout/Section.js +0 -116
- package/v2Components/FormBuilder/Functional/renderers/smsRenderers.js +0 -258
- package/v2Components/FormBuilder/Functional/tests/channelRegistry.test.js +0 -21
- package/v2Components/FormBuilder/Functional/tests/fieldRegistry.test.js +0 -65
- package/v2Components/FormBuilder/Functional/tests/fieldSlot.test.js +0 -97
- package/v2Components/FormBuilder/Functional/tests/fixtures/smsParityCases.js +0 -192
- package/v2Components/FormBuilder/Functional/tests/formReducer.test.js +0 -129
- package/v2Components/FormBuilder/Functional/tests/initializeFormState.test.js +0 -132
- package/v2Components/FormBuilder/Functional/tests/schemaForm.test.js +0 -40
- package/v2Components/FormBuilder/Functional/tests/section.test.js +0 -99
- package/v2Components/FormBuilder/Functional/tests/selectors.test.js +0 -67
- package/v2Components/FormBuilder/Functional/tests/sms.crossFlowParity.test.js +0 -155
- package/v2Components/FormBuilder/Functional/tests/sms.liquid.test.js +0 -172
- package/v2Components/FormBuilder/Functional/tests/sms.rollout.test.js +0 -122
- package/v2Components/FormBuilder/Functional/tests/sms.shell.parity.test.js +0 -329
- package/v2Components/FormBuilder/Functional/tests/smsRenderers.test.js +0 -160
- package/v2Components/FormBuilder/Functional/tests/toLegacyFormData.test.js +0 -95
- package/v2Components/FormBuilder/tests/__snapshots__/sms.characterization.test.js.snap +0 -114
- package/v2Components/FormBuilder/tests/entryGate.test.js +0 -106
- package/v2Components/FormBuilder/tests/sms.characterization.test.js +0 -336
|
@@ -1,114 +0,0 @@
|
|
|
1
|
-
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
|
2
|
-
|
|
3
|
-
exports[`SMS characterization — pure golden utilities updateCharCount golden values for ASCII and unicode parts math: ascii-11-chars 1`] = `
|
|
4
|
-
Object {
|
|
5
|
-
"char_list": Array [
|
|
6
|
-
<span
|
|
7
|
-
className=""
|
|
8
|
-
>
|
|
9
|
-
h
|
|
10
|
-
</span>,
|
|
11
|
-
<span
|
|
12
|
-
className=""
|
|
13
|
-
>
|
|
14
|
-
e
|
|
15
|
-
</span>,
|
|
16
|
-
<span
|
|
17
|
-
className=""
|
|
18
|
-
>
|
|
19
|
-
l
|
|
20
|
-
</span>,
|
|
21
|
-
<span
|
|
22
|
-
className=""
|
|
23
|
-
>
|
|
24
|
-
l
|
|
25
|
-
</span>,
|
|
26
|
-
<span
|
|
27
|
-
className=""
|
|
28
|
-
>
|
|
29
|
-
o
|
|
30
|
-
</span>,
|
|
31
|
-
<span
|
|
32
|
-
className=""
|
|
33
|
-
>
|
|
34
|
-
|
|
35
|
-
</span>,
|
|
36
|
-
<span
|
|
37
|
-
className=""
|
|
38
|
-
>
|
|
39
|
-
w
|
|
40
|
-
</span>,
|
|
41
|
-
<span
|
|
42
|
-
className=""
|
|
43
|
-
>
|
|
44
|
-
o
|
|
45
|
-
</span>,
|
|
46
|
-
<span
|
|
47
|
-
className=""
|
|
48
|
-
>
|
|
49
|
-
r
|
|
50
|
-
</span>,
|
|
51
|
-
<span
|
|
52
|
-
className=""
|
|
53
|
-
>
|
|
54
|
-
l
|
|
55
|
-
</span>,
|
|
56
|
-
<span
|
|
57
|
-
className=""
|
|
58
|
-
>
|
|
59
|
-
d
|
|
60
|
-
</span>,
|
|
61
|
-
],
|
|
62
|
-
"chars_sms": 160,
|
|
63
|
-
"chars_used": 11,
|
|
64
|
-
"optoutUrlPresent": false,
|
|
65
|
-
"optouturlLength": 0,
|
|
66
|
-
"parts": 1,
|
|
67
|
-
"responseState": "REQUEST",
|
|
68
|
-
"unicode": false,
|
|
69
|
-
}
|
|
70
|
-
`;
|
|
71
|
-
|
|
72
|
-
exports[`SMS characterization — pure golden utilities updateCharCount golden values for ASCII and unicode parts math: unicode-enabled 1`] = `
|
|
73
|
-
Object {
|
|
74
|
-
"char_list": Array [
|
|
75
|
-
<span
|
|
76
|
-
className=""
|
|
77
|
-
>
|
|
78
|
-
न
|
|
79
|
-
</span>,
|
|
80
|
-
<span
|
|
81
|
-
className=""
|
|
82
|
-
>
|
|
83
|
-
म
|
|
84
|
-
</span>,
|
|
85
|
-
<span
|
|
86
|
-
className=""
|
|
87
|
-
>
|
|
88
|
-
स
|
|
89
|
-
</span>,
|
|
90
|
-
<span
|
|
91
|
-
className=""
|
|
92
|
-
>
|
|
93
|
-
्
|
|
94
|
-
</span>,
|
|
95
|
-
<span
|
|
96
|
-
className=""
|
|
97
|
-
>
|
|
98
|
-
त
|
|
99
|
-
</span>,
|
|
100
|
-
<span
|
|
101
|
-
className=""
|
|
102
|
-
>
|
|
103
|
-
े
|
|
104
|
-
</span>,
|
|
105
|
-
],
|
|
106
|
-
"chars_sms": 70,
|
|
107
|
-
"chars_used": 6,
|
|
108
|
-
"optoutUrlPresent": false,
|
|
109
|
-
"optouturlLength": 0,
|
|
110
|
-
"parts": 1,
|
|
111
|
-
"responseState": "REQUEST",
|
|
112
|
-
"unicode": true,
|
|
113
|
-
}
|
|
114
|
-
`;
|
|
@@ -1,106 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* FormBuilder entry-gate (index.js) routing tests — PR1.
|
|
3
|
-
*
|
|
4
|
-
* Verifies ONLY the gate's routing decision (Classic vs Functional). The two
|
|
5
|
-
* implementations are mocked with trivial stubs so this stays a fast unit test
|
|
6
|
-
* of the gate, independent of the 4,400-line monolith.
|
|
7
|
-
*
|
|
8
|
-
* Contract pinned here:
|
|
9
|
-
* - flag OFF -> Classic, for every channel (the default until rollout)
|
|
10
|
-
* - flag ON + SMS channel (via `channel` prop OR `schema.channel`) -> Functional
|
|
11
|
-
* - flag ON + non-SMS channel -> Classic (other channels untouched)
|
|
12
|
-
* - flag ON + channel unknown (async schema not yet arrived) -> Classic
|
|
13
|
-
* - flag read throws (bootstrap/test race) -> Classic, no crash
|
|
14
|
-
*/
|
|
15
|
-
|
|
16
|
-
import React from 'react';
|
|
17
|
-
import '@testing-library/jest-dom';
|
|
18
|
-
import { render, screen } from '../../../utils/test-utils';
|
|
19
|
-
|
|
20
|
-
// Stub both implementations: assert which one the gate chose, not what they render.
|
|
21
|
-
jest.mock('../Classic', () => (props) => (
|
|
22
|
-
<div data-testid="impl-classic">classic:{(props.schema && props.schema.channel) || props.channel || ''}</div>
|
|
23
|
-
));
|
|
24
|
-
jest.mock('../Functional', () => (props) => (
|
|
25
|
-
<div data-testid="impl-functional">functional:{(props.schema && props.schema.channel) || props.channel || ''}</div>
|
|
26
|
-
));
|
|
27
|
-
|
|
28
|
-
// Mock only the one flag reader the gate uses; keep the rest of common.js intact.
|
|
29
|
-
jest.mock('../../../utils/common', () => ({
|
|
30
|
-
...jest.requireActual('../../../utils/common'),
|
|
31
|
-
hasNewFormBuilderEnabledForSms: jest.fn(),
|
|
32
|
-
}));
|
|
33
|
-
|
|
34
|
-
// eslint-disable-next-line import/first
|
|
35
|
-
import FormBuilder from '../index';
|
|
36
|
-
// eslint-disable-next-line import/first
|
|
37
|
-
import { hasNewFormBuilderEnabledForSms } from '../../../utils/common';
|
|
38
|
-
|
|
39
|
-
const renderGate = (props = {}) => render(<FormBuilder {...props} />);
|
|
40
|
-
|
|
41
|
-
describe('FormBuilder entry gate — routing', () => {
|
|
42
|
-
describe('flag OFF (default)', () => {
|
|
43
|
-
beforeEach(() => hasNewFormBuilderEnabledForSms.mockReturnValue(false));
|
|
44
|
-
|
|
45
|
-
it('renders Classic for SMS', () => {
|
|
46
|
-
renderGate({ schema: { channel: 'SMS' } });
|
|
47
|
-
expect(screen.getByTestId('impl-classic')).toBeInTheDocument();
|
|
48
|
-
expect(screen.queryByTestId('impl-functional')).toBeNull();
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
it('renders Classic for non-SMS channels', () => {
|
|
52
|
-
renderGate({ schema: { channel: 'EMAIL' } });
|
|
53
|
-
expect(screen.getByTestId('impl-classic')).toBeInTheDocument();
|
|
54
|
-
});
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
describe('flag ON', () => {
|
|
58
|
-
beforeEach(() => hasNewFormBuilderEnabledForSms.mockReturnValue(true));
|
|
59
|
-
|
|
60
|
-
it('routes SMS to Functional when channel comes from schema.channel', () => {
|
|
61
|
-
renderGate({ schema: { channel: 'SMS' } });
|
|
62
|
-
expect(screen.getByTestId('impl-functional')).toBeInTheDocument();
|
|
63
|
-
expect(screen.queryByTestId('impl-classic')).toBeNull();
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
it('routes SMS to Functional when channel comes from the synchronous channel prop', () => {
|
|
67
|
-
// Create-style mount: schema not yet arrived, but the channel prop is present.
|
|
68
|
-
renderGate({ channel: 'SMS', schema: {} });
|
|
69
|
-
expect(screen.getByTestId('impl-functional')).toBeInTheDocument();
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
it('is case-insensitive on the channel value', () => {
|
|
73
|
-
renderGate({ schema: { channel: 'sms' } });
|
|
74
|
-
expect(screen.getByTestId('impl-functional')).toBeInTheDocument();
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
it('keeps non-SMS channels on Classic even when the SMS flag is on', () => {
|
|
78
|
-
renderGate({ schema: { channel: 'EMAIL' } });
|
|
79
|
-
expect(screen.getByTestId('impl-classic')).toBeInTheDocument();
|
|
80
|
-
expect(screen.queryByTestId('impl-functional')).toBeNull();
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
it('renders Classic when the channel is not yet known (async schema, no channel prop)', () => {
|
|
84
|
-
// SMS Create at first render: schema empty, no channel prop -> stay on Classic
|
|
85
|
-
// until the channel resolves. Safe default; no premature swap.
|
|
86
|
-
renderGate({ schema: {} });
|
|
87
|
-
expect(screen.getByTestId('impl-classic')).toBeInTheDocument();
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
it('forwards all props unchanged to the chosen implementation', () => {
|
|
91
|
-
renderGate({ schema: { channel: 'SMS' } });
|
|
92
|
-
expect(screen.getByTestId('impl-functional')).toHaveTextContent('functional:SMS');
|
|
93
|
-
});
|
|
94
|
-
});
|
|
95
|
-
|
|
96
|
-
describe('resilience', () => {
|
|
97
|
-
it('falls back to Classic if the flag read throws (window.capAuth not initialized)', () => {
|
|
98
|
-
hasNewFormBuilderEnabledForSms.mockImplementation(() => {
|
|
99
|
-
throw new Error('capAuth not initialized');
|
|
100
|
-
});
|
|
101
|
-
renderGate({ schema: { channel: 'SMS' } });
|
|
102
|
-
expect(screen.getByTestId('impl-classic')).toBeInTheDocument();
|
|
103
|
-
expect(screen.queryByTestId('impl-functional')).toBeNull();
|
|
104
|
-
});
|
|
105
|
-
});
|
|
106
|
-
});
|
|
@@ -1,336 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* SMS characterization (golden) tests — FormBuilder V3 migration, Phase 0.
|
|
3
|
-
*
|
|
4
|
-
* These tests pin the CURRENT behavior of the FormBuilder monolith
|
|
5
|
-
* (app/v2Components/FormBuilder/Classic.js) for the SMS channel, using the real
|
|
6
|
-
* bundled layout schema (app/v2Containers/Sms/initialSchema.js). They are the
|
|
7
|
-
* parity gate for the functional rewrite: the new implementation must keep
|
|
8
|
-
* every assertion here green without edits.
|
|
9
|
-
*
|
|
10
|
-
* Behavior is pinned as-is, including quirks (e.g. getChannelData's
|
|
11
|
-
* "undefined undefined" output for empty formData). Do not "fix" expectations
|
|
12
|
-
* here — if the monolith changes, that is the regression.
|
|
13
|
-
*/
|
|
14
|
-
|
|
15
|
-
import React from 'react';
|
|
16
|
-
import { injectIntl } from 'react-intl';
|
|
17
|
-
import '@testing-library/jest-dom';
|
|
18
|
-
import _ from 'lodash';
|
|
19
|
-
import { Provider } from 'react-redux';
|
|
20
|
-
import { Router } from 'react-router-dom';
|
|
21
|
-
import { configureStore } from '@capillarytech/vulcan-react-sdk/utils';
|
|
22
|
-
// Import Classic directly, NOT the entry-gated ../index: the gate could route SMS to the
|
|
23
|
-
// Functional build based on feature access, making this golden suite non-deterministic.
|
|
24
|
-
// Functional parity is covered separately (Functional/tests/sms.*parity*.test.js).
|
|
25
|
-
import FormBuilder from '../Classic';
|
|
26
|
-
import history from '../../../utils/history';
|
|
27
|
-
import { initialReducer } from '../../../initialReducer';
|
|
28
|
-
import { render, screen, fireEvent, act } from '../../../utils/test-utils';
|
|
29
|
-
import { response as smsSchemaResponse } from '../../../v2Containers/Sms/initialSchema';
|
|
30
|
-
import { getChannelData } from '../../../utils/commonUtils';
|
|
31
|
-
import { checkUnicode, updateCharCount } from '../../../utils/smsCharCountV2';
|
|
32
|
-
|
|
33
|
-
// FormBuilder renders v2Containers/TagList, whose export is wrapped in
|
|
34
|
-
// UserIsAuthenticated (redux-auth-wrapper). The wrapper reads ownProps.history
|
|
35
|
-
// (not router context) and crashes outside a Route. Neutralize it the same way
|
|
36
|
-
// the InApp container tests do (app/v2Containers/InApp/tests/index.test.js).
|
|
37
|
-
jest.mock('redux-auth-wrapper/history4/redirect', () => ({
|
|
38
|
-
connectedRouterRedirect: jest.fn(() => (Component) => Component),
|
|
39
|
-
}));
|
|
40
|
-
|
|
41
|
-
// updateCharCount (smsCharCountV2) is impure: its first invocation fires
|
|
42
|
-
// Api.getUnsubscribeUrl() from module-level state. Stub it with a plain
|
|
43
|
-
// function (NOT jest.fn — this repo's resetMocks:true would wipe a jest.fn
|
|
44
|
-
// implementation between tests and resurrect the network call).
|
|
45
|
-
jest.mock('../../../services/api', () => ({
|
|
46
|
-
...jest.requireActual('../../../services/api'),
|
|
47
|
-
getUnsubscribeUrl: () => Promise.resolve({ response: { response: '' } }),
|
|
48
|
-
}));
|
|
49
|
-
|
|
50
|
-
const ComponentToRender = injectIntl(FormBuilder);
|
|
51
|
-
|
|
52
|
-
/**
|
|
53
|
-
* Mimics the containers' injectEvents step (Sms/Edit + Sms/Create walk the
|
|
54
|
-
* schema and glue handler functions onto nodes as `injectedEvents`). Each
|
|
55
|
-
* handler records its arguments and its `this` binding, because the monolith
|
|
56
|
-
* invokes them with .call(this.props.parent, ...) — a contract the rewrite's
|
|
57
|
-
* parent bridge must preserve.
|
|
58
|
-
*/
|
|
59
|
-
const injectHandlers = (schema) => {
|
|
60
|
-
const registry = {};
|
|
61
|
-
const attach = (field) => {
|
|
62
|
-
if (!field || !field.id || !field.supportedEvents) return;
|
|
63
|
-
field.injectedEvents = {};
|
|
64
|
-
field.supportedEvents.forEach((event) => {
|
|
65
|
-
const record = { calls: [], contexts: [] };
|
|
66
|
-
registry[`${field.id}:${event}`] = record;
|
|
67
|
-
field.injectedEvents[event] = function capture(...args) {
|
|
68
|
-
record.calls.push(args);
|
|
69
|
-
record.contexts.push(this);
|
|
70
|
-
};
|
|
71
|
-
});
|
|
72
|
-
};
|
|
73
|
-
const walkSection = (section) => {
|
|
74
|
-
if (!section) return;
|
|
75
|
-
if (section.type === 'parent') {
|
|
76
|
-
(section.childSections || []).forEach(walkSection);
|
|
77
|
-
} else if (section.type === 'multicols') {
|
|
78
|
-
(section.inputFields || []).forEach((row) => (row.cols || []).forEach(attach));
|
|
79
|
-
(section.actionFields || []).forEach((row) => (row.cols || []).forEach(attach));
|
|
80
|
-
} else if (section.type === 'col-label') {
|
|
81
|
-
(section.inputFields || []).forEach(attach);
|
|
82
|
-
(section.actionFields || []).forEach(attach);
|
|
83
|
-
}
|
|
84
|
-
};
|
|
85
|
-
((schema.standalone && schema.standalone.sections) || []).forEach(walkSection);
|
|
86
|
-
return registry;
|
|
87
|
-
};
|
|
88
|
-
|
|
89
|
-
const buildSmsSchema = () => {
|
|
90
|
-
const schema = _.cloneDeep(smsSchemaResponse.metaEntities[0].definition);
|
|
91
|
-
const registry = injectHandlers(schema);
|
|
92
|
-
return { schema, registry };
|
|
93
|
-
};
|
|
94
|
-
|
|
95
|
-
const buildProps = (overrides = {}) => ({
|
|
96
|
-
formData: {},
|
|
97
|
-
location: { pathname: '/sms/create', query: { type: false, module: 'default' } },
|
|
98
|
-
usingTabContainer: true,
|
|
99
|
-
isFullMode: true,
|
|
100
|
-
isEdit: false,
|
|
101
|
-
startValidation: false,
|
|
102
|
-
checkValidation: false,
|
|
103
|
-
currentTab: 1,
|
|
104
|
-
tabCount: 1,
|
|
105
|
-
tabKey: '',
|
|
106
|
-
tags: [],
|
|
107
|
-
injectedTags: {},
|
|
108
|
-
onChange: jest.fn(),
|
|
109
|
-
onSubmit: jest.fn(),
|
|
110
|
-
onFormValidityChange: jest.fn(),
|
|
111
|
-
stopValidation: jest.fn(),
|
|
112
|
-
showLiquidErrorInFooter: jest.fn(),
|
|
113
|
-
setModalContent: jest.fn(),
|
|
114
|
-
handleCancelModal: jest.fn(),
|
|
115
|
-
...overrides,
|
|
116
|
-
});
|
|
117
|
-
|
|
118
|
-
const renderSms = (props) => {
|
|
119
|
-
const store = configureStore({}, initialReducer, history);
|
|
120
|
-
// Router is required: a component in FormBuilder's SMS subtree is wrapped
|
|
121
|
-
// by UserIsAuthenticated (redux-auth-wrapper), which needs router history.
|
|
122
|
-
const ui = (p) => (
|
|
123
|
-
<Router history={history}>
|
|
124
|
-
<Provider store={store}>
|
|
125
|
-
<ComponentToRender {...p} />
|
|
126
|
-
</Provider>
|
|
127
|
-
</Router>
|
|
128
|
-
);
|
|
129
|
-
const utils = render(ui(props));
|
|
130
|
-
return { ...utils, rerenderWith: (next) => utils.rerender(ui(next)) };
|
|
131
|
-
};
|
|
132
|
-
|
|
133
|
-
const lastCall = (mockFn) => mockFn.mock.calls[mockFn.mock.calls.length - 1];
|
|
134
|
-
|
|
135
|
-
describe('SMS characterization — mount & seeding', () => {
|
|
136
|
-
it('renders every active SMS schema field', () => {
|
|
137
|
-
const { schema } = buildSmsSchema();
|
|
138
|
-
renderSms(buildProps({ schema }));
|
|
139
|
-
|
|
140
|
-
expect(screen.getByPlaceholderText('Enter template name')).toBeInTheDocument();
|
|
141
|
-
expect(screen.getByPlaceholderText('Please input sms template content.')).toBeInTheDocument();
|
|
142
|
-
expect(screen.getByLabelText('Allow Unicode characters')).toBeInTheDocument();
|
|
143
|
-
expect(screen.getByText('Save')).toBeInTheDocument();
|
|
144
|
-
expect(screen.getByText('Preview and Test')).toBeInTheDocument();
|
|
145
|
-
});
|
|
146
|
-
|
|
147
|
-
it('mount validation reports invalid: template-name flagged at ROOT, sms-editor flagged under tab 0', () => {
|
|
148
|
-
const { schema } = buildSmsSchema();
|
|
149
|
-
const props = buildProps({ schema });
|
|
150
|
-
renderSms(props);
|
|
151
|
-
|
|
152
|
-
expect(props.onFormValidityChange).toHaveBeenCalled();
|
|
153
|
-
const [isValid, errorData] = lastCall(props.onFormValidityChange);
|
|
154
|
-
expect(isValid).toBe(false);
|
|
155
|
-
// template-name is validated at the formData ROOT (not inside tab 0)
|
|
156
|
-
expect(errorData['template-name']).toBe(true);
|
|
157
|
-
// empty content flags the editor inside tab 0 with a message
|
|
158
|
-
expect(errorData[0]['sms-editor']).toBeTruthy();
|
|
159
|
-
});
|
|
160
|
-
});
|
|
161
|
-
|
|
162
|
-
describe('SMS characterization — onChange contract', () => {
|
|
163
|
-
it('typing in sms-editor writes formData[0] + mirrors to the base alias, and emits onChange(formData, tabCount, currentTab, fieldSchema)', () => {
|
|
164
|
-
const { schema } = buildSmsSchema();
|
|
165
|
-
const props = buildProps({ schema });
|
|
166
|
-
renderSms(props);
|
|
167
|
-
|
|
168
|
-
fireEvent.change(screen.getByPlaceholderText('Please input sms template content.'), {
|
|
169
|
-
target: { value: 'hello world' },
|
|
170
|
-
});
|
|
171
|
-
|
|
172
|
-
expect(props.onChange).toHaveBeenCalled();
|
|
173
|
-
const [formData, tabCount, currentTab, fieldSchema] = lastCall(props.onChange);
|
|
174
|
-
expect(tabCount).toBe(1);
|
|
175
|
-
expect(currentTab).toBe(1);
|
|
176
|
-
expect(fieldSchema.id).toBe('sms-editor');
|
|
177
|
-
// tab-indexed write
|
|
178
|
-
expect(formData[0]['sms-editor']).toBe('hello world');
|
|
179
|
-
// structural quirk: formData.base is an ALIAS of the base tab and mirrors writes
|
|
180
|
-
expect(formData[0].base).toBe(true);
|
|
181
|
-
expect(formData.base['sms-editor']).toBe('hello world');
|
|
182
|
-
// checkbox seeding default
|
|
183
|
-
expect(formData[0]['unicode-validity']).toBe(false);
|
|
184
|
-
});
|
|
185
|
-
|
|
186
|
-
it('template-name is a high-frequency field: DOM updates immediately, onChange is debounced (~300ms) and writes to the formData ROOT', () => {
|
|
187
|
-
jest.useFakeTimers();
|
|
188
|
-
try {
|
|
189
|
-
const { schema } = buildSmsSchema();
|
|
190
|
-
const props = buildProps({ schema });
|
|
191
|
-
renderSms(props);
|
|
192
|
-
props.onChange.mockClear();
|
|
193
|
-
|
|
194
|
-
const nameInput = screen.getByPlaceholderText('Enter template name');
|
|
195
|
-
fireEvent.change(nameInput, { target: { value: 'My SMS' } });
|
|
196
|
-
|
|
197
|
-
// immediate UI feedback, no parent emission yet
|
|
198
|
-
expect(nameInput).toHaveValue('My SMS');
|
|
199
|
-
expect(props.onChange).not.toHaveBeenCalled();
|
|
200
|
-
|
|
201
|
-
act(() => {
|
|
202
|
-
jest.advanceTimersByTime(350);
|
|
203
|
-
});
|
|
204
|
-
|
|
205
|
-
expect(props.onChange).toHaveBeenCalled();
|
|
206
|
-
const [formData] = lastCall(props.onChange);
|
|
207
|
-
// standalone field lands at the ROOT, not inside tab 0
|
|
208
|
-
expect(formData['template-name']).toBe('My SMS');
|
|
209
|
-
} finally {
|
|
210
|
-
jest.useRealTimers();
|
|
211
|
-
}
|
|
212
|
-
});
|
|
213
|
-
});
|
|
214
|
-
|
|
215
|
-
describe('SMS characterization — validation & save flow (startValidation rising edge)', () => {
|
|
216
|
-
const typeNameAndContent = (content) => {
|
|
217
|
-
jest.useFakeTimers();
|
|
218
|
-
fireEvent.change(screen.getByPlaceholderText('Enter template name'), {
|
|
219
|
-
target: { value: 'My SMS' },
|
|
220
|
-
});
|
|
221
|
-
act(() => {
|
|
222
|
-
jest.advanceTimersByTime(350);
|
|
223
|
-
});
|
|
224
|
-
jest.useRealTimers();
|
|
225
|
-
fireEvent.change(screen.getByPlaceholderText('Please input sms template content.'), {
|
|
226
|
-
target: { value: content },
|
|
227
|
-
});
|
|
228
|
-
};
|
|
229
|
-
|
|
230
|
-
it('unicode content with the checkbox OFF blocks save: invalid + unicode-validity error + stopValidation, no onSubmit', () => {
|
|
231
|
-
const { schema } = buildSmsSchema();
|
|
232
|
-
const props = buildProps({ schema });
|
|
233
|
-
const { rerenderWith } = renderSms(props);
|
|
234
|
-
|
|
235
|
-
typeNameAndContent('नमस्ते');
|
|
236
|
-
rerenderWith({ ...props, startValidation: true });
|
|
237
|
-
|
|
238
|
-
const [isValid, errorData] = lastCall(props.onFormValidityChange);
|
|
239
|
-
expect(isValid).toBe(false);
|
|
240
|
-
expect(errorData[0]['unicode-validity']).toBe(true);
|
|
241
|
-
expect(props.stopValidation).toHaveBeenCalled();
|
|
242
|
-
expect(props.onSubmit).not.toHaveBeenCalled();
|
|
243
|
-
});
|
|
244
|
-
|
|
245
|
-
it('checking "Allow Unicode characters" makes the same content valid and submits (isFullMode skips the liquid pipeline)', () => {
|
|
246
|
-
const { schema } = buildSmsSchema();
|
|
247
|
-
const props = buildProps({ schema });
|
|
248
|
-
const { rerenderWith } = renderSms(props);
|
|
249
|
-
|
|
250
|
-
typeNameAndContent('नमस्ते');
|
|
251
|
-
fireEvent.click(screen.getByLabelText('Allow Unicode characters'));
|
|
252
|
-
|
|
253
|
-
rerenderWith({ ...props, startValidation: true });
|
|
254
|
-
|
|
255
|
-
const [isValid] = lastCall(props.onFormValidityChange);
|
|
256
|
-
expect(isValid).toBe(true);
|
|
257
|
-
expect(props.onSubmit).toHaveBeenCalledTimes(1);
|
|
258
|
-
const [submitted] = lastCall(props.onSubmit);
|
|
259
|
-
expect(submitted['template-name']).toBe('My SMS');
|
|
260
|
-
expect(submitted[0]['sms-editor']).toBe('नमस्ते');
|
|
261
|
-
expect(submitted[0]['unicode-validity']).toBe(true);
|
|
262
|
-
});
|
|
263
|
-
|
|
264
|
-
it('plain ASCII content with a template name is valid and submits unchanged', () => {
|
|
265
|
-
const { schema } = buildSmsSchema();
|
|
266
|
-
const props = buildProps({ schema });
|
|
267
|
-
const { rerenderWith } = renderSms(props);
|
|
268
|
-
|
|
269
|
-
typeNameAndContent('hello world');
|
|
270
|
-
rerenderWith({ ...props, startValidation: true });
|
|
271
|
-
|
|
272
|
-
const [isValid] = lastCall(props.onFormValidityChange);
|
|
273
|
-
expect(isValid).toBe(true);
|
|
274
|
-
expect(props.onSubmit).toHaveBeenCalledTimes(1);
|
|
275
|
-
expect(lastCall(props.onSubmit)[0][0]['sms-editor']).toBe('hello world');
|
|
276
|
-
});
|
|
277
|
-
});
|
|
278
|
-
|
|
279
|
-
describe('SMS characterization — container handler contract (injectedEvents + parent rebinding)', () => {
|
|
280
|
-
it('clicking Save invokes the container-injected saveFormData handler with `this` rebound to props.parent and val.id as second arg', () => {
|
|
281
|
-
const { schema, registry } = buildSmsSchema();
|
|
282
|
-
const parentSentinel = { iAmTheContainer: true };
|
|
283
|
-
renderSms(buildProps({ schema, parent: parentSentinel }));
|
|
284
|
-
|
|
285
|
-
fireEvent.click(screen.getByText('Save'));
|
|
286
|
-
|
|
287
|
-
const record = registry['save-button:saveFormData'];
|
|
288
|
-
expect(record.calls).toHaveLength(1);
|
|
289
|
-
expect(record.calls[0][1]).toBe('save-button');
|
|
290
|
-
expect(record.contexts[0]).toBe(parentSentinel);
|
|
291
|
-
});
|
|
292
|
-
});
|
|
293
|
-
|
|
294
|
-
describe('SMS characterization — edit-mode hydration', () => {
|
|
295
|
-
it('renders saved template data passed via the formData prop without overwriting it', () => {
|
|
296
|
-
const { schema } = buildSmsSchema();
|
|
297
|
-
const tab0 = {
|
|
298
|
-
'sms-editor': 'Existing body',
|
|
299
|
-
'unicode-validity': false,
|
|
300
|
-
base: true,
|
|
301
|
-
tabKey: 'k1',
|
|
302
|
-
};
|
|
303
|
-
const props = buildProps({
|
|
304
|
-
schema,
|
|
305
|
-
isEdit: true,
|
|
306
|
-
formData: { 'template-name': 'Existing name', 0: tab0, base: tab0 },
|
|
307
|
-
});
|
|
308
|
-
renderSms(props);
|
|
309
|
-
|
|
310
|
-
expect(screen.getByPlaceholderText('Enter template name')).toHaveValue('Existing name');
|
|
311
|
-
expect(screen.getByPlaceholderText('Please input sms template content.')).toHaveValue('Existing body');
|
|
312
|
-
});
|
|
313
|
-
});
|
|
314
|
-
|
|
315
|
-
describe('SMS characterization — pure golden utilities', () => {
|
|
316
|
-
it('getChannelData(SMS) concatenates base sms-editor and root template-name', () => {
|
|
317
|
-
const tab0 = { 'sms-editor': 'hello world', base: true };
|
|
318
|
-
const formData = { 'template-name': 'My SMS', 0: tab0, base: tab0 };
|
|
319
|
-
expect(getChannelData('SMS', formData)).toBe('hello world My SMS');
|
|
320
|
-
});
|
|
321
|
-
|
|
322
|
-
it('getChannelData(SMS) quirk: empty formData yields the literal string "undefined undefined"', () => {
|
|
323
|
-
// Pinned as-is: the template literal is truthy, so the || "" fallback never applies.
|
|
324
|
-
expect(getChannelData('SMS', {})).toBe('undefined undefined');
|
|
325
|
-
});
|
|
326
|
-
|
|
327
|
-
it('checkUnicode distinguishes unicode from GSM content', () => {
|
|
328
|
-
expect(checkUnicode('नमस्ते')).toBe(true);
|
|
329
|
-
expect(checkUnicode('hello world')).toBe(false);
|
|
330
|
-
});
|
|
331
|
-
|
|
332
|
-
it('updateCharCount golden values for ASCII and unicode parts math', () => {
|
|
333
|
-
expect(updateCharCount('hello world', false)).toMatchSnapshot('ascii-11-chars');
|
|
334
|
-
expect(updateCharCount('नमस्ते', true)).toMatchSnapshot('unicode-enabled');
|
|
335
|
-
});
|
|
336
|
-
});
|