@eeacms/volto-hero-block 7.1.0 → 8.0.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 +27 -0
- package/package.json +1 -1
- package/src/components/Blocks/Hero/Edit.jsx +2 -3
- package/src/components/Blocks/Hero/Edit.test.jsx +39 -11
- package/src/components/Blocks/Hero/Hero.jsx +34 -33
- package/src/components/Blocks/Hero/Hero.test.jsx +3 -3
- package/src/components/Blocks/Hero/View.jsx +5 -3
- package/src/components/Blocks/Hero/View.test.jsx +4 -2
- package/src/components/Blocks/Hero/schema.js +8 -7
- package/src/components/Blocks/Hero/schema.test.js +66 -0
- package/src/helpers.js +2 -25
- package/src/helpers.test.js +1 -118
- package/src/hooks.test.js +17 -11
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,33 @@ 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
|
+
### [8.0.0](https://github.com/eea/volto-hero-block/compare/7.1.0...8.0.0) - 28 July 2025
|
|
8
|
+
|
|
9
|
+
#### :house: Internal changes
|
|
10
|
+
|
|
11
|
+
- chore: package.json [alin - [`61b396a`](https://github.com/eea/volto-hero-block/commit/61b396a72309e7be8cab0d77e92d1159d93a4d70)]
|
|
12
|
+
- chore(release): use * for volto-object-widget instead of develop branch [David Ichim - [`075da00`](https://github.com/eea/volto-hero-block/commit/075da00049b8afc17e200369e544a11d37497d51)]
|
|
13
|
+
- chore: bump major version [Nilesh - [`df4edc7`](https://github.com/eea/volto-hero-block/commit/df4edc70703c74ae89dfcef6dfed28cb63835e02)]
|
|
14
|
+
- style: Automated code fix [eea-jenkins - [`7de43a1`](https://github.com/eea/volto-hero-block/commit/7de43a1730c18530d67cee67b573835eb5457dfe)]
|
|
15
|
+
- style: Automated code fix [eea-jenkins - [`34d6a07`](https://github.com/eea/volto-hero-block/commit/34d6a07ad2d2ef83ef2a4c8c12e5343ca34ac810)]
|
|
16
|
+
|
|
17
|
+
#### :hammer_and_wrench: Others
|
|
18
|
+
|
|
19
|
+
- rafactor: do not preload the image as the LCP improvement is negligible [nileshgulia1 - [`dd49a13`](https://github.com/eea/volto-hero-block/commit/dd49a13ddb538d18278631eb46134926e384963d)]
|
|
20
|
+
- revert to background image with image preload [vladcalin-edw - [`34a9533`](https://github.com/eea/volto-hero-block/commit/34a953327e9778c4a34c8c1b587cfe179b0f05ff)]
|
|
21
|
+
- passing tests [vladcalin-edw - [`840ad7b`](https://github.com/eea/volto-hero-block/commit/840ad7b05395c913d712bb90eb64cc3e129f8646)]
|
|
22
|
+
- removed console [vladcalin-edw - [`b47d199`](https://github.com/eea/volto-hero-block/commit/b47d1994fce50a5715580c117775e365ac9ab16c)]
|
|
23
|
+
- fix pipeline and removed alt [vladcalin-edw - [`2183529`](https://github.com/eea/volto-hero-block/commit/218352982b9d2280073ae4b291efb6c843a57ae8)]
|
|
24
|
+
- fix pipeline [vladcalin-edw - [`6be0b71`](https://github.com/eea/volto-hero-block/commit/6be0b71d339c0ae8960b704faa10a9779c3705b0)]
|
|
25
|
+
- conditional image and using Image from core [vladcalin-edw - [`9000d76`](https://github.com/eea/volto-hero-block/commit/9000d7653bc515ec20c1ba96ed78a975a1cd4990)]
|
|
26
|
+
- cleanup [vladcalin-edw - [`167c2b7`](https://github.com/eea/volto-hero-block/commit/167c2b7775016a8247671319f41d654a5fe7d692)]
|
|
27
|
+
- modified Makefile and pipeline pass [vladcalin-edw - [`b7f6d1d`](https://github.com/eea/volto-hero-block/commit/b7f6d1db006d70c45c1bfb00b906f416d8b48f1c)]
|
|
28
|
+
- removed preload, added Image from core, lazy to eager [vladcalin-edw - [`39c4fd1`](https://github.com/eea/volto-hero-block/commit/39c4fd1c120130d65724916ed9e1419c9764d881)]
|
|
29
|
+
- updated tests [vladcalin-edw - [`0a9f94d`](https://github.com/eea/volto-hero-block/commit/0a9f94da3c9089ddd7d3b8ebc60eaf54500b43e1)]
|
|
30
|
+
- passing pipeline [vladcalin-edw - [`3cf0b98`](https://github.com/eea/volto-hero-block/commit/3cf0b988beadc1c7238bceb62cb4ae1328e9eb92)]
|
|
31
|
+
- small fixes [vladcalin-edw - [`e45156f`](https://github.com/eea/volto-hero-block/commit/e45156f7592690b9f06113b799c87cd6d49f6316)]
|
|
32
|
+
- switched to img [vladcalin-edw - [`51e9362`](https://github.com/eea/volto-hero-block/commit/51e9362d8cb04894be032fd602df270dcc2c441f)]
|
|
33
|
+
- background image preload [vladcalin-edw - [`f5b2e20`](https://github.com/eea/volto-hero-block/commit/f5b2e205ae93d4d51b73fd2c6a05659184150f39)]
|
|
7
34
|
### [7.1.0](https://github.com/eea/volto-hero-block/compare/7.0.1...7.1.0) - 28 June 2024
|
|
8
35
|
|
|
9
36
|
#### :bug: Bug Fixes
|
package/package.json
CHANGED
|
@@ -15,8 +15,7 @@ import {
|
|
|
15
15
|
UniversalLink,
|
|
16
16
|
} from '@plone/volto/components';
|
|
17
17
|
import { BodyClass } from '@plone/volto/helpers';
|
|
18
|
-
|
|
19
|
-
import { getFieldURL } from '@eeacms/volto-hero-block/helpers';
|
|
18
|
+
import { getFieldURL } from '@plone/volto/helpers/Url/Url';
|
|
20
19
|
import { HeroBlockSchema } from './schema';
|
|
21
20
|
import Copyright from './Copyright';
|
|
22
21
|
import Hero from './Hero';
|
|
@@ -117,7 +116,7 @@ export default function Edit(props) {
|
|
|
117
116
|
allowedBlocks={'slate'}
|
|
118
117
|
selectedBlock={selectedBlock}
|
|
119
118
|
title={data.placeholder}
|
|
120
|
-
onSelectBlock={(s
|
|
119
|
+
onSelectBlock={(s) => {
|
|
121
120
|
setSelectedBlock(s);
|
|
122
121
|
}}
|
|
123
122
|
onChangeFormData={(newFormData) => {
|
|
@@ -1,21 +1,30 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { render } from '@testing-library/react';
|
|
3
3
|
import { Provider } from 'react-redux';
|
|
4
|
+
import { IntlProvider } from 'react-intl';
|
|
4
5
|
import configureStore from 'redux-mock-store';
|
|
5
6
|
import Edit from './Edit';
|
|
6
7
|
import config from '@plone/volto/registry';
|
|
7
8
|
import '@testing-library/jest-dom/extend-expect';
|
|
8
9
|
|
|
10
|
+
// Mock uuid to avoid node:crypto import issues
|
|
11
|
+
jest.mock('uuid', () => ({
|
|
12
|
+
v4: () => 'mock-uuid-' + Math.random().toString(36).substr(2, 9),
|
|
13
|
+
}));
|
|
14
|
+
|
|
9
15
|
const mockStore = configureStore([]);
|
|
10
16
|
const observe = jest.fn();
|
|
11
17
|
const unobserve = jest.fn();
|
|
18
|
+
const disconnect = jest.fn();
|
|
12
19
|
jest.mock('@plone/volto/components', () => {
|
|
13
20
|
return {
|
|
14
21
|
__esModule: true,
|
|
15
22
|
BlocksForm: ({ placeholder, children, onChange, onFocus }) => (
|
|
16
23
|
<div id="test">
|
|
17
24
|
<div>{placeholder}</div>
|
|
18
|
-
{children
|
|
25
|
+
{typeof children === 'function'
|
|
26
|
+
? children({}, <div>Mock Edit Block</div>, {})
|
|
27
|
+
: children}
|
|
19
28
|
</div>
|
|
20
29
|
),
|
|
21
30
|
SidebarPortal: ({ children }) => <div>{children}</div>,
|
|
@@ -24,6 +33,15 @@ jest.mock('@plone/volto/components', () => {
|
|
|
24
33
|
RenderBlocks: () => <div></div>,
|
|
25
34
|
};
|
|
26
35
|
});
|
|
36
|
+
|
|
37
|
+
jest.mock(
|
|
38
|
+
'@plone/volto/components/manage/Blocks/Block/EditBlockWrapper',
|
|
39
|
+
() => {
|
|
40
|
+
return ({ children }) => (
|
|
41
|
+
<div className="edit-block-wrapper">{children}</div>
|
|
42
|
+
);
|
|
43
|
+
},
|
|
44
|
+
);
|
|
27
45
|
jest.mock('react-router-dom', () => ({
|
|
28
46
|
useLocation: jest.fn().mockReturnValue({
|
|
29
47
|
pathname: '/test-jest',
|
|
@@ -34,9 +52,10 @@ jest.mock('react-router-dom', () => ({
|
|
|
34
52
|
}),
|
|
35
53
|
}));
|
|
36
54
|
|
|
37
|
-
window.IntersectionObserver = jest.fn((
|
|
55
|
+
window.IntersectionObserver = jest.fn(() => ({
|
|
38
56
|
observe,
|
|
39
57
|
unobserve,
|
|
58
|
+
disconnect,
|
|
40
59
|
}));
|
|
41
60
|
|
|
42
61
|
config.blocks = {
|
|
@@ -45,6 +64,7 @@ config.blocks = {
|
|
|
45
64
|
copyrightPrefix: 'Test Prefix',
|
|
46
65
|
schema: {
|
|
47
66
|
title: 'Hero',
|
|
67
|
+
required: [],
|
|
48
68
|
},
|
|
49
69
|
},
|
|
50
70
|
},
|
|
@@ -53,6 +73,7 @@ config.settings = {
|
|
|
53
73
|
slate: {
|
|
54
74
|
textblockExtensions: [],
|
|
55
75
|
},
|
|
76
|
+
themeColors: [],
|
|
56
77
|
};
|
|
57
78
|
|
|
58
79
|
describe('Edit component', () => {
|
|
@@ -67,9 +88,11 @@ describe('Edit component', () => {
|
|
|
67
88
|
|
|
68
89
|
it('renders without crashing', () => {
|
|
69
90
|
const { container } = render(
|
|
70
|
-
<
|
|
71
|
-
<
|
|
72
|
-
|
|
91
|
+
<IntlProvider locale="en" messages={{}}>
|
|
92
|
+
<Provider store={store}>
|
|
93
|
+
<Edit onChangeBlock={() => {}} onSelectBlock={() => {}} />
|
|
94
|
+
</Provider>
|
|
95
|
+
</IntlProvider>,
|
|
73
96
|
);
|
|
74
97
|
expect(container).toBeTruthy();
|
|
75
98
|
});
|
|
@@ -89,9 +112,11 @@ describe('Edit component', () => {
|
|
|
89
112
|
};
|
|
90
113
|
|
|
91
114
|
const { container } = render(
|
|
92
|
-
<
|
|
93
|
-
<
|
|
94
|
-
|
|
115
|
+
<IntlProvider locale="en" messages={{}}>
|
|
116
|
+
<Provider store={store}>
|
|
117
|
+
<Edit data={data} onChangeBlock={() => {}} />
|
|
118
|
+
</Provider>
|
|
119
|
+
</IntlProvider>,
|
|
95
120
|
);
|
|
96
121
|
|
|
97
122
|
expect(container.querySelector('#test')).toBeInTheDocument();
|
|
@@ -103,15 +128,18 @@ describe('Edit component', () => {
|
|
|
103
128
|
hero: {
|
|
104
129
|
schema: () => ({
|
|
105
130
|
title: 'Hero',
|
|
131
|
+
required: [],
|
|
106
132
|
}),
|
|
107
133
|
},
|
|
108
134
|
},
|
|
109
135
|
};
|
|
110
136
|
const onSelectBlock = jest.fn();
|
|
111
137
|
render(
|
|
112
|
-
<
|
|
113
|
-
<
|
|
114
|
-
|
|
138
|
+
<IntlProvider locale="en" messages={{}}>
|
|
139
|
+
<Provider store={store}>
|
|
140
|
+
<Edit onSelectBlock={onSelectBlock} onChangeBlock={() => {}} />
|
|
141
|
+
</Provider>
|
|
142
|
+
</IntlProvider>,
|
|
115
143
|
);
|
|
116
144
|
});
|
|
117
145
|
});
|
|
@@ -1,12 +1,11 @@
|
|
|
1
|
-
import React from 'react';
|
|
1
|
+
import React, { useMemo, useRef } from 'react';
|
|
2
2
|
import cx from 'classnames';
|
|
3
3
|
import PropTypes from 'prop-types';
|
|
4
|
-
import {
|
|
5
|
-
import { isImageGif, getFieldURL } from '@eeacms/volto-hero-block/helpers';
|
|
4
|
+
import { getImageScaleParams } from '@eeacms/volto-object-widget/helpers';
|
|
6
5
|
import { useFirstVisited } from '@eeacms/volto-hero-block/hooks';
|
|
7
6
|
|
|
8
7
|
Hero.propTypes = {
|
|
9
|
-
image: PropTypes.string,
|
|
8
|
+
image: PropTypes.oneOfType([PropTypes.string, PropTypes.array]),
|
|
10
9
|
fullWidth: PropTypes.bool,
|
|
11
10
|
fullHeight: PropTypes.bool,
|
|
12
11
|
alignContent: PropTypes.string,
|
|
@@ -18,46 +17,48 @@ Hero.propTypes = {
|
|
|
18
17
|
textVariant: PropTypes.string,
|
|
19
18
|
};
|
|
20
19
|
|
|
21
|
-
function Hero({
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
20
|
+
function Hero(props) {
|
|
21
|
+
const {
|
|
22
|
+
image,
|
|
23
|
+
fullWidth = false,
|
|
24
|
+
fullHeight = false,
|
|
25
|
+
height,
|
|
26
|
+
styles,
|
|
27
|
+
overlay = true,
|
|
28
|
+
children,
|
|
29
|
+
spaced = false,
|
|
30
|
+
inverted = false,
|
|
31
|
+
} = props;
|
|
32
|
+
|
|
33
|
+
const scaledImage = useMemo(
|
|
34
|
+
() => (image ? getImageScaleParams(image, 'huge') : null),
|
|
35
|
+
[image],
|
|
36
|
+
);
|
|
34
37
|
const { alignContent = 'center', backgroundVariant = 'primary' } =
|
|
35
38
|
styles || {};
|
|
36
|
-
const bgImgRef =
|
|
39
|
+
const bgImgRef = useRef();
|
|
37
40
|
const onScreen = useFirstVisited(bgImgRef);
|
|
38
|
-
const containerCssStyles =
|
|
41
|
+
const containerCssStyles = useMemo(
|
|
39
42
|
() => ({
|
|
40
43
|
...(height && !fullHeight ? { height } : {}),
|
|
41
44
|
}),
|
|
42
45
|
[height, fullHeight],
|
|
43
46
|
);
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
}
|
|
54
|
-
: {};
|
|
47
|
+
const backgroundImageStyle = useMemo(
|
|
48
|
+
() =>
|
|
49
|
+
onScreen && scaledImage
|
|
50
|
+
? {
|
|
51
|
+
backgroundImage: `url(${scaledImage.download})`,
|
|
52
|
+
}
|
|
53
|
+
: {},
|
|
54
|
+
[onScreen, scaledImage],
|
|
55
|
+
);
|
|
55
56
|
|
|
56
57
|
return (
|
|
57
58
|
<div
|
|
58
59
|
className={cx(
|
|
59
60
|
'eea hero-block',
|
|
60
|
-
!
|
|
61
|
+
!scaledImage &&
|
|
61
62
|
backgroundVariant &&
|
|
62
63
|
!fullWidth &&
|
|
63
64
|
`color-bg-${backgroundVariant}`,
|
|
@@ -71,7 +72,7 @@ function Hero({
|
|
|
71
72
|
<div
|
|
72
73
|
className={cx(
|
|
73
74
|
'hero-block-image-wrapper',
|
|
74
|
-
!
|
|
75
|
+
!scaledImage &&
|
|
75
76
|
backgroundVariant &&
|
|
76
77
|
fullWidth &&
|
|
77
78
|
`color-bg-${backgroundVariant}`,
|
|
@@ -86,7 +87,7 @@ function Hero({
|
|
|
86
87
|
ref={bgImgRef}
|
|
87
88
|
style={backgroundImageStyle}
|
|
88
89
|
/>
|
|
89
|
-
{
|
|
90
|
+
{scaledImage && overlay && (
|
|
90
91
|
<div className="hero-block-image-overlay dark-overlay-4"></div>
|
|
91
92
|
)}
|
|
92
93
|
</div>
|
|
@@ -103,7 +103,7 @@ describe('Hero block', () => {
|
|
|
103
103
|
const { container } = render(
|
|
104
104
|
<Provider store={store}>
|
|
105
105
|
<Hero
|
|
106
|
-
image={{ '@type': 'URL', url: 'url_url', href: 'href_url' }}
|
|
106
|
+
image={[{ '@type': 'URL', url: 'url_url', href: 'href_url' }]}
|
|
107
107
|
overlay={true}
|
|
108
108
|
fullWidth={false}
|
|
109
109
|
fullHeight={true}
|
|
@@ -122,10 +122,10 @@ describe('Hero block', () => {
|
|
|
122
122
|
</Provider>,
|
|
123
123
|
);
|
|
124
124
|
|
|
125
|
-
expect(container.querySelector('.eea.hero-block')).toBeInTheDocument();
|
|
126
125
|
expect(container.querySelector('.hero-block-image')).toHaveStyle({
|
|
127
126
|
backgroundImage: 'url(url_url)',
|
|
128
127
|
});
|
|
128
|
+
expect(container.querySelector('.eea.hero-block')).toBeInTheDocument();
|
|
129
129
|
});
|
|
130
130
|
|
|
131
131
|
it('renders a hero component', () => {
|
|
@@ -157,10 +157,10 @@ describe('Hero block', () => {
|
|
|
157
157
|
</Provider>,
|
|
158
158
|
);
|
|
159
159
|
|
|
160
|
-
expect(container.querySelector('.eea.hero-block')).toBeInTheDocument();
|
|
161
160
|
expect(container.querySelector('.hero-block-image')).toHaveStyle({
|
|
162
161
|
backgroundImage: 'url(/foo/bar/@@images/image/huge)',
|
|
163
162
|
});
|
|
163
|
+
expect(container.querySelector('.eea.hero-block')).toBeInTheDocument();
|
|
164
164
|
});
|
|
165
165
|
|
|
166
166
|
it('renders a hero component', () => {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React from 'react';
|
|
1
|
+
import React, { useMemo } from 'react';
|
|
2
2
|
import cx from 'classnames';
|
|
3
3
|
import { Icon } from 'semantic-ui-react';
|
|
4
4
|
import { UniversalLink, RenderBlocks } from '@plone/volto/components';
|
|
@@ -6,7 +6,8 @@ import { BodyClass } from '@plone/volto/helpers';
|
|
|
6
6
|
import { useLocation } from 'react-router-dom';
|
|
7
7
|
import Hero from './Hero';
|
|
8
8
|
import Copyright from './Copyright';
|
|
9
|
-
import { serializeText
|
|
9
|
+
import { serializeText } from '@eeacms/volto-hero-block/helpers';
|
|
10
|
+
import { getFieldURL } from '@plone/volto/helpers/Url/Url';
|
|
10
11
|
import config from '@plone/volto/registry';
|
|
11
12
|
|
|
12
13
|
const Metadata = ({ buttonLabel, inverted, styles, ...props }) => {
|
|
@@ -29,6 +30,7 @@ const View = (props) => {
|
|
|
29
30
|
const location = useLocation();
|
|
30
31
|
const { data = {} } = props;
|
|
31
32
|
const { text, copyright, copyrightIcon, copyrightPosition } = data;
|
|
33
|
+
const serializedText = useMemo(() => serializeText(text), [text]);
|
|
32
34
|
|
|
33
35
|
const metadata = props.metadata || props.properties;
|
|
34
36
|
const copyrightPrefix = config.blocks.blocksConfig.hero.copyrightPrefix || '';
|
|
@@ -44,7 +46,7 @@ const View = (props) => {
|
|
|
44
46
|
content={data?.data || {}}
|
|
45
47
|
/>
|
|
46
48
|
) : (
|
|
47
|
-
|
|
49
|
+
serializedText
|
|
48
50
|
)}
|
|
49
51
|
</Hero.Text>
|
|
50
52
|
<Hero.Meta {...data}>
|
|
@@ -5,9 +5,11 @@ import View from './View';
|
|
|
5
5
|
|
|
6
6
|
const observe = jest.fn();
|
|
7
7
|
const unobserve = jest.fn();
|
|
8
|
-
|
|
8
|
+
const disconnect = jest.fn();
|
|
9
|
+
window.IntersectionObserver = jest.fn(() => ({
|
|
9
10
|
observe,
|
|
10
11
|
unobserve,
|
|
12
|
+
disconnect,
|
|
11
13
|
}));
|
|
12
14
|
jest.mock('@plone/volto/components', () => {
|
|
13
15
|
return {
|
|
@@ -46,7 +48,7 @@ describe('View Component', () => {
|
|
|
46
48
|
text: 'Hello, World!',
|
|
47
49
|
copyright: 'Copyright 2023',
|
|
48
50
|
copyrightIcon: 'copyright-icon-class',
|
|
49
|
-
copyrightPosition: '
|
|
51
|
+
copyrightPosition: 'left',
|
|
50
52
|
};
|
|
51
53
|
render(<View data={data} />);
|
|
52
54
|
});
|
|
@@ -13,7 +13,7 @@ const ALIGN_INFO_MAP = {
|
|
|
13
13
|
'': [clearSVG, 'None'],
|
|
14
14
|
};
|
|
15
15
|
|
|
16
|
-
export const HeroBlockSchema = ({ data }) => {
|
|
16
|
+
export const HeroBlockSchema = ({ data = {} }) => {
|
|
17
17
|
return {
|
|
18
18
|
title: 'Hero',
|
|
19
19
|
fieldsets: [
|
|
@@ -43,27 +43,27 @@ export const HeroBlockSchema = ({ data }) => {
|
|
|
43
43
|
fullWidth: {
|
|
44
44
|
title: 'Full width',
|
|
45
45
|
type: 'boolean',
|
|
46
|
-
|
|
46
|
+
default: true,
|
|
47
47
|
},
|
|
48
48
|
fullHeight: {
|
|
49
49
|
title: 'Full height',
|
|
50
50
|
type: 'boolean',
|
|
51
|
-
|
|
51
|
+
default: true,
|
|
52
52
|
},
|
|
53
53
|
quoted: {
|
|
54
54
|
title: 'Quoted',
|
|
55
55
|
type: 'boolean',
|
|
56
|
-
|
|
56
|
+
default: false,
|
|
57
57
|
},
|
|
58
58
|
spaced: {
|
|
59
59
|
title: 'Spaced',
|
|
60
60
|
type: 'boolean',
|
|
61
|
-
|
|
61
|
+
default: false,
|
|
62
62
|
},
|
|
63
63
|
inverted: {
|
|
64
64
|
title: 'Inverted',
|
|
65
65
|
type: 'boolean',
|
|
66
|
-
|
|
66
|
+
default: true,
|
|
67
67
|
},
|
|
68
68
|
buttonLabel: {
|
|
69
69
|
title: 'Button label',
|
|
@@ -76,11 +76,12 @@ export const HeroBlockSchema = ({ data }) => {
|
|
|
76
76
|
overlay: {
|
|
77
77
|
title: 'Image darken overlay',
|
|
78
78
|
type: 'boolean',
|
|
79
|
-
|
|
79
|
+
default: true,
|
|
80
80
|
},
|
|
81
81
|
image: {
|
|
82
82
|
title: 'Image',
|
|
83
83
|
widget: 'attachedimage',
|
|
84
|
+
selectedItemAttrs: ['image_field', 'image_scales'],
|
|
84
85
|
},
|
|
85
86
|
copyright: {
|
|
86
87
|
title: 'Text',
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { stylingSchema, HeroBlockSchema } from './schema';
|
|
2
|
+
import config from '@plone/volto/registry';
|
|
3
|
+
|
|
4
|
+
jest.mock('@plone/volto/helpers', () => ({
|
|
5
|
+
addStyling: jest.fn((schema) => schema),
|
|
6
|
+
}));
|
|
7
|
+
|
|
8
|
+
export const EMPTY_STYLES_SCHEMA = {
|
|
9
|
+
fieldsets: [
|
|
10
|
+
{
|
|
11
|
+
id: 'default',
|
|
12
|
+
title: 'Default',
|
|
13
|
+
fields: [],
|
|
14
|
+
},
|
|
15
|
+
],
|
|
16
|
+
properties: {
|
|
17
|
+
styles: {},
|
|
18
|
+
},
|
|
19
|
+
required: [],
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
describe('stylingSchema', () => {
|
|
23
|
+
it('returns the enhanced stylesSchema', () => {
|
|
24
|
+
const schema = EMPTY_STYLES_SCHEMA;
|
|
25
|
+
expect(stylingSchema(schema)).not.toBe({});
|
|
26
|
+
});
|
|
27
|
+
it('checks if the stylesSchema has desired fields', () => {
|
|
28
|
+
const schema = EMPTY_STYLES_SCHEMA;
|
|
29
|
+
const stylesSchema = stylingSchema(schema);
|
|
30
|
+
expect(
|
|
31
|
+
stylesSchema.properties.styles.schema.fieldsets[0].fields,
|
|
32
|
+
).toHaveLength(7);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it('checks if the backgroundVariant styles has value', () => {
|
|
36
|
+
config.settings.themeColors = [{ value: 'primary', title: 'Primary' }];
|
|
37
|
+
const schema = EMPTY_STYLES_SCHEMA;
|
|
38
|
+
const stylesSchema = stylingSchema(schema);
|
|
39
|
+
expect(
|
|
40
|
+
stylesSchema.properties.styles.schema.properties.backgroundVariant.colors,
|
|
41
|
+
).toHaveLength(1);
|
|
42
|
+
});
|
|
43
|
+
it('checks if the Text styles has value', () => {
|
|
44
|
+
config.settings.themeColors = [{ value: 'primary', title: 'Primary' }];
|
|
45
|
+
const schema = EMPTY_STYLES_SCHEMA;
|
|
46
|
+
const stylesSchema = stylingSchema(schema);
|
|
47
|
+
expect(
|
|
48
|
+
stylesSchema.properties.styles.schema.properties.textVariant.colors,
|
|
49
|
+
).toHaveLength(1);
|
|
50
|
+
});
|
|
51
|
+
it('checks if the button styles has value', () => {
|
|
52
|
+
config.settings.themeColors = [{ value: 'primary', title: 'Primary' }];
|
|
53
|
+
const schema = EMPTY_STYLES_SCHEMA;
|
|
54
|
+
const stylesSchema = stylingSchema(schema);
|
|
55
|
+
expect(
|
|
56
|
+
stylesSchema.properties.styles.schema.properties.buttonVariant.colors,
|
|
57
|
+
).toHaveLength(1);
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
describe('HeroBlockSchema', () => {
|
|
62
|
+
it('checks if the hero block schema is valid', () => {
|
|
63
|
+
const schema = HeroBlockSchema({ data: {} });
|
|
64
|
+
expect(schema.fieldsets).toHaveLength(2);
|
|
65
|
+
});
|
|
66
|
+
});
|
package/src/helpers.js
CHANGED
|
@@ -1,23 +1,4 @@
|
|
|
1
1
|
import { serializeNodes } from '@plone/volto-slate/editor/render';
|
|
2
|
-
import isArray from 'lodash/isArray';
|
|
3
|
-
import isObject from 'lodash/isObject';
|
|
4
|
-
import isString from 'lodash/isString';
|
|
5
|
-
import { isInternalURL, flattenToAppURL } from '@plone/volto/helpers';
|
|
6
|
-
|
|
7
|
-
export const getFieldURL = (data) => {
|
|
8
|
-
let url = data;
|
|
9
|
-
const _isObject = data && isObject(data) && !isArray(data);
|
|
10
|
-
if (_isObject && data['@type'] === 'URL') {
|
|
11
|
-
url = data['value'] ?? data['url'] ?? data['href'] ?? data;
|
|
12
|
-
} else if (_isObject) {
|
|
13
|
-
url = data['@id'] ?? data['url'] ?? data['href'] ?? data;
|
|
14
|
-
}
|
|
15
|
-
if (isArray(data)) {
|
|
16
|
-
url = data.map((item) => getFieldURL(item));
|
|
17
|
-
}
|
|
18
|
-
if (isString(url) && isInternalURL(url)) return flattenToAppURL(url);
|
|
19
|
-
return url;
|
|
20
|
-
};
|
|
21
2
|
|
|
22
3
|
const createEmptyHeader = () => {
|
|
23
4
|
return {
|
|
@@ -27,13 +8,9 @@ const createEmptyHeader = () => {
|
|
|
27
8
|
};
|
|
28
9
|
|
|
29
10
|
export const createSlateHeader = (text) => {
|
|
30
|
-
return isArray(text) ? text : [createEmptyHeader()];
|
|
11
|
+
return Array.isArray(text) ? text : [createEmptyHeader()];
|
|
31
12
|
};
|
|
32
13
|
|
|
33
14
|
export const serializeText = (text) => {
|
|
34
|
-
return isArray(text) ? serializeNodes(text) : text;
|
|
35
|
-
};
|
|
36
|
-
|
|
37
|
-
export const isImageGif = (image) => {
|
|
38
|
-
return image?.endsWith?.('.gif');
|
|
15
|
+
return Array.isArray(text) ? serializeNodes(text) : text;
|
|
39
16
|
};
|
package/src/helpers.test.js
CHANGED
|
@@ -1,9 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
getFieldURL,
|
|
3
|
-
createSlateHeader,
|
|
4
|
-
serializeText,
|
|
5
|
-
isImageGif,
|
|
6
|
-
} from './helpers';
|
|
1
|
+
import { createSlateHeader, serializeText } from './helpers';
|
|
7
2
|
import { isArray } from 'lodash';
|
|
8
3
|
import { serializeNodes } from '@plone/volto-slate/editor/render';
|
|
9
4
|
|
|
@@ -11,94 +6,6 @@ jest.mock('@plone/volto-slate/editor/render', () => ({
|
|
|
11
6
|
serializeNodes: jest.fn(),
|
|
12
7
|
}));
|
|
13
8
|
|
|
14
|
-
describe('getFieldURL', () => {
|
|
15
|
-
it('handles a URL type object with type and value', () => {
|
|
16
|
-
const data = {
|
|
17
|
-
'@type': 'URL',
|
|
18
|
-
value: 'value_url',
|
|
19
|
-
url: 'url_url',
|
|
20
|
-
href: 'href_url',
|
|
21
|
-
};
|
|
22
|
-
expect(getFieldURL(data)).toEqual('value_url');
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
it('handles an object with type and url', () => {
|
|
26
|
-
const data = {
|
|
27
|
-
'@type': 'URL',
|
|
28
|
-
url: 'url_url',
|
|
29
|
-
href: 'href_url',
|
|
30
|
-
};
|
|
31
|
-
expect(getFieldURL(data)).toEqual('url_url');
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
it('handles an object with type and href', () => {
|
|
35
|
-
const data = {
|
|
36
|
-
'@type': 'URL',
|
|
37
|
-
href: 'href_url',
|
|
38
|
-
};
|
|
39
|
-
expect(getFieldURL(data)).toEqual('href_url');
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
it('handles an object with type and no value, url and href', () => {
|
|
43
|
-
const data = {
|
|
44
|
-
'@type': 'URL',
|
|
45
|
-
};
|
|
46
|
-
expect(getFieldURL(data)).toEqual({ '@type': 'URL' });
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
it('handles an object without a specific type and url', () => {
|
|
50
|
-
const data = {
|
|
51
|
-
url: 'url_url',
|
|
52
|
-
href: 'href_url',
|
|
53
|
-
};
|
|
54
|
-
expect(getFieldURL(data)).toEqual('url_url');
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
it('handles an object without a specific type and href', () => {
|
|
58
|
-
const data = {
|
|
59
|
-
href: 'href_url',
|
|
60
|
-
};
|
|
61
|
-
expect(getFieldURL(data)).toEqual('href_url');
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
it('handles an object without a specific type and no id, url, href', () => {
|
|
65
|
-
const data = {
|
|
66
|
-
test: 'test_url',
|
|
67
|
-
};
|
|
68
|
-
expect(getFieldURL(data)).toEqual({
|
|
69
|
-
test: 'test_url',
|
|
70
|
-
});
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
it('handles an array', () => {
|
|
74
|
-
const data = [
|
|
75
|
-
{
|
|
76
|
-
'@type': 'URL',
|
|
77
|
-
value: 'value_url',
|
|
78
|
-
url: 'url_url',
|
|
79
|
-
href: 'href_url',
|
|
80
|
-
},
|
|
81
|
-
{
|
|
82
|
-
'@id': 'id_url',
|
|
83
|
-
url: 'url_url',
|
|
84
|
-
href: 'href_url',
|
|
85
|
-
},
|
|
86
|
-
];
|
|
87
|
-
expect(getFieldURL(data)).toEqual(['value_url', 'id_url']);
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
it('handles a string', () => {
|
|
91
|
-
const data = '/some/url';
|
|
92
|
-
expect(getFieldURL(data)).toEqual('/some/url');
|
|
93
|
-
});
|
|
94
|
-
|
|
95
|
-
it('returns the data unchanged for non-object/non-array/non-string inputs', () => {
|
|
96
|
-
expect(getFieldURL(42)).toEqual(42);
|
|
97
|
-
expect(getFieldURL(undefined)).toEqual(undefined);
|
|
98
|
-
expect(getFieldURL(null)).toEqual(null);
|
|
99
|
-
});
|
|
100
|
-
});
|
|
101
|
-
|
|
102
9
|
describe('createSlateHeader', () => {
|
|
103
10
|
it('should return the text if it is an array', () => {
|
|
104
11
|
const text = ['some', 'text'];
|
|
@@ -133,27 +40,3 @@ describe('serializeText', () => {
|
|
|
133
40
|
expect(serializeNodes).toHaveBeenCalledWith(text);
|
|
134
41
|
});
|
|
135
42
|
});
|
|
136
|
-
|
|
137
|
-
describe('isImageGif', () => {
|
|
138
|
-
it('should return true when input is a gif image', () => {
|
|
139
|
-
const input = 'image.gif';
|
|
140
|
-
const result = isImageGif(input);
|
|
141
|
-
expect(result).toBeTruthy();
|
|
142
|
-
});
|
|
143
|
-
|
|
144
|
-
it('should return false when input is not a gif image', () => {
|
|
145
|
-
const input = 'image.jpg';
|
|
146
|
-
const result = isImageGif(input);
|
|
147
|
-
expect(result).toBeFalsy();
|
|
148
|
-
});
|
|
149
|
-
|
|
150
|
-
it('should return false when input is null or undefined', () => {
|
|
151
|
-
let input;
|
|
152
|
-
let result = isImageGif(input);
|
|
153
|
-
expect(result).toBeFalsy();
|
|
154
|
-
|
|
155
|
-
input = null;
|
|
156
|
-
result = isImageGif(input);
|
|
157
|
-
expect(result).toBeFalsy();
|
|
158
|
-
});
|
|
159
|
-
});
|
package/src/hooks.test.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import { render } from '@testing-library/react';
|
|
2
|
+
import { render, act } from '@testing-library/react';
|
|
3
3
|
import { useFirstVisited } from './hooks';
|
|
4
4
|
|
|
5
5
|
let observerCallback;
|
|
@@ -16,6 +16,12 @@ window.IntersectionObserver = jest.fn((callback) => {
|
|
|
16
16
|
});
|
|
17
17
|
|
|
18
18
|
describe('useFirstVisited', () => {
|
|
19
|
+
beforeEach(() => {
|
|
20
|
+
observeMock.mockClear();
|
|
21
|
+
unobserveMock.mockClear();
|
|
22
|
+
disconnectMock.mockClear();
|
|
23
|
+
});
|
|
24
|
+
|
|
19
25
|
it('should observe and unobserve the ref element', () => {
|
|
20
26
|
const ref = { current: {} };
|
|
21
27
|
const rootMargin = '10px';
|
|
@@ -31,7 +37,9 @@ describe('useFirstVisited', () => {
|
|
|
31
37
|
expect(container.textContent).toBe('Not Intersected');
|
|
32
38
|
|
|
33
39
|
// Simulate the element becoming visible in the viewport
|
|
34
|
-
|
|
40
|
+
act(() => {
|
|
41
|
+
observerCallback([{ isIntersecting: true }]);
|
|
42
|
+
});
|
|
35
43
|
|
|
36
44
|
// Re-render: Intersected
|
|
37
45
|
expect(container.textContent).toBe('Intersected');
|
|
@@ -44,7 +52,7 @@ describe('useFirstVisited', () => {
|
|
|
44
52
|
expect(disconnectMock).toHaveBeenCalled();
|
|
45
53
|
});
|
|
46
54
|
|
|
47
|
-
it('should
|
|
55
|
+
it('should stay intersected once it becomes true', () => {
|
|
48
56
|
const ref = { current: {} };
|
|
49
57
|
|
|
50
58
|
const TestComponent = () => {
|
|
@@ -58,17 +66,15 @@ describe('useFirstVisited', () => {
|
|
|
58
66
|
expect(container.textContent).toBe('Not Intersected');
|
|
59
67
|
|
|
60
68
|
// Simulate the element becoming visible in the viewport
|
|
61
|
-
|
|
69
|
+
act(() => {
|
|
70
|
+
observerCallback([{ isIntersecting: true }]);
|
|
71
|
+
});
|
|
62
72
|
|
|
63
73
|
// Re-render: Intersected
|
|
64
74
|
expect(container.textContent).toBe('Intersected');
|
|
65
75
|
|
|
66
|
-
//
|
|
67
|
-
//
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
// Re-render: Intersected
|
|
71
|
-
expect(container.textContent).toBe('Not Intersected');
|
|
72
|
-
// expect(observeMock).not.toHaveBeenCalled();
|
|
76
|
+
// Once intersected is true, it should stay true regardless of further callbacks
|
|
77
|
+
// The hook should not set up a new observer when intersected is already true
|
|
78
|
+
expect(container.textContent).toBe('Intersected');
|
|
73
79
|
});
|
|
74
80
|
});
|