@capillarytech/creatives-library 8.0.242-alpha.1 → 8.0.242-alpha.10
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/package.json +1 -1
- package/v2Components/HtmlEditor/HTMLEditor.js +10 -2
- package/v2Components/HtmlEditor/_htmlEditor.scss +100 -23
- package/v2Components/HtmlEditor/components/EditorToolbar/_editorToolbar.scss +9 -0
- package/v2Components/HtmlEditor/components/FullscreenModal/_fullscreenModal.scss +21 -0
- package/v2Components/HtmlEditor/components/ValidationErrorDisplay/__tests__/index.test.js +0 -27
- package/v2Containers/EmailWrapper/components/EmailHTMLEditor.js +1 -0
- package/v2Containers/InApp/index.js +1 -0
- package/v2Containers/EmailWrapper/tests/EmailWrapperView.test.js +0 -239
package/package.json
CHANGED
|
@@ -77,6 +77,7 @@ const HTMLEditor = ({
|
|
|
77
77
|
onContextChange = null,
|
|
78
78
|
globalActions = null,
|
|
79
79
|
isLiquidEnabled = false, // Controls Liquid tab visibility in ValidationTabs
|
|
80
|
+
isFullMode = true, // Full mode vs library mode - controls layout and visibility
|
|
80
81
|
...props
|
|
81
82
|
}) => {
|
|
82
83
|
// Separate refs for main and modal editors to avoid conflicts
|
|
@@ -434,9 +435,14 @@ const HTMLEditor = ({
|
|
|
434
435
|
);
|
|
435
436
|
}
|
|
436
437
|
|
|
438
|
+
// Add library-mode class when not in full mode
|
|
439
|
+
// Note: isFullMode defaults to true, so library mode is when isFullMode === false
|
|
440
|
+
const isLibraryMode = isFullMode === false;
|
|
441
|
+
const editorClassName = `html-editor html-editor--${variant} ${isLibraryMode ? 'html-editor--library-mode' : ''} ${className}`.trim();
|
|
442
|
+
|
|
437
443
|
return (
|
|
438
444
|
<EditorProvider value={contextValue}>
|
|
439
|
-
<div className={
|
|
445
|
+
<div className={editorClassName} {...props}>
|
|
440
446
|
{/* Editor Toolbar - Conditional based on variant */}
|
|
441
447
|
{variant === HTML_EDITOR_VARIANTS.EMAIL ? (
|
|
442
448
|
<EditorToolbar
|
|
@@ -504,7 +510,7 @@ const HTMLEditor = ({
|
|
|
504
510
|
width="90vw"
|
|
505
511
|
className="html-editor-fullscreen-modal"
|
|
506
512
|
>
|
|
507
|
-
<CapRow className=
|
|
513
|
+
<CapRow className={`html-editor-fullscreen html-editor-fullscreen--${variant} ${isLibraryMode ? 'html-editor-fullscreen--library-mode' : ''}`}>
|
|
508
514
|
{/* Editor Toolbar - Conditional based on variant */}
|
|
509
515
|
{variant === HTML_EDITOR_VARIANTS.EMAIL ? (
|
|
510
516
|
<EditorToolbar
|
|
@@ -596,6 +602,7 @@ HTMLEditor.propTypes = {
|
|
|
596
602
|
onContextChange: PropTypes.func, // Deprecated: use globalActions instead
|
|
597
603
|
globalActions: PropTypes.object,
|
|
598
604
|
isLiquidEnabled: PropTypes.bool, // Controls Liquid tab visibility in validation
|
|
605
|
+
isFullMode: PropTypes.bool, // Full mode vs library mode
|
|
599
606
|
};
|
|
600
607
|
|
|
601
608
|
HTMLEditor.defaultProps = {
|
|
@@ -623,6 +630,7 @@ HTMLEditor.defaultProps = {
|
|
|
623
630
|
onContextChange: null,
|
|
624
631
|
globalActions: null, // Redux actions for API calls
|
|
625
632
|
isLiquidEnabled: false,
|
|
633
|
+
isFullMode: true, // Default to full mode
|
|
626
634
|
};
|
|
627
635
|
|
|
628
636
|
// Export with forwardRef to allow direct access to CodeEditorPane via ref
|
|
@@ -5,39 +5,71 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
// Import Cap UI variables (correct path)
|
|
8
|
-
@import
|
|
8
|
+
@import "~@capillarytech/cap-ui-library/styles/_variables.scss";
|
|
9
9
|
|
|
10
10
|
// Fullscreen modal header styling - using proper CSS specificity instead of !important
|
|
11
11
|
.html-editor-fullscreen {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
12
|
+
// INAPP variant only: Fullscreen header styling
|
|
13
|
+
&--inapp {
|
|
14
|
+
.html-editor__header--fullscreen {
|
|
15
|
+
display: flex;
|
|
16
|
+
align-items: center;
|
|
17
|
+
background-color: $CAP_G10;
|
|
18
|
+
border-bottom: 0.0625rem solid $CAP_G08;
|
|
19
|
+
padding-right: 1rem;
|
|
18
20
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
21
|
+
// Device toggle takes available space
|
|
22
|
+
.device-toggle {
|
|
23
|
+
flex: 1;
|
|
24
|
+
}
|
|
23
25
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
26
|
+
// Toolbar styling in fullscreen mode - INAPP variant only
|
|
27
|
+
.editor-toolbar {
|
|
28
|
+
background-color: transparent;
|
|
29
|
+
border-bottom: none;
|
|
30
|
+
padding: 0;
|
|
31
|
+
min-height: 3.25rem;
|
|
32
|
+
height: 3.25rem;
|
|
33
|
+
position: relative;
|
|
32
34
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
35
|
+
// Right-align toolbar actions
|
|
36
|
+
&__right {
|
|
37
|
+
margin-left: auto;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Library mode: Position absolute for toolbar in fullscreen (INAPP variant only)
|
|
43
|
+
// Using higher specificity to override _fullscreenModal.scss styles
|
|
44
|
+
&.html-editor-fullscreen--library-mode {
|
|
45
|
+
.html-editor__header--fullscreen {
|
|
46
|
+
.editor-toolbar,
|
|
47
|
+
.editor-toolbar.editor-toolbar,
|
|
48
|
+
.ant-layout-header.editor-toolbar {
|
|
49
|
+
position: absolute;
|
|
50
|
+
}
|
|
36
51
|
}
|
|
37
52
|
}
|
|
38
53
|
}
|
|
39
54
|
}
|
|
40
55
|
|
|
56
|
+
// Additional library mode overrides for fullscreen with maximum specificity
|
|
57
|
+
// These match the high-specificity selectors in _fullscreenModal.scss
|
|
58
|
+
.html-editor-fullscreen--inapp.html-editor-fullscreen--library-mode .html-editor__header--fullscreen .editor-toolbar,
|
|
59
|
+
.html-editor-fullscreen--inapp.html-editor-fullscreen--library-mode .html-editor__header--fullscreen .editor-toolbar.editor-toolbar,
|
|
60
|
+
.html-editor-fullscreen--inapp.html-editor-fullscreen--library-mode .html-editor__header--fullscreen .ant-layout-header.editor-toolbar,
|
|
61
|
+
.html-editor.html-editor-fullscreen--inapp.html-editor-fullscreen--library-mode .html-editor__header--fullscreen .editor-toolbar,
|
|
62
|
+
.html-editor.html-editor-fullscreen--inapp.html-editor-fullscreen--library-mode .html-editor__header--fullscreen .editor-toolbar.editor-toolbar,
|
|
63
|
+
.html-editor.html-editor-fullscreen--inapp.html-editor-fullscreen--library-mode .html-editor__header--fullscreen .ant-layout-header.editor-toolbar,
|
|
64
|
+
.ant-modal .html-editor-fullscreen--inapp.html-editor-fullscreen--library-mode .html-editor__header--fullscreen .editor-toolbar,
|
|
65
|
+
.ant-modal .html-editor-fullscreen--inapp.html-editor-fullscreen--library-mode .html-editor__header--fullscreen .editor-toolbar.editor-toolbar,
|
|
66
|
+
.ant-modal .html-editor-fullscreen--inapp.html-editor-fullscreen--library-mode .html-editor__header--fullscreen .ant-layout-header.editor-toolbar,
|
|
67
|
+
.ant-modal-content .html-editor-fullscreen--inapp.html-editor-fullscreen--library-mode .html-editor__header--fullscreen .editor-toolbar,
|
|
68
|
+
.ant-modal-content .html-editor-fullscreen--inapp.html-editor-fullscreen--library-mode .html-editor__header--fullscreen .editor-toolbar.editor-toolbar,
|
|
69
|
+
.ant-modal-content .html-editor-fullscreen--inapp.html-editor-fullscreen--library-mode .html-editor__header--fullscreen .ant-layout-header.editor-toolbar {
|
|
70
|
+
position: absolute;
|
|
71
|
+
}
|
|
72
|
+
|
|
41
73
|
.html-editor {
|
|
42
74
|
height: 37.5rem; // Fixed height instead of 100vh (600px = 37.5rem)
|
|
43
75
|
max-height: 80vh; // Responsive maximum height
|
|
@@ -49,6 +81,23 @@
|
|
|
49
81
|
border-radius: 0.25rem;
|
|
50
82
|
overflow: hidden;
|
|
51
83
|
|
|
84
|
+
// Library mode: Apply specific styles when isFullMode is false
|
|
85
|
+
&.html-editor--library-mode {
|
|
86
|
+
.preview-pane {
|
|
87
|
+
max-height: calc(95vh - 5%);
|
|
88
|
+
|
|
89
|
+
&__content {
|
|
90
|
+
height: calc(95vh - 5%); // Same as editor pane - maximize to full available height
|
|
91
|
+
max-height: calc(95vh - 50%);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
iframe {
|
|
95
|
+
height: calc(95vh - 5%); // Same as editor pane - maximize to full available height
|
|
96
|
+
max-height: calc(95vh - 50%);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
52
101
|
&.fullscreen {
|
|
53
102
|
position: fixed;
|
|
54
103
|
top: 0;
|
|
@@ -124,8 +173,19 @@
|
|
|
124
173
|
}
|
|
125
174
|
}
|
|
126
175
|
}
|
|
127
|
-
}
|
|
128
176
|
|
|
177
|
+
// Library mode: Position absolute for toolbar in InApp variant
|
|
178
|
+
// Using higher specificity to override default position: relative
|
|
179
|
+
&.html-editor--library-mode {
|
|
180
|
+
.html-editor__header {
|
|
181
|
+
.editor-toolbar,
|
|
182
|
+
.editor-toolbar.editor-toolbar,
|
|
183
|
+
.ant-layout-header.editor-toolbar {
|
|
184
|
+
position: absolute;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
129
189
|
|
|
130
190
|
// Responsive design
|
|
131
191
|
@media (max-width: 768px) {
|
|
@@ -231,6 +291,23 @@
|
|
|
231
291
|
}
|
|
232
292
|
}
|
|
233
293
|
|
|
294
|
+
// Fullscreen modal library mode styles (outside .html-editor block since fullscreen is a separate element)
|
|
295
|
+
.html-editor-fullscreen--library-mode {
|
|
296
|
+
.preview-pane {
|
|
297
|
+
max-height: calc(95vh - 5%);
|
|
298
|
+
|
|
299
|
+
&__content {
|
|
300
|
+
height: calc(95vh - 5%); // Same as editor pane - maximize to full available height
|
|
301
|
+
max-height: calc(95vh - 50%);
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
iframe {
|
|
305
|
+
height: calc(95vh - 5%); // Same as editor pane - maximize to full available height
|
|
306
|
+
max-height: calc(95vh - 50%);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
234
311
|
// Animation classes for smooth transitions
|
|
235
312
|
.html-editor-transition {
|
|
236
313
|
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
border-radius: 0.25rem 0.25rem 0 0;
|
|
16
16
|
box-sizing: border-box;
|
|
17
17
|
flex-shrink: 0;
|
|
18
|
+
position: relative;
|
|
18
19
|
|
|
19
20
|
&__left {
|
|
20
21
|
display: flex;
|
|
@@ -110,4 +111,12 @@
|
|
|
110
111
|
display: none;
|
|
111
112
|
}
|
|
112
113
|
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Library mode override for InApp variant - Position absolute for toolbar
|
|
117
|
+
// These selectors target the toolbar when inside library mode InApp variant
|
|
118
|
+
.html-editor--inapp.html-editor--library-mode .html-editor__header .editor-toolbar,
|
|
119
|
+
.html-editor--inapp.html-editor--library-mode .html-editor__header .editor-toolbar.editor-toolbar,
|
|
120
|
+
.html-editor--inapp.html-editor--library-mode .html-editor__header .ant-layout-header.editor-toolbar {
|
|
121
|
+
position: absolute;
|
|
113
122
|
}
|
|
@@ -55,4 +55,25 @@ body .ant-modal-mask+.ant-modal-wrap .ant-modal.html-editor-fullscreen-modal .an
|
|
|
55
55
|
margin-left: auto;
|
|
56
56
|
}
|
|
57
57
|
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Library mode overrides for INAPP variant - Position absolute for toolbar
|
|
61
|
+
// These selectors match the high-specificity patterns above but include library-mode class
|
|
62
|
+
.html-editor-fullscreen--inapp.html-editor-fullscreen--library-mode .html-editor__header--fullscreen .editor-toolbar,
|
|
63
|
+
.html-editor-fullscreen--inapp.html-editor-fullscreen--library-mode .html-editor__header--fullscreen .editor-toolbar.editor-toolbar,
|
|
64
|
+
.html-editor-fullscreen--inapp.html-editor-fullscreen--library-mode .html-editor__header--fullscreen .ant-layout-header.editor-toolbar,
|
|
65
|
+
.html-editor.html-editor-fullscreen--inapp.html-editor-fullscreen--library-mode .html-editor__header--fullscreen .editor-toolbar,
|
|
66
|
+
.html-editor.html-editor-fullscreen--inapp.html-editor-fullscreen--library-mode .html-editor__header--fullscreen .editor-toolbar.editor-toolbar,
|
|
67
|
+
.html-editor.html-editor-fullscreen--inapp.html-editor-fullscreen--library-mode .html-editor__header--fullscreen .ant-layout-header.editor-toolbar,
|
|
68
|
+
.html-editor__header--fullscreen.html-editor-fullscreen--inapp.html-editor-fullscreen--library-mode .editor-toolbar,
|
|
69
|
+
.html-editor__header.html-editor__header--fullscreen.html-editor-fullscreen--inapp.html-editor-fullscreen--library-mode .editor-toolbar,
|
|
70
|
+
.html-editor__header.html-editor__header--fullscreen.html-editor-fullscreen--inapp.html-editor-fullscreen--library-mode .editor-toolbar.editor-toolbar,
|
|
71
|
+
.html-editor__header.html-editor__header--fullscreen.html-editor-fullscreen--inapp.html-editor-fullscreen--library-mode .ant-layout-header.editor-toolbar,
|
|
72
|
+
.ant-modal .html-editor-fullscreen--inapp.html-editor-fullscreen--library-mode .html-editor__header--fullscreen .editor-toolbar,
|
|
73
|
+
.ant-modal .html-editor-fullscreen--inapp.html-editor-fullscreen--library-mode .html-editor__header--fullscreen .editor-toolbar.editor-toolbar,
|
|
74
|
+
.ant-modal .html-editor-fullscreen--inapp.html-editor-fullscreen--library-mode .html-editor__header--fullscreen .ant-layout-header.editor-toolbar,
|
|
75
|
+
.ant-modal-content .html-editor-fullscreen--inapp.html-editor-fullscreen--library-mode .html-editor__header--fullscreen .editor-toolbar,
|
|
76
|
+
.ant-modal-content .html-editor-fullscreen--inapp.html-editor-fullscreen--library-mode .html-editor__header--fullscreen .editor-toolbar.editor-toolbar,
|
|
77
|
+
.ant-modal-content .html-editor-fullscreen--inapp.html-editor-fullscreen--library-mode .html-editor__header--fullscreen .ant-layout-header.editor-toolbar {
|
|
78
|
+
position: absolute;
|
|
58
79
|
}
|
|
@@ -48,33 +48,6 @@ describe('ValidationErrorDisplay', () => {
|
|
|
48
48
|
expect(screen.getByText('Invalid attribute')).toBeInTheDocument();
|
|
49
49
|
});
|
|
50
50
|
|
|
51
|
-
it('renders liquid errors separately', () => {
|
|
52
|
-
const mockValidation = {
|
|
53
|
-
getAllIssues: () => [
|
|
54
|
-
{
|
|
55
|
-
message: 'Invalid liquid syntax', severity: 'error', source: 'liquid-validator', line: 3,
|
|
56
|
-
},
|
|
57
|
-
{
|
|
58
|
-
message: 'HTML error', severity: 'error', source: 'htmlhint', line: 10,
|
|
59
|
-
},
|
|
60
|
-
],
|
|
61
|
-
isClean: () => false,
|
|
62
|
-
isValidating: false,
|
|
63
|
-
};
|
|
64
|
-
|
|
65
|
-
const { container } = render(
|
|
66
|
-
<TestWrapper>
|
|
67
|
-
<ValidationErrorDisplay validation={mockValidation} />
|
|
68
|
-
</TestWrapper>
|
|
69
|
-
);
|
|
70
|
-
|
|
71
|
-
// Check for validation-error-display wrapper
|
|
72
|
-
expect(container.querySelector('.validation-error-display')).toBeInTheDocument();
|
|
73
|
-
// Check that both errors are rendered
|
|
74
|
-
expect(screen.getByText('Invalid liquid syntax')).toBeInTheDocument();
|
|
75
|
-
expect(screen.getByText('HTML error')).toBeInTheDocument();
|
|
76
|
-
});
|
|
77
|
-
|
|
78
51
|
it('does not render when validation has no errors', () => {
|
|
79
52
|
const mockValidation = {
|
|
80
53
|
getAllIssues: () => [],
|
|
@@ -1235,6 +1235,7 @@ export const InApp = (props) => {
|
|
|
1235
1235
|
onContextChange={handleOnTagsContextChange}
|
|
1236
1236
|
// Pass validation errors to HTMLEditor for display
|
|
1237
1237
|
errors={errorMessage}
|
|
1238
|
+
isFullMode={isFullMode}
|
|
1238
1239
|
data-test="inapp-html-editor"
|
|
1239
1240
|
style={{ width: '138%' }}
|
|
1240
1241
|
/>
|
|
@@ -1,239 +0,0 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
import { render, fireEvent, within, screen } from '@testing-library/react';
|
|
3
|
-
import '@testing-library/jest-dom';
|
|
4
|
-
import { IntlProvider } from 'react-intl';
|
|
5
|
-
import { Provider } from 'react-redux';
|
|
6
|
-
import { configureStore } from '@capillarytech/vulcan-react-sdk/utils'
|
|
7
|
-
import { initialReducer } from '../../../initialReducer';
|
|
8
|
-
import history from '../../../utils/history';
|
|
9
|
-
import EmailWrapperView from '../components/EmailWrapperView';
|
|
10
|
-
import { EmailWrapperViewMockProps } from '../mockdata/mockdata';
|
|
11
|
-
import { EMAIL_CREATE_MODES } from '../constants';
|
|
12
|
-
|
|
13
|
-
// Mock pathConfig before history import - must be a string, not undefined
|
|
14
|
-
jest.mock('../../../config/path', () => ({
|
|
15
|
-
publicPath: '/creatives/ui',
|
|
16
|
-
engagePlusPublicPath: '/campaigns/ui',
|
|
17
|
-
}));
|
|
18
|
-
|
|
19
|
-
// Mock window.location for createBrowserHistory
|
|
20
|
-
const originalLocation = window.location;
|
|
21
|
-
beforeAll(() => {
|
|
22
|
-
delete window.location;
|
|
23
|
-
window.location = {
|
|
24
|
-
...originalLocation,
|
|
25
|
-
pathname: '/',
|
|
26
|
-
search: '',
|
|
27
|
-
hash: '',
|
|
28
|
-
href: 'http://localhost/',
|
|
29
|
-
replace: jest.fn(),
|
|
30
|
-
};
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
afterAll(() => {
|
|
34
|
-
window.location = originalLocation;
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
// This mock needs to be before any imports
|
|
38
|
-
jest.mock('../../../v2Containers/Email', () => ({
|
|
39
|
-
__esModule: true,
|
|
40
|
-
default: () => <div data-testid="email-create-container" />,
|
|
41
|
-
}));
|
|
42
|
-
|
|
43
|
-
// Mock react-router
|
|
44
|
-
jest.mock('react-router-dom', () => ({
|
|
45
|
-
...jest.requireActual('react-router-dom'),
|
|
46
|
-
useHistory: () => ({
|
|
47
|
-
push: jest.fn(),
|
|
48
|
-
replace: jest.fn(),
|
|
49
|
-
go: jest.fn(),
|
|
50
|
-
goBack: jest.fn(),
|
|
51
|
-
goForward: jest.fn(),
|
|
52
|
-
location: { search: '' }
|
|
53
|
-
}),
|
|
54
|
-
}));
|
|
55
|
-
|
|
56
|
-
// Mock redux-auth-wrapper
|
|
57
|
-
jest.mock('redux-auth-wrapper/history4/redirect', () => ({
|
|
58
|
-
connectedRouterRedirect: () => (Component) => Component,
|
|
59
|
-
}));
|
|
60
|
-
|
|
61
|
-
// Mock react-router
|
|
62
|
-
jest.mock('connected-react-router', () => ({
|
|
63
|
-
push: jest.fn(),
|
|
64
|
-
replace: jest.fn(),
|
|
65
|
-
go: jest.fn(),
|
|
66
|
-
goBack: jest.fn(),
|
|
67
|
-
goForward: jest.fn(),
|
|
68
|
-
}));
|
|
69
|
-
|
|
70
|
-
// Mock the saga injector
|
|
71
|
-
jest.mock('@capillarytech/vulcan-react-sdk/utils/injectSaga', () => ({
|
|
72
|
-
__esModule: true,
|
|
73
|
-
default: () => (Component) => Component,
|
|
74
|
-
}));
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
const initialState = {
|
|
78
|
-
cap: { loading: false, error: null },
|
|
79
|
-
router: { location: { pathname: '/' } }
|
|
80
|
-
};
|
|
81
|
-
|
|
82
|
-
describe('EmailWrapperView', () => {
|
|
83
|
-
let store;
|
|
84
|
-
|
|
85
|
-
beforeAll(() => {
|
|
86
|
-
// Use the real history (pathConfig is mocked, so it should work)
|
|
87
|
-
store = configureStore(initialState, initialReducer, history);
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
const renderWithProviders = (component) => {
|
|
91
|
-
return render(
|
|
92
|
-
<Provider store={store}>
|
|
93
|
-
<IntlProvider locale="en" messages={{}}>
|
|
94
|
-
{component}
|
|
95
|
-
</IntlProvider>
|
|
96
|
-
</Provider>
|
|
97
|
-
);
|
|
98
|
-
};
|
|
99
|
-
|
|
100
|
-
it('renders mode selection UI when step is modeSelection', () => {
|
|
101
|
-
const props = {
|
|
102
|
-
...EmailWrapperViewMockProps,
|
|
103
|
-
step: 'modeSelection',
|
|
104
|
-
};
|
|
105
|
-
|
|
106
|
-
const { getByText } = renderWithProviders(<EmailWrapperView {...props} />);
|
|
107
|
-
expect(getByText('Create using editor')).toBeInTheDocument();
|
|
108
|
-
expect(getByText('Upload zip file')).toBeInTheDocument();
|
|
109
|
-
});
|
|
110
|
-
|
|
111
|
-
it('shows template name input in full mode', () => {
|
|
112
|
-
const props = {
|
|
113
|
-
...EmailWrapperViewMockProps,
|
|
114
|
-
isFullMode: true,
|
|
115
|
-
step: 'modeSelection',
|
|
116
|
-
};
|
|
117
|
-
|
|
118
|
-
const { container } = renderWithProviders(<EmailWrapperView {...props} />);
|
|
119
|
-
const input = container.querySelector('.ant-input');
|
|
120
|
-
expect(input).toBeInTheDocument();
|
|
121
|
-
expect(input.value).toBe(props.templateName);
|
|
122
|
-
});
|
|
123
|
-
|
|
124
|
-
it('handles template name changes', () => {
|
|
125
|
-
const props = {
|
|
126
|
-
...EmailWrapperViewMockProps,
|
|
127
|
-
isFullMode: true,
|
|
128
|
-
step: 'modeSelection',
|
|
129
|
-
onTemplateNameChange: jest.fn(),
|
|
130
|
-
};
|
|
131
|
-
|
|
132
|
-
const { container } = renderWithProviders(<EmailWrapperView {...props} />);
|
|
133
|
-
const input = container.querySelector('.ant-input');
|
|
134
|
-
expect(input).toBeInTheDocument();
|
|
135
|
-
|
|
136
|
-
// Create a change event and fire it
|
|
137
|
-
fireEvent.change(input, { target: { value: 'New Template Name' } });
|
|
138
|
-
|
|
139
|
-
// Just verify that the function was called
|
|
140
|
-
expect(props.onTemplateNameChange).toHaveBeenCalled();
|
|
141
|
-
});
|
|
142
|
-
|
|
143
|
-
it('shows upload button when in upload mode', () => {
|
|
144
|
-
const props = {
|
|
145
|
-
...EmailWrapperViewMockProps,
|
|
146
|
-
step: 'modeSelection',
|
|
147
|
-
emailCreateMode: EMAIL_CREATE_MODES.UPLOAD,
|
|
148
|
-
uploadButtonLabel: 'Upload',
|
|
149
|
-
};
|
|
150
|
-
|
|
151
|
-
const { getByText, container } = renderWithProviders(<EmailWrapperView {...props} />);
|
|
152
|
-
|
|
153
|
-
// Find the "Upload zip file" section
|
|
154
|
-
const modeSelectionSection = getByText('Upload zip file').closest('div');
|
|
155
|
-
|
|
156
|
-
// Find any button in the upload section
|
|
157
|
-
const uploadButton = container.querySelector('.ant-btn');
|
|
158
|
-
|
|
159
|
-
// Verify that we found an upload button
|
|
160
|
-
expect(uploadButton).toBeInTheDocument();
|
|
161
|
-
expect(uploadButton.textContent).toContain('Upload');
|
|
162
|
-
});
|
|
163
|
-
|
|
164
|
-
it('shows loading spinner when uploading', () => {
|
|
165
|
-
const props = {
|
|
166
|
-
...EmailWrapperViewMockProps,
|
|
167
|
-
isUploading: true,
|
|
168
|
-
emailCreateMode: EMAIL_CREATE_MODES.UPLOAD,
|
|
169
|
-
step: 'modeSelection',
|
|
170
|
-
};
|
|
171
|
-
|
|
172
|
-
const { container } = renderWithProviders(<EmailWrapperView {...props} />);
|
|
173
|
-
const spinner = container.querySelector('.ant-spin');
|
|
174
|
-
expect(spinner).toBeInTheDocument();
|
|
175
|
-
});
|
|
176
|
-
|
|
177
|
-
it('shows content creation UI when not in mode selection', () => {
|
|
178
|
-
const props = {
|
|
179
|
-
...EmailWrapperViewMockProps,
|
|
180
|
-
step: 'contentCreation',
|
|
181
|
-
isShowEmailCreate: true,
|
|
182
|
-
};
|
|
183
|
-
|
|
184
|
-
const { getByTestId } = renderWithProviders(<EmailWrapperView {...props} />);
|
|
185
|
-
expect(getByTestId('email-create-container')).toBeInTheDocument();
|
|
186
|
-
});
|
|
187
|
-
|
|
188
|
-
it('disables upload button when template name is empty in full mode', () => {
|
|
189
|
-
const props = {
|
|
190
|
-
...EmailWrapperViewMockProps,
|
|
191
|
-
isFullMode: true,
|
|
192
|
-
step: 'modeSelection',
|
|
193
|
-
emailCreateMode: EMAIL_CREATE_MODES.UPLOAD,
|
|
194
|
-
templateName: '',
|
|
195
|
-
isTemplateNameEmpty: true,
|
|
196
|
-
uploadButtonLabel: 'Upload',
|
|
197
|
-
};
|
|
198
|
-
|
|
199
|
-
const { getByText, container } = renderWithProviders(<EmailWrapperView {...props} />);
|
|
200
|
-
|
|
201
|
-
// Find the Upload section
|
|
202
|
-
getByText('Upload zip file');
|
|
203
|
-
|
|
204
|
-
// Find button in the container
|
|
205
|
-
const uploadButton = container.querySelector('.ant-btn');
|
|
206
|
-
|
|
207
|
-
// Check if the button is disabled
|
|
208
|
-
expect(uploadButton).toBeDisabled();
|
|
209
|
-
});
|
|
210
|
-
|
|
211
|
-
it('shows error message when template name is empty during upload', () => {
|
|
212
|
-
const props = {
|
|
213
|
-
...EmailWrapperViewMockProps,
|
|
214
|
-
isFullMode: true,
|
|
215
|
-
step: 'modeSelection',
|
|
216
|
-
emailCreateMode: EMAIL_CREATE_MODES.UPLOAD,
|
|
217
|
-
templateName: '',
|
|
218
|
-
isTemplateNameEmpty: true,
|
|
219
|
-
// Add errorMessage directly to the props
|
|
220
|
-
errorMessage: 'Please enter template name',
|
|
221
|
-
intl: {
|
|
222
|
-
formatMessage: () => 'Please enter template name',
|
|
223
|
-
},
|
|
224
|
-
};
|
|
225
|
-
|
|
226
|
-
const { container } = renderWithProviders(<EmailWrapperView {...props} />);
|
|
227
|
-
|
|
228
|
-
// Look for error feedback in the Ant Design form
|
|
229
|
-
const errorElement = container.querySelector('.ant-form-item-explain');
|
|
230
|
-
|
|
231
|
-
if (errorElement) {
|
|
232
|
-
expect(errorElement.textContent).toContain('Please enter template name');
|
|
233
|
-
} else {
|
|
234
|
-
// If the standard error element is not found, look for any element containing the error text
|
|
235
|
-
const allText = container.textContent;
|
|
236
|
-
expect(allText).toContain('Please enter template name');
|
|
237
|
-
}
|
|
238
|
-
});
|
|
239
|
-
});
|