@eeacms/volto-eea-website-theme 1.22.1 → 1.24.0
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/CHANGELOG.md +42 -10
- package/RELEASE.md +14 -14
- package/cypress.config.js +3 -3
- package/jest-addon.config.js +3 -0
- package/package.json +4 -3
- package/src/components/manage/Blocks/Title/schema.js +40 -1
- package/src/components/theme/Banner/View.jsx +2 -1
- package/src/config.js +27 -15
- package/src/customizations/@plone/volto-slate/editor/extensions/normalizeExternalData.js +9 -0
- package/src/customizations/volto/components/manage/Blocks/Image/Edit.jsx +9 -6
- package/src/customizations/volto/components/manage/Blocks/Image/Edit.test.jsx +312 -0
- package/src/customizations/volto/components/manage/Blocks/Image/View.jsx +9 -6
- package/src/customizations/volto/components/manage/Blocks/LeadImage/Edit.jsx +8 -4
- package/src/customizations/volto/components/manage/Blocks/LeadImage/View.jsx +8 -5
- package/src/customizations/volto/components/manage/Blocks/LeadImage/schema.js +0 -1
- package/src/customizations/volto/components/manage/Form/Form.test.jsx +1124 -0
- package/src/customizations/volto/components/manage/Widgets/ObjectBrowserWidget.test.jsx +193 -0
- package/src/customizations/volto/components/theme/Comments/Comments.test.jsx +407 -0
- package/src/customizations/volto/components/theme/Header/Header.jsx +53 -50
- package/src/customizations/volto/components/theme/Header/Header.test.jsx +326 -0
- package/src/helpers/schema-utils.js +40 -34
- package/src/index.js +122 -15
- package/src/index.test.js +343 -0
- package/src/customizations/@eeacms/volto-tabs-block/components/templates/default/schema.js +0 -109
- package/src/customizations/@eeacms/volto-tabs-block/components/templates/horizontal-responsive/schema.js +0 -109
@@ -92,21 +92,22 @@ const EEAHeader = ({ pathname, token, items, history, subsite }) => {
|
|
92
92
|
{isHomePageInverse && <BodyClass className="homepage" />}
|
93
93
|
<Header.TopHeader>
|
94
94
|
<Header.TopItem className="official-union">
|
95
|
-
<Image src={eeaFlag} alt="
|
95
|
+
<Image src={eeaFlag} alt="European Union flag"></Image>
|
96
96
|
<Header.TopDropdownMenu
|
97
97
|
text="An official website of the European Union | How do you know?"
|
98
98
|
tabletText="EEA information systems"
|
99
|
-
mobileText=" "
|
99
|
+
mobileText="EEA information systems"
|
100
100
|
icon="chevron down"
|
101
101
|
aria-label="dropdown"
|
102
|
-
|
102
|
+
classNameHeader="mobile-sr-only"
|
103
103
|
viewportWidth={width}
|
104
104
|
>
|
105
|
-
{/* eslint-disable-next-line jsx-a11y/no-static-element-interactions */}
|
106
105
|
<div
|
107
106
|
className="content"
|
108
107
|
onClick={(evt) => evt.stopPropagation()}
|
109
108
|
onKeyDown={(evt) => evt.stopPropagation()}
|
109
|
+
tabIndex={0}
|
110
|
+
role={'presentation'}
|
110
111
|
>
|
111
112
|
<p>
|
112
113
|
All official European Union website addresses are in the{' '}
|
@@ -129,9 +130,10 @@ const EEAHeader = ({ pathname, token, items, history, subsite }) => {
|
|
129
130
|
<Header.TopDropdownMenu
|
130
131
|
id="theme-sites"
|
131
132
|
text={headerOpts.partnerLinks.title}
|
133
|
+
aria-label={headerOpts.partnerLinks.title}
|
132
134
|
viewportWidth={width}
|
133
135
|
>
|
134
|
-
<div className="wrapper">
|
136
|
+
<div className="wrapper" tabIndex={0} role={'presentation'}>
|
135
137
|
{headerOpts.partnerLinks.links.map((item, index) => (
|
136
138
|
<Dropdown.Item key={index}>
|
137
139
|
<a
|
@@ -150,53 +152,54 @@ const EEAHeader = ({ pathname, token, items, history, subsite }) => {
|
|
150
152
|
</Header.TopItem>
|
151
153
|
)}
|
152
154
|
|
153
|
-
{config.settings.isMultilingual &&
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
<ul
|
169
|
-
className="wrapper language-list"
|
170
|
-
role="listbox"
|
171
|
-
aria-label="language switcher"
|
155
|
+
{config.settings.isMultilingual &&
|
156
|
+
config.settings.supportedLanguages.length > 1 &&
|
157
|
+
config.settings.hasLanguageDropdown && (
|
158
|
+
<Header.TopDropdownMenu
|
159
|
+
id="language-switcher"
|
160
|
+
className="item"
|
161
|
+
text={`${language.toUpperCase()}`}
|
162
|
+
mobileText={`${language.toUpperCase()}`}
|
163
|
+
icon={
|
164
|
+
<Image
|
165
|
+
src={globeIcon}
|
166
|
+
alt="language dropdown globe icon"
|
167
|
+
></Image>
|
168
|
+
}
|
169
|
+
viewportWidth={width}
|
172
170
|
>
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
171
|
+
<ul
|
172
|
+
className="wrapper language-list"
|
173
|
+
role="listbox"
|
174
|
+
aria-label="language switcher"
|
175
|
+
>
|
176
|
+
{eea.languages.map((item, index) => (
|
177
|
+
<Dropdown.Item
|
178
|
+
as="li"
|
179
|
+
key={index}
|
180
|
+
text={
|
181
|
+
<span>
|
182
|
+
{item.name}
|
183
|
+
<span className="country-code">
|
184
|
+
{item.code.toUpperCase()}
|
185
|
+
</span>
|
182
186
|
</span>
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
</
|
198
|
-
|
199
|
-
)}
|
187
|
+
}
|
188
|
+
onClick={() => {
|
189
|
+
const translation = find(translations, {
|
190
|
+
language: item.code,
|
191
|
+
});
|
192
|
+
const to = translation
|
193
|
+
? flattenToAppURL(translation['@id'])
|
194
|
+
: `/${item.code}`;
|
195
|
+
setLanguage(item.code);
|
196
|
+
history.push(to);
|
197
|
+
}}
|
198
|
+
></Dropdown.Item>
|
199
|
+
))}
|
200
|
+
</ul>
|
201
|
+
</Header.TopDropdownMenu>
|
202
|
+
)}
|
200
203
|
</Header.TopHeader>
|
201
204
|
<Header.Main
|
202
205
|
pathname={pathname}
|
@@ -0,0 +1,326 @@
|
|
1
|
+
import React from 'react';
|
2
|
+
import renderer from 'react-test-renderer';
|
3
|
+
import { render, fireEvent } from '@testing-library/react';
|
4
|
+
import configureStore from 'redux-mock-store';
|
5
|
+
import { Router } from 'react-router-dom';
|
6
|
+
import { createMemoryHistory } from 'history';
|
7
|
+
import { Provider } from 'react-intl-redux';
|
8
|
+
import config from '@plone/volto/registry';
|
9
|
+
|
10
|
+
import Header from './Header';
|
11
|
+
|
12
|
+
const mockStore = configureStore();
|
13
|
+
let history = createMemoryHistory();
|
14
|
+
|
15
|
+
describe('Header', () => {
|
16
|
+
it('renders a header component', () => {
|
17
|
+
const store = mockStore({
|
18
|
+
userSession: { token: null },
|
19
|
+
intl: {
|
20
|
+
locale: 'en',
|
21
|
+
messages: {},
|
22
|
+
},
|
23
|
+
navigation: {
|
24
|
+
items: ['en'],
|
25
|
+
},
|
26
|
+
content: {
|
27
|
+
data: {
|
28
|
+
layout: 'homepage_inverse_view',
|
29
|
+
},
|
30
|
+
},
|
31
|
+
router: {
|
32
|
+
location: {
|
33
|
+
pathname: '/home/',
|
34
|
+
},
|
35
|
+
},
|
36
|
+
});
|
37
|
+
|
38
|
+
config.settings = {
|
39
|
+
...config.settings,
|
40
|
+
eea: {
|
41
|
+
headerOpts: undefined,
|
42
|
+
},
|
43
|
+
};
|
44
|
+
|
45
|
+
const component = renderer.create(
|
46
|
+
<Provider store={store}>
|
47
|
+
<Router history={history}>
|
48
|
+
<Header pathname="/home" />
|
49
|
+
</Router>
|
50
|
+
</Provider>,
|
51
|
+
);
|
52
|
+
const json = component.toJSON();
|
53
|
+
expect(json).toMatchSnapshot();
|
54
|
+
});
|
55
|
+
|
56
|
+
it('renders a header component', () => {
|
57
|
+
const store = mockStore({
|
58
|
+
userSession: { token: null },
|
59
|
+
intl: {
|
60
|
+
locale: 'en',
|
61
|
+
messages: {},
|
62
|
+
},
|
63
|
+
navigation: {
|
64
|
+
items: ['en'],
|
65
|
+
},
|
66
|
+
content: {
|
67
|
+
data: {
|
68
|
+
layout: 'homepage_inverse_view',
|
69
|
+
},
|
70
|
+
},
|
71
|
+
router: {
|
72
|
+
location: {
|
73
|
+
pathname: '/home/',
|
74
|
+
},
|
75
|
+
},
|
76
|
+
});
|
77
|
+
|
78
|
+
config.settings = {
|
79
|
+
...config.settings,
|
80
|
+
eea: {
|
81
|
+
headerOpts: {},
|
82
|
+
},
|
83
|
+
};
|
84
|
+
|
85
|
+
const component = renderer.create(
|
86
|
+
<Provider store={store}>
|
87
|
+
<Router history={history}>
|
88
|
+
<Header pathname="/blog" />
|
89
|
+
</Router>
|
90
|
+
</Provider>,
|
91
|
+
);
|
92
|
+
const json = component.toJSON();
|
93
|
+
expect(json).toMatchSnapshot();
|
94
|
+
});
|
95
|
+
|
96
|
+
it('renders a header component', () => {
|
97
|
+
const store = mockStore({
|
98
|
+
userSession: { token: null },
|
99
|
+
intl: {
|
100
|
+
locale: undefined,
|
101
|
+
messages: {},
|
102
|
+
},
|
103
|
+
navigation: {
|
104
|
+
items: ['en'],
|
105
|
+
},
|
106
|
+
content: {
|
107
|
+
data: {
|
108
|
+
layout: 'homepage_view',
|
109
|
+
'@components': {
|
110
|
+
translations: {
|
111
|
+
items: [{ language: 'en' }, { language: 'ro' }],
|
112
|
+
},
|
113
|
+
},
|
114
|
+
},
|
115
|
+
},
|
116
|
+
router: {
|
117
|
+
location: {
|
118
|
+
pathname: '/home/',
|
119
|
+
},
|
120
|
+
},
|
121
|
+
});
|
122
|
+
|
123
|
+
config.settings = {
|
124
|
+
...config.settings,
|
125
|
+
eea: {
|
126
|
+
headerOpts: {
|
127
|
+
partnerLinks: {
|
128
|
+
links: [{ href: '/link1', title: 'link 1' }],
|
129
|
+
},
|
130
|
+
},
|
131
|
+
defaultLanguage: 'en',
|
132
|
+
languages: [{ code: 'en' }, { code: 'ro' }],
|
133
|
+
},
|
134
|
+
isMultilingual: true,
|
135
|
+
supportedLanguages: ['en', 'ro'],
|
136
|
+
hasLanguageDropdown: true,
|
137
|
+
};
|
138
|
+
|
139
|
+
const { container, rerender } = render(
|
140
|
+
<Provider store={store}>
|
141
|
+
<Router history={history}>
|
142
|
+
<Header pathname="/blog" />
|
143
|
+
</Router>
|
144
|
+
</Provider>,
|
145
|
+
);
|
146
|
+
|
147
|
+
fireEvent.click(container.querySelector('.content'));
|
148
|
+
fireEvent.keyDown(container.querySelector('.content'), { keyCode: 37 });
|
149
|
+
fireEvent.keyDown(container.querySelector('.content a'), { keyCode: 37 });
|
150
|
+
fireEvent.keyDown(container.querySelector('a[href="/link1"]'), {
|
151
|
+
keyCode: 37,
|
152
|
+
});
|
153
|
+
fireEvent.click(container.querySelector('.country-code'));
|
154
|
+
|
155
|
+
// expect(getByText('da')).toBeInTheDocument();
|
156
|
+
|
157
|
+
rerender(
|
158
|
+
<Provider store={{ ...store, userSession: { token: '1234' } }}>
|
159
|
+
<Router history={history}>
|
160
|
+
<Header pathname="/blog" />
|
161
|
+
</Router>
|
162
|
+
</Provider>,
|
163
|
+
);
|
164
|
+
});
|
165
|
+
|
166
|
+
it('renders a header component', () => {
|
167
|
+
const store = mockStore({
|
168
|
+
userSession: { token: null },
|
169
|
+
intl: {
|
170
|
+
locale: undefined,
|
171
|
+
messages: {},
|
172
|
+
},
|
173
|
+
navigation: {
|
174
|
+
items: ['en'],
|
175
|
+
},
|
176
|
+
content: {
|
177
|
+
data: {
|
178
|
+
layout: 'homepage_view',
|
179
|
+
'@components': {
|
180
|
+
subsite: {
|
181
|
+
'@type': 'Subsite',
|
182
|
+
title: 'Home Page',
|
183
|
+
subsite_logo: {
|
184
|
+
scales: {
|
185
|
+
mini: {
|
186
|
+
download:
|
187
|
+
'http://localhost:8080/Plone/subsite_logo/@@images/image/mini',
|
188
|
+
},
|
189
|
+
},
|
190
|
+
},
|
191
|
+
},
|
192
|
+
translations: {
|
193
|
+
items: [{ language: 'ro' }],
|
194
|
+
},
|
195
|
+
},
|
196
|
+
},
|
197
|
+
},
|
198
|
+
router: {
|
199
|
+
location: {
|
200
|
+
pathname: '/home/',
|
201
|
+
},
|
202
|
+
},
|
203
|
+
});
|
204
|
+
|
205
|
+
config.settings = {
|
206
|
+
...config.settings,
|
207
|
+
eea: {
|
208
|
+
headerOpts: {
|
209
|
+
partnerLinks: {
|
210
|
+
links: [{ href: '/link1', title: 'link 1' }],
|
211
|
+
},
|
212
|
+
},
|
213
|
+
defaultLanguage: 'en',
|
214
|
+
languages: [{ code: 'en' }, { code: 'ro' }],
|
215
|
+
},
|
216
|
+
isMultilingual: true,
|
217
|
+
supportedLanguages: ['en', 'ro'],
|
218
|
+
hasLanguageDropdown: true,
|
219
|
+
};
|
220
|
+
|
221
|
+
const { container, rerender } = render(
|
222
|
+
<Provider store={store}>
|
223
|
+
<Router history={history}>
|
224
|
+
<Header pathname="/blog" />
|
225
|
+
</Router>
|
226
|
+
</Provider>,
|
227
|
+
);
|
228
|
+
|
229
|
+
fireEvent.click(container.querySelector('.content'));
|
230
|
+
fireEvent.keyDown(container.querySelector('.content'), { keyCode: 37 });
|
231
|
+
fireEvent.keyDown(container.querySelector('.content a'), { keyCode: 37 });
|
232
|
+
fireEvent.keyDown(container.querySelector('a[href="/link1"]'), {
|
233
|
+
keyCode: 37,
|
234
|
+
});
|
235
|
+
fireEvent.click(container.querySelector('.country-code'));
|
236
|
+
|
237
|
+
// expect(getByText('da')).toBeInTheDocument();
|
238
|
+
|
239
|
+
rerender(
|
240
|
+
<Provider store={{ ...store, userSession: { token: '1234' } }}>
|
241
|
+
<Router history={history}>
|
242
|
+
<Header pathname="/blog" />
|
243
|
+
</Router>
|
244
|
+
</Provider>,
|
245
|
+
);
|
246
|
+
});
|
247
|
+
|
248
|
+
it('renders a header component', () => {
|
249
|
+
const store = mockStore({
|
250
|
+
userSession: { token: null },
|
251
|
+
intl: {
|
252
|
+
locale: undefined,
|
253
|
+
messages: {},
|
254
|
+
},
|
255
|
+
navigation: {
|
256
|
+
items: [
|
257
|
+
{ url: '/test1', title: 'test 1', nav_title: 'Test 1', items: [] },
|
258
|
+
{ url: undefined, title: 'test 2', items: [] },
|
259
|
+
],
|
260
|
+
},
|
261
|
+
content: {
|
262
|
+
data: {
|
263
|
+
layout: 'homepage_view',
|
264
|
+
'@components': {
|
265
|
+
subsite: {
|
266
|
+
'@type': 'Subsite',
|
267
|
+
title: 'Home Page',
|
268
|
+
subsite_logo: undefined,
|
269
|
+
},
|
270
|
+
translations: {
|
271
|
+
items: [{ language: 'ro' }],
|
272
|
+
},
|
273
|
+
},
|
274
|
+
},
|
275
|
+
},
|
276
|
+
router: {
|
277
|
+
location: {
|
278
|
+
pathname: '/home/',
|
279
|
+
},
|
280
|
+
},
|
281
|
+
});
|
282
|
+
|
283
|
+
config.settings = {
|
284
|
+
...config.settings,
|
285
|
+
eea: {
|
286
|
+
headerOpts: {
|
287
|
+
partnerLinks: {
|
288
|
+
links: [{ href: '/link1', title: 'link 1' }],
|
289
|
+
},
|
290
|
+
},
|
291
|
+
defaultLanguage: 'en',
|
292
|
+
languages: [{ code: 'en' }, { code: 'ro' }],
|
293
|
+
},
|
294
|
+
isMultilingual: true,
|
295
|
+
supportedLanguages: ['en', 'ro'],
|
296
|
+
hasLanguageDropdown: true,
|
297
|
+
};
|
298
|
+
|
299
|
+
const { container, rerender } = render(
|
300
|
+
<Provider store={store}>
|
301
|
+
<Router history={history}>
|
302
|
+
<Header pathname="/blog" />
|
303
|
+
</Router>
|
304
|
+
</Provider>,
|
305
|
+
);
|
306
|
+
|
307
|
+
fireEvent.click(container.querySelector('.content'));
|
308
|
+
fireEvent.keyDown(container.querySelector('.content'), { keyCode: 37 });
|
309
|
+
fireEvent.keyDown(container.querySelector('.content a'), { keyCode: 37 });
|
310
|
+
fireEvent.keyDown(container.querySelector('a[href="/link1"]'), {
|
311
|
+
keyCode: 37,
|
312
|
+
});
|
313
|
+
fireEvent.click(container.querySelector('.country-code'));
|
314
|
+
fireEvent.click(container.querySelector('a[href="/test1"]'));
|
315
|
+
|
316
|
+
// expect(getByText('da')).toBeInTheDocument();
|
317
|
+
|
318
|
+
rerender(
|
319
|
+
<Provider store={{ ...store, userSession: { token: '1234' } }}>
|
320
|
+
<Router history={history}>
|
321
|
+
<Header pathname="/blog" />
|
322
|
+
</Router>
|
323
|
+
</Provider>,
|
324
|
+
);
|
325
|
+
});
|
326
|
+
});
|
@@ -1,8 +1,11 @@
|
|
1
|
-
import { cloneDeep } from 'lodash';
|
2
1
|
import imageNarrowSVG from '@eeacms/volto-eea-website-theme/icons/image-narrow.svg';
|
3
2
|
import imageFitSVG from '@plone/volto/icons/image-fit.svg';
|
4
3
|
import imageWideSVG from '@plone/volto/icons/image-wide.svg';
|
5
4
|
import imageFullSVG from '@plone/volto/icons/image-full.svg';
|
5
|
+
import alignTopSVG from '@plone/volto/icons/move-up.svg';
|
6
|
+
import alignCenterSVG from '@plone/volto/icons/row.svg';
|
7
|
+
import alignBottomSVG from '@plone/volto/icons/move-down.svg';
|
8
|
+
import { addStyling } from '@plone/volto/helpers';
|
6
9
|
|
7
10
|
export const ALIGN_INFO_MAP = {
|
8
11
|
narrow_width: [imageNarrowSVG, 'Narrow width'],
|
@@ -10,45 +13,48 @@ export const ALIGN_INFO_MAP = {
|
|
10
13
|
wide_width: [imageWideSVG, 'Wide width'],
|
11
14
|
full: [imageFullSVG, 'Full width'],
|
12
15
|
};
|
16
|
+
const ALIGN_INFO_MAP_IMAGE_POSITION = {
|
17
|
+
'has--object-position--top': [alignTopSVG, 'Top'],
|
18
|
+
'has--object-position--center': [alignCenterSVG, 'Center'],
|
19
|
+
'has--object-position--bottom': [alignBottomSVG, 'Bottom'],
|
20
|
+
};
|
13
21
|
|
14
|
-
export const addStylingFieldsetSchemaEnhancer = (
|
15
|
-
const
|
22
|
+
export const addStylingFieldsetSchemaEnhancer = (props) => {
|
23
|
+
const schema = addStyling(props);
|
24
|
+
schema.properties.styles.schema.properties.size = {
|
25
|
+
widget: 'style_align',
|
26
|
+
title: 'Section size',
|
27
|
+
actions: Object.keys(ALIGN_INFO_MAP),
|
28
|
+
actionsInfoMap: ALIGN_INFO_MAP,
|
29
|
+
};
|
16
30
|
|
17
|
-
|
18
|
-
|
31
|
+
schema.properties.styles.schema.fieldsets[0].fields = [
|
32
|
+
...schema.properties.styles.schema.fieldsets[0].fields,
|
33
|
+
'size',
|
34
|
+
];
|
19
35
|
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
widget: 'style_align',
|
39
|
-
title: 'Section size',
|
40
|
-
actions: Object.keys(ALIGN_INFO_MAP),
|
41
|
-
actionsInfoMap: ALIGN_INFO_MAP,
|
42
|
-
},
|
43
|
-
},
|
44
|
-
required: [],
|
45
|
-
},
|
46
|
-
};
|
47
|
-
return resSchema;
|
48
|
-
}
|
36
|
+
return schema;
|
37
|
+
};
|
38
|
+
|
39
|
+
export const addStylingFieldsetSchemaEnhancerImagePosition = (props) => {
|
40
|
+
const schema = addStyling(props);
|
41
|
+
|
42
|
+
schema.properties.styles.schema.properties.objectPosition = {
|
43
|
+
title: 'Image position',
|
44
|
+
widget: 'style_align',
|
45
|
+
actions: Object.keys(ALIGN_INFO_MAP_IMAGE_POSITION),
|
46
|
+
actionsInfoMap: ALIGN_INFO_MAP_IMAGE_POSITION,
|
47
|
+
defaultValue: 'has--object-position--center',
|
48
|
+
};
|
49
|
+
|
50
|
+
schema.properties.styles.schema.fieldsets[0].fields = [
|
51
|
+
...schema.properties.styles.schema.fieldsets[0].fields,
|
52
|
+
'objectPosition',
|
53
|
+
];
|
49
54
|
|
50
55
|
return schema;
|
51
56
|
};
|
57
|
+
|
52
58
|
export const getVoltoStyles = (props) => {
|
53
59
|
// return an object with same key and value for cx class setting
|
54
60
|
const styles = props ? props : {};
|