@eeacms/volto-eea-website-theme 3.7.0 → 3.9.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.
Files changed (42) hide show
  1. package/CHANGELOG.md +19 -2
  2. package/package.json +3 -1
  3. package/src/actions/index.js +1 -0
  4. package/src/actions/navigation.js +24 -0
  5. package/src/actions/print.js +9 -1
  6. package/src/components/manage/Blocks/ContextNavigation/variations/Accordion.jsx +42 -35
  7. package/src/components/manage/Blocks/LayoutSettings/LayoutSettingsEdit.test.jsx +383 -0
  8. package/src/components/manage/Blocks/Title/variations/WebReportPage.test.jsx +232 -0
  9. package/src/components/theme/Banner/View.jsx +11 -92
  10. package/src/components/theme/PrintLoader/PrintLoader.jsx +56 -0
  11. package/src/components/theme/PrintLoader/PrintLoader.test.jsx +91 -0
  12. package/src/components/theme/PrintLoader/style.less +12 -0
  13. package/src/components/theme/WebReport/WebReportSectionView.test.jsx +462 -0
  14. package/src/components/theme/Widgets/ImageViewWidget.test.jsx +26 -0
  15. package/src/components/theme/Widgets/NavigationBehaviorWidget.jsx +601 -0
  16. package/src/components/theme/Widgets/NavigationBehaviorWidget.test.jsx +507 -0
  17. package/src/components/theme/Widgets/SimpleArrayWidget.jsx +183 -0
  18. package/src/components/theme/Widgets/SimpleArrayWidget.test.jsx +283 -0
  19. package/src/constants/ActionTypes.js +2 -0
  20. package/src/customizations/volto/components/manage/History/History.diff +207 -0
  21. package/src/customizations/volto/components/manage/History/History.jsx +444 -0
  22. package/src/customizations/volto/components/theme/Comments/Comments.jsx +9 -2
  23. package/src/customizations/volto/components/theme/Comments/Comments.test.jsx +4 -4
  24. package/src/customizations/volto/components/theme/Comments/comments.less +16 -0
  25. package/src/customizations/volto/components/theme/Header/Header.jsx +60 -1
  26. package/src/customizations/volto/components/theme/View/DefaultView.jsx +42 -33
  27. package/src/customizations/volto/helpers/Html/Html.jsx +212 -0
  28. package/src/customizations/volto/helpers/Html/Readme.md +1 -0
  29. package/src/customizations/volto/server.jsx +375 -0
  30. package/src/helpers/loadLazyImages.js +11 -0
  31. package/src/helpers/loadLazyImages.test.js +22 -0
  32. package/src/helpers/setupPrintView.js +134 -0
  33. package/src/helpers/setupPrintView.test.js +49 -0
  34. package/src/index.js +11 -1
  35. package/src/index.test.js +6 -0
  36. package/src/middleware/voltoCustom.test.js +282 -0
  37. package/src/reducers/index.js +2 -1
  38. package/src/reducers/navigation/navigation.js +47 -0
  39. package/src/reducers/navigation/navigation.test.js +348 -0
  40. package/src/reducers/navigation.js +55 -0
  41. package/src/reducers/print.js +18 -8
  42. package/src/reducers/print.test.js +117 -0
