@eeacms/volto-eea-website-theme 4.0.1 → 4.0.3

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 CHANGED
@@ -4,12 +4,19 @@ All notable changes to this project will be documented in this file. Dates are d
4
4
 
5
5
  Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
6
6
 
7
+ ### [4.0.3](https://github.com/eea/volto-eea-website-theme/compare/4.0.2...4.0.3) - 8 April 2026
8
+
9
+ #### :bug: Bug Fixes
10
+
11
+ - fix: crash in AlternateHrefLangs when content.language is undefined [Alin Voinea - [`0305bf2`](https://github.com/eea/volto-eea-website-theme/commit/0305bf2f0ba9f3e0167b4a798a40b81804a7b71f)]
12
+
13
+ ### [4.0.2](https://github.com/eea/volto-eea-website-theme/compare/4.0.1...4.0.2) - 8 April 2026
14
+
7
15
  ### [4.0.1](https://github.com/eea/volto-eea-website-theme/compare/4.0.0...4.0.1) - 8 April 2026
8
16
 
9
17
  #### :hammer_and_wrench: Others
10
18
 
11
19
  - Add eslint-disable [Miu Razvan - [`4566fb4`](https://github.com/eea/volto-eea-website-theme/commit/4566fb419fe531ff1dc077859e217e01164313ae)]
12
- - Import routes from @root instead of @plone/volto [Miu Razvan - [`bc239e7`](https://github.com/eea/volto-eea-website-theme/commit/bc239e7a94b5b66c33ab2f6d1321fb7ebc5348ce)]
13
20
  ## [4.0.0](https://github.com/eea/volto-eea-website-theme/compare/3.19.1...4.0.0) - 7 April 2026
14
21
 
15
22
  #### :rocket: New Features
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@eeacms/volto-eea-website-theme",
3
- "version": "4.0.1",
3
+ "version": "4.0.3",
4
4
  "description": "@eeacms/volto-eea-website-theme: Volto add-on",
5
5
  "main": "src/index.js",
6
6
  "author": "European Environment Agency: IDM2 A-Team",
@@ -1,5 +1,5 @@
1
1
  import { flattenToAppURL } from '@plone/volto/helpers/Url/Url';
2
- import { GET_NAVIGATION_SETTINGS } from '../constants/ActionTypes';
2
+ import { GET_NAVIGATION_SETTINGS } from '@eeacms/volto-eea-website-theme/constants/ActionTypes';
3
3
 
4
4
  export const getNavigationSettings = (url = '') => {
5
5
  let cleanedUrl = typeof url === 'string' ? url : '';
@@ -1,5 +1,5 @@
1
1
  import { getNavigationSettings } from './navigation';
2
- import { GET_NAVIGATION_SETTINGS } from '../constants/ActionTypes';
2
+ import { GET_NAVIGATION_SETTINGS } from '@eeacms/volto-eea-website-theme/constants/ActionTypes';
3
3
 
4
4
  jest.mock('@plone/volto/helpers', () => ({
5
5
  flattenToAppURL: jest.fn((url) => {
@@ -13,7 +13,6 @@ import {
13
13
  } from '@plone/volto-slate/editor/render';
14
14
  import { Node } from 'slate';
15
15
 
16
- // TODO: loading LESS files with `volto-slate/...` paths does not work currently
17
16
  import '../../editor/plugins/Table/less/public.less';
18
17
 
19
18
  /**
@@ -0,0 +1,36 @@
1
+ /**
2
+ * PATCH from Volto 18.33.0
3
+ * To be removed when issue is fixed and released:
4
+ * https://github.com/plone/volto/issues/7309
5
+ * https://github.com/plone/volto/pull/8093
6
+ * https://github.com/plone/volto/pull/8094
7
+ * */
8
+ import config from '@plone/volto/registry';
9
+ import Helmet from '@plone/volto/helpers/Helmet/Helmet';
10
+ import { flattenToAppURL, toPublicURL } from '@plone/volto/helpers/Url/Url';
11
+
12
+ const AlternateHrefLangs = (props) => {
13
+ const { content } = props;
14
+ return (
15
+ <Helmet>
16
+ {config.settings.isMultilingual &&
17
+ content['@components']?.translations?.items &&
18
+ content.language?.token &&
19
+ [
20
+ ...content['@components']?.translations?.items,
21
+ { '@id': content['@id'], language: content.language.token },
22
+ ].map((item, key) => {
23
+ return (
24
+ <link
25
+ key={key}
26
+ rel="alternate"
27
+ hrefLang={item.language}
28
+ href={toPublicURL(flattenToAppURL(item['@id']))}
29
+ />
30
+ );
31
+ })}
32
+ </Helmet>
33
+ );
34
+ };
35
+
36
+ export { AlternateHrefLangs };
@@ -0,0 +1,223 @@
1
+ import React from 'react';
2
+ import Helmet from '@plone/volto/helpers/Helmet/Helmet';
3
+
4
+ import renderer from 'react-test-renderer';
5
+ import configureStore from 'redux-mock-store';
6
+ import { Provider } from 'react-intl-redux';
7
+ import config from '@plone/volto/registry';
8
+
9
+ import { AlternateHrefLangs } from './AlternateHrefLangs';
10
+
11
+ const mockStore = configureStore();
12
+
13
+ describe('AlternateHrefLangs', () => {
14
+ beforeEach(() => {});
15
+ it('non multilingual site, renders nothing', () => {
16
+ config.settings.isMultilingual = false;
17
+ const content = {
18
+ '@id': '/',
19
+ '@components': {},
20
+ };
21
+ const store = mockStore({
22
+ intl: {
23
+ locale: 'en',
24
+ messages: {},
25
+ },
26
+ });
27
+ // We need to force the component rendering
28
+ // to fill the Helmet
29
+ renderer.create(
30
+ <Provider store={store}>
31
+ <AlternateHrefLangs content={content} />
32
+ </Provider>,
33
+ );
34
+
35
+ const helmetLinks = Helmet.peek().linkTags;
36
+ expect(helmetLinks.length).toBe(0);
37
+ });
38
+
39
+ it('multilingual site, content without language field, renders nothing', () => {
40
+ config.settings.publicURL = 'https://plone.org';
41
+ config.settings.supportedLanguages = ['en', 'es'];
42
+
43
+ const content = {
44
+ '@id': 'http://localhost:8080/Plone/en/newsroom/news',
45
+ '@components': {
46
+ translations: {
47
+ items: [{ '@id': 'http://localhost:8080/Plone/es', language: 'es' }],
48
+ },
49
+ },
50
+ };
51
+
52
+ const store = mockStore({
53
+ intl: {
54
+ locale: 'en',
55
+ messages: {},
56
+ },
57
+ });
58
+
59
+ renderer.create(
60
+ <Provider store={store}>
61
+ <AlternateHrefLangs content={content} />
62
+ </Provider>,
63
+ );
64
+
65
+ const helmetLinks = Helmet.peek().linkTags;
66
+ expect(helmetLinks.length).toBe(0);
67
+ });
68
+
69
+ it('multilingual site, with some translations', () => {
70
+ config.settings.publicURL = 'https://plone.org';
71
+ config.settings.isMultilingual = true;
72
+ config.settings.supportedLanguages = ['en', 'es', 'eu'];
73
+
74
+ const content = {
75
+ '@id': 'http://localhost:8080/Plone/en',
76
+ language: { token: 'en', title: 'English' },
77
+ '@components': {
78
+ translations: {
79
+ items: [{ '@id': 'http://localhost:8080/Plone/es', language: 'es' }],
80
+ },
81
+ },
82
+ };
83
+
84
+ const store = mockStore({
85
+ intl: {
86
+ locale: 'en',
87
+ messages: {},
88
+ },
89
+ });
90
+
91
+ // We need to force the component rendering
92
+ // to fill the Helmet
93
+ renderer.create(
94
+ <Provider store={store}>
95
+ <>
96
+ <AlternateHrefLangs content={content} />
97
+ </>
98
+ </Provider>,
99
+ );
100
+ const helmetLinks = Helmet.peek().linkTags;
101
+
102
+ expect(helmetLinks.length).toBe(2);
103
+
104
+ expect(helmetLinks).toContainEqual({
105
+ rel: 'alternate',
106
+ href: 'https://plone.org/es',
107
+ hrefLang: 'es',
108
+ });
109
+ expect(helmetLinks).toContainEqual({
110
+ rel: 'alternate',
111
+ href: 'https://plone.org/en',
112
+ hrefLang: 'en',
113
+ });
114
+ });
115
+
116
+ it('multilingual site, with all available translations', () => {
117
+ config.settings.publicURL = 'https://plone.org';
118
+ config.settings.isMultilingual = true;
119
+ config.settings.supportedLanguages = ['en', 'es', 'eu'];
120
+ const store = mockStore({
121
+ intl: {
122
+ locale: 'en',
123
+ messages: {},
124
+ },
125
+ });
126
+
127
+ const content = {
128
+ '@id': 'http://localhost:8080/Plone/en',
129
+ language: { token: 'en', title: 'English' },
130
+ '@components': {
131
+ translations: {
132
+ items: [
133
+ { '@id': 'http://localhost:8080/Plone/eu', language: 'eu' },
134
+ { '@id': 'http://localhost:8080/Plone/es', language: 'es' },
135
+ ],
136
+ },
137
+ },
138
+ };
139
+
140
+ // We need to force the component rendering
141
+ // to fill the Helmet
142
+ renderer.create(
143
+ <Provider store={store}>
144
+ <AlternateHrefLangs content={content} />
145
+ </Provider>,
146
+ );
147
+
148
+ const helmetLinks = Helmet.peek().linkTags;
149
+
150
+ // We expect having 3 links
151
+ expect(helmetLinks.length).toBe(3);
152
+
153
+ expect(helmetLinks).toContainEqual({
154
+ rel: 'alternate',
155
+ href: 'https://plone.org/eu',
156
+ hrefLang: 'eu',
157
+ });
158
+ expect(helmetLinks).toContainEqual({
159
+ rel: 'alternate',
160
+ href: 'https://plone.org/es',
161
+ hrefLang: 'es',
162
+ });
163
+ expect(helmetLinks).toContainEqual({
164
+ rel: 'alternate',
165
+ href: 'https://plone.org/en',
166
+ hrefLang: 'en',
167
+ });
168
+ });
169
+
170
+ it('multilingual site, with all available translations - with server URL', () => {
171
+ config.settings.publicURL = 'https://plone.org';
172
+ config.settings.isMultilingual = true;
173
+ config.settings.supportedLanguages = ['en', 'es', 'eu'];
174
+ const store = mockStore({
175
+ intl: {
176
+ locale: 'en',
177
+ messages: {},
178
+ },
179
+ });
180
+
181
+ const content = {
182
+ '@id': 'http://localhost:8080/Plone/en',
183
+ language: { token: 'en', title: 'English' },
184
+ '@components': {
185
+ translations: {
186
+ items: [
187
+ { '@id': 'http://localhost:8080/Plone/eu', language: 'eu' },
188
+ { '@id': 'http://localhost:8080/Plone/es', language: 'es' },
189
+ ],
190
+ },
191
+ },
192
+ };
193
+
194
+ // We need to force the component rendering
195
+ // to fill the Helmet
196
+ renderer.create(
197
+ <Provider store={store}>
198
+ <AlternateHrefLangs content={content} />
199
+ </Provider>,
200
+ );
201
+
202
+ const helmetLinks = Helmet.peek().linkTags;
203
+
204
+ // We expect having 3 links
205
+ expect(helmetLinks.length).toBe(3);
206
+
207
+ expect(helmetLinks).toContainEqual({
208
+ rel: 'alternate',
209
+ href: 'https://plone.org/eu',
210
+ hrefLang: 'eu',
211
+ });
212
+ expect(helmetLinks).toContainEqual({
213
+ rel: 'alternate',
214
+ href: 'https://plone.org/es',
215
+ hrefLang: 'es',
216
+ });
217
+ expect(helmetLinks).toContainEqual({
218
+ rel: 'alternate',
219
+ href: 'https://plone.org/en',
220
+ hrefLang: 'en',
221
+ });
222
+ });
223
+ });
@@ -21,9 +21,9 @@ import crypto from 'crypto';
21
21
  import routes from '@root/routes';
22
22
  import config from '@plone/volto/registry';
23
23
 
24
- import { flattenToAppURL } from '@plone/volto/helpers/Url/Url';
25
24
  import Html from '@plone/volto/helpers/Html/Html';
26
25
  import Api from '@plone/volto/helpers/Api/Api';
26
+ import { flattenToAppURL } from '@plone/volto/helpers/Url/Url';
27
27
  import { persistAuthToken } from '@plone/volto/helpers/AuthToken/AuthToken';
28
28
  import {
29
29
  toBackendLang,
@@ -1,4 +1,4 @@
1
- import { GET_NAVIGATION_SETTINGS } from '../../constants/ActionTypes';
1
+ import { GET_NAVIGATION_SETTINGS } from '@eeacms/volto-eea-website-theme/constants/ActionTypes';
2
2
 
3
3
  const initialState = {
4
4
  error: null,
@@ -1,5 +1,5 @@
1
1
  import navigationReducer from './navigation';
2
- import { GET_NAVIGATION_SETTINGS } from '../../constants/ActionTypes';
2
+ import { GET_NAVIGATION_SETTINGS } from '@eeacms/volto-eea-website-theme/constants/ActionTypes';
3
3
 
4
4
  describe('navigation reducer', () => {
5
5
  const initialState = {
@@ -3,7 +3,7 @@
3
3
  * @module reducers/navigation
4
4
  */
5
5
 
6
- import { GET_NAVIGATION_SETTINGS } from '../constants/ActionTypes';
6
+ import { GET_NAVIGATION_SETTINGS } from '@eeacms/volto-eea-website-theme/constants/ActionTypes';
7
7
 
8
8
  const initialState = {
9
9
  error: null,