@@ -0,0 +1,462 @@
1
+ import React from 'react';
2
+ import { render } from '@testing-library/react';
3
+ import { Router } from 'react-router-dom';
4
+ import { createMemoryHistory } from 'history';
5
+ import WebReportSectionView from './WebReportSectionView';
6
+
7
+ // Add jest-dom matchers
8
+ import '@testing-library/jest-dom';
9
+
10
+ // Mock external dependencies
11
+ jest.mock('@plone/volto/helpers', () => ({
12
+ isInternalURL: jest.fn(),
13
+ flattenToAppURL: jest.fn(),
14
+ }));
15
+
16
+ jest.mock('@plone/volto/components/', () => ({
17
+ DefaultView: jest.fn(({ children, content, token, ...otherProps }) => (
18
+ <div data-testid="default-view">{children}</div>
19
+ )),
20
+ }));
21
+
22
+ jest.mock('react-router-dom', () => ({
23
+ ...jest.requireActual('react-router-dom'),
24
+ Redirect: jest.fn(({ to }) => <div data-testid="redirect" data-to={to} />),
25
+ }));
26
+
27
+ const { isInternalURL, flattenToAppURL } = require('@plone/volto/helpers');
28
+ const { DefaultView } = require('@plone/volto/components/');
29
+ const { Redirect } = require('react-router-dom');
30
+
31
+ describe('WebReportSectionView', () => {
32
+ let history;
33
+ const originalServer = global.__SERVER__;
34
+
35
+ beforeAll(() => {
36
+ // Set default values for global variables
37
+ global.__SERVER__ = false;
38
+ });
39
+
40
+ beforeEach(() => {
41
+ history = createMemoryHistory();
42
+ history.replace = jest.fn();
43
+
44
+ // Reset mocks
45
+ isInternalURL.mockClear();
46
+ flattenToAppURL.mockClear();
47
+ DefaultView.mockClear();
48
+ Redirect.mockClear();
49
+
50
+ // Reset window.location mock
51
+ delete window.location;
52
+ window.location = { href: '' };
53
+
54
+ // Default mock implementations
55
+ isInternalURL.mockReturnValue(true);
56
+ flattenToAppURL.mockImplementation((url) => url);
57
+
58
+ // Reset __SERVER__ to false for each test
59
+ global.__SERVER__ = false;
60
+ });
61
+
62
+ afterEach(() => {
63
+ global.__SERVER__ = originalServer;
64
+ });
65
+
66
+ const defaultProps = {
67
+ content: null,
68
+ token: null,
69
+ };
70
+
71
+ it('renders without crashing', () => {
72
+ const { container } = render(
73
+ <Router history={history}>
74
+ <WebReportSectionView {...defaultProps} />
75
+ </Router>,
76
+ );
77
+ expect(container).toBeTruthy();
78
+ });
79
+
80
+ it('renders DefaultView when no content is provided', () => {
81
+ render(
82
+ <Router history={history}>
83
+ <WebReportSectionView {...defaultProps} />
84
+ </Router>,
85
+ );
86
+
87
+ expect(DefaultView).toHaveBeenCalledWith(
88
+ expect.objectContaining(defaultProps),
89
+ {},
90
+ );
91
+ });
92
+
93
+ it('renders DefaultView when content has no items', () => {
94
+ const props = {
95
+ ...defaultProps,
96
+ content: { items: [] },
97
+ };
98
+
99
+ render(
100
+ <Router history={history}>
101
+ <WebReportSectionView {...props} />
102
+ </Router>,
103
+ );
104
+
105
+ expect(DefaultView).toHaveBeenCalledWith(
106
+ expect.objectContaining(props),
107
+ {},
108
+ );
109
+ });
110
+
111
+ it('renders DefaultView when token is present', () => {
112
+ const props = {
113
+ content: {
114
+ items: [{ '@id': '/first-item' }],
115
+ },
116
+ token: 'some-token',
117
+ };
118
+
119
+ render(
120
+ <Router history={history}>
121
+ <WebReportSectionView {...props} />
122
+ </Router>,
123
+ );
124
+
125
+ expect(DefaultView).toHaveBeenCalledWith(
126
+ expect.objectContaining(props),
127
+ {},
128
+ );
129
+ expect(history.replace).not.toHaveBeenCalled();
130
+ });
131
+
132
+ it('redirects to first item when no token and internal URL on client', () => {
133
+ global.__SERVER__ = false;
134
+
135
+ const props = {
136
+ content: {
137
+ items: [{ '@id': '/first-item' }, { '@id': '/second-item' }],
138
+ },
139
+ token: null,
140
+ };
141
+
142
+ isInternalURL.mockReturnValue(true);
143
+ flattenToAppURL.mockReturnValue('/flattened-first-item');
144
+
145
+ render(
146
+ <Router history={history}>
147
+ <WebReportSectionView {...props} />
148
+ </Router>,
149
+ );
150
+
151
+ expect(isInternalURL).toHaveBeenCalledWith('/first-item');
152
+ expect(flattenToAppURL).toHaveBeenCalledWith('/first-item');
153
+ expect(history.replace).toHaveBeenCalledWith('/flattened-first-item');
154
+ });
155
+
156
+ it('redirects via window.location for external URL on client', () => {
157
+ global.__SERVER__ = false;
158
+
159
+ const props = {
160
+ content: {
161
+ items: [{ '@id': 'https://external.com/first-item' }],
162
+ },
163
+ token: null,
164
+ };
165
+
166
+ isInternalURL.mockReturnValue(false);
167
+ flattenToAppURL.mockReturnValue(
168
+ 'https://external.com/flattened-first-item',
169
+ );
170
+
171
+ render(
172
+ <Router history={history}>
173
+ <WebReportSectionView {...props} />
174
+ </Router>,
175
+ );
176
+
177
+ expect(isInternalURL).toHaveBeenCalledWith(
178
+ 'https://external.com/first-item',
179
+ );
180
+ expect(flattenToAppURL).toHaveBeenCalledWith(
181
+ 'https://external.com/first-item',
182
+ );
183
+ expect(window.location.href).toBe(
184
+ 'https://external.com/flattened-first-item',
185
+ );
186
+ expect(history.replace).not.toHaveBeenCalled();
187
+ });
188
+
189
+ it('returns Redirect component on server side when no token', () => {
190
+ global.__SERVER__ = true;
191
+
192
+ const props = {
193
+ content: {
194
+ items: [{ '@id': '/first-item' }],
195
+ },
196
+ token: null,
197
+ };
198
+
199
+ render(
200
+ <Router history={history}>
201
+ <WebReportSectionView {...props} />
202
+ </Router>,
203
+ );
204
+
205
+ expect(Redirect).toHaveBeenCalledWith({ to: '/first-item' }, {});
206
+ });
207
+
208
+ it('does not redirect on server side when token is present', () => {
209
+ global.__SERVER__ = true;
210
+
211
+ const props = {
212
+ content: {
213
+ items: [{ '@id': '/first-item' }],
214
+ },
215
+ token: 'some-token',
216
+ };
217
+
218
+ render(
219
+ <Router history={history}>
220
+ <WebReportSectionView {...props} />
221
+ </Router>,
222
+ );
223
+
224
+ expect(Redirect).not.toHaveBeenCalled();
225
+ expect(DefaultView).toHaveBeenCalledWith(
226
+ expect.objectContaining(props),
227
+ {},
228
+ );
229
+ });
230
+
231
+ it('does not redirect when no redirectUrl is available', () => {
232
+ global.__SERVER__ = false;
233
+
234
+ const props = {
235
+ content: {
236
+ items: [],
237
+ },
238
+ token: null,
239
+ };
240
+
241
+ isInternalURL.mockReturnValue(true);
242
+ flattenToAppURL.mockReturnValue(undefined);
243
+
244
+ render(
245
+ <Router history={history}>
246
+ <WebReportSectionView {...props} />
247
+ </Router>,
248
+ );
249
+
250
+ expect(isInternalURL).toHaveBeenCalledWith(undefined);
251
+ expect(history.replace).toHaveBeenCalledWith(undefined);
252
+ });
253
+
254
+ it('handles content with null items', () => {
255
+ const props = {
256
+ content: {
257
+ items: null,
258
+ },
259
+ token: null,
260
+ };
261
+
262
+ isInternalURL.mockReturnValue(true);
263
+ flattenToAppURL.mockReturnValue(undefined);
264
+
265
+ render(
266
+ <Router history={history}>
267
+ <WebReportSectionView {...props} />
268
+ </Router>,
269
+ );
270
+
271
+ expect(DefaultView).toHaveBeenCalledWith(
272
+ expect.objectContaining(props),
273
+ {},
274
+ );
275
+ expect(history.replace).toHaveBeenCalledWith(undefined);
276
+ });
277
+
278
+ it('handles content with undefined items', () => {
279
+ const props = {
280
+ content: {},
281
+ token: null,
282
+ };
283
+
284
+ isInternalURL.mockReturnValue(true);
285
+ flattenToAppURL.mockReturnValue(undefined);
286
+
287
+ render(
288
+ <Router history={history}>
289
+ <WebReportSectionView {...props} />
290
+ </Router>,
291
+ );
292
+
293
+ expect(DefaultView).toHaveBeenCalledWith(
294
+ expect.objectContaining(props),
295
+ {},
296
+ );
297
+ expect(history.replace).toHaveBeenCalledWith(undefined);
298
+ });
299
+
300
+ it('handles items without @id property', () => {
301
+ const props = {
302
+ content: {
303
+ items: [{ title: 'Item without @id' }],
304
+ },
305
+ token: null,
306
+ };
307
+
308
+ isInternalURL.mockReturnValue(true);
309
+ flattenToAppURL.mockReturnValue(undefined);
310
+
311
+ render(
312
+ <Router history={history}>
313
+ <WebReportSectionView {...props} />
314
+ </Router>,
315
+ );
316
+
317
+ expect(DefaultView).toHaveBeenCalledWith(
318
+ expect.objectContaining(props),
319
+ {},
320
+ );
321
+ expect(history.replace).toHaveBeenCalledWith(undefined);
322
+ });
323
+
324
+ it('uses the first item even if it has no @id', () => {
325
+ global.__SERVER__ = false;
326
+
327
+ const props = {
328
+ content: {
329
+ items: [
330
+ { title: 'No @id' },
331
+ { '@id': '/second-item' },
332
+ { '@id': '/third-item' },
333
+ ],
334
+ },
335
+ token: null,
336
+ };
337
+
338
+ isInternalURL.mockReturnValue(true);
339
+ flattenToAppURL.mockReturnValue(undefined);
340
+
341
+ render(
342
+ <Router history={history}>
343
+ <WebReportSectionView {...props} />
344
+ </Router>,
345
+ );
346
+
347
+ // The component gets the first item's @id (which is undefined)
348
+ expect(isInternalURL).toHaveBeenCalledWith(undefined);
349
+ expect(flattenToAppURL).toHaveBeenCalledWith(undefined);
350
+ expect(history.replace).toHaveBeenCalledWith(undefined);
351
+ });
352
+
353
+ it('redirects to first item when it has valid @id', () => {
354
+ global.__SERVER__ = false;
355
+
356
+ const props = {
357
+ content: {
358
+ items: [{ '@id': '/first-item' }, { '@id': '/second-item' }],
359
+ },
360
+ token: null,
361
+ };
362
+
363
+ isInternalURL.mockReturnValue(true);
364
+ flattenToAppURL.mockReturnValue('/flattened-first-item');
365
+
366
+ render(
367
+ <Router history={history}>
368
+ <WebReportSectionView {...props} />
369
+ </Router>,
370
+ );
371
+
372
+ expect(isInternalURL).toHaveBeenCalledWith('/first-item');
373
+ expect(flattenToAppURL).toHaveBeenCalledWith('/first-item');
374
+ expect(history.replace).toHaveBeenCalledWith('/flattened-first-item');
375
+ });
376
+
377
+ it('passes through all props to DefaultView', () => {
378
+ const props = {
379
+ content: null,
380
+ token: null,
381
+ customProp: 'custom-value',
382
+ anotherProp: { nested: 'object' },
383
+ };
384
+
385
+ render(
386
+ <Router history={history}>
387
+ <WebReportSectionView {...props} />
388
+ </Router>,
389
+ );
390
+
391
+ expect(DefaultView).toHaveBeenCalledWith(
392
+ expect.objectContaining(props),
393
+ {},
394
+ );
395
+ });
396
+
397
+ it('memoizes redirectUrl correctly', () => {
398
+ const props = {
399
+ content: {
400
+ items: [{ '@id': '/first-item' }],
401
+ },
402
+ token: 'some-token',
403
+ };
404
+
405
+ const { rerender } = render(
406
+ <Router history={history}>
407
+ <WebReportSectionView {...props} />
408
+ </Router>,
409
+ );
410
+
411
+ // Re-render with same content
412
+ rerender(
413
+ <Router history={history}>
414
+ <WebReportSectionView {...props} />
415
+ </Router>,
416
+ );
417
+
418
+ // Should only call DefaultView twice (once per render)
419
+ expect(DefaultView).toHaveBeenCalledTimes(2);
420
+ });
421
+
422
+ it('updates redirectUrl when content changes', () => {
423
+ global.__SERVER__ = false;
424
+
425
+ const initialProps = {
426
+ content: {
427
+ items: [{ '@id': '/first-item' }],
428
+ },
429
+ token: null,
430
+ };
431
+
432
+ isInternalURL.mockReturnValue(true);
433
+ flattenToAppURL.mockImplementation((url) => `/flattened${url}`);
434
+
435
+ const { rerender } = render(
436
+ <Router history={history}>
437
+ <WebReportSectionView {...initialProps} />
438
+ </Router>,
439
+ );
440
+
441
+ expect(history.replace).toHaveBeenCalledWith('/flattened/first-item');
442
+
443
+ // Clear previous calls
444
+ history.replace.mockClear();
445
+
446
+ // Re-render with different content
447
+ const updatedProps = {
448
+ content: {
449
+ items: [{ '@id': '/different-item' }],
450
+ },
451
+ token: null,
452
+ };
453
+
454
+ rerender(
455
+ <Router history={history}>
456
+ <WebReportSectionView {...updatedProps} />
457
+ </Router>,
458
+ );
459
+
460
+ expect(history.replace).toHaveBeenCalledWith('/flattened/different-item');
461
+ });
462
+ });
@@ -0,0 +1,26 @@
1
+ import '@testing-library/jest-dom';
2
+ import { render, screen } from '@testing-library/react';
3
+ import ImageViewWidget from './ImageViewWidget';
4
+
5
+ describe('ImageViewWidget', () => {
6
+ it('renders img with correct src and alt', () => {
7
+ const mockValue = {
8
+ download: 'https://example.com/image.jpg',
9
+ filename: 'example.jpg',
10
+ };
11
+
12
+ render(<ImageViewWidget value={mockValue} />);
13
+
14
+ const img = screen.getByRole('img');
15
+ expect(img).toHaveAttribute('src', mockValue.download);
16
+ expect(img).toHaveAttribute('alt', mockValue.filename);
17
+ });
18
+
19
+ it('does not render src or alt when value is null', () => {
20
+ render(<ImageViewWidget value={null} />);
21
+
22
+ const img = screen.getByRole('img');
23
+ expect(img).not.toHaveAttribute('src');
24
+ expect(img).not.toHaveAttribute('alt');
25
+ });
26
+ });