@blaze-cms/react-page-builder 0.146.0-node18-tooltips.38 → 0.146.0-node18-core-styles-tooltips.52
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 +134 -44
- package/lib/components/Card/CardsRender.js +28 -20
- package/lib/components/Card/CardsRender.js.map +1 -1
- package/lib/components/List/helpers/build-query-booster.js +9 -6
- package/lib/components/List/helpers/build-query-booster.js.map +1 -1
- package/lib/components/Menu/Menu.js +4 -1
- package/lib/components/Menu/Menu.js.map +1 -1
- package/lib/components/Menu/MenuContext.js +2 -1
- package/lib/components/Menu/MenuContext.js.map +1 -1
- package/lib/components/MenuItem/MenuItemRender.js +27 -12
- package/lib/components/MenuItem/MenuItemRender.js.map +1 -1
- package/lib/components/MenuItem/helpers/has-active-child.js +19 -0
- package/lib/components/MenuItem/helpers/has-active-child.js.map +1 -0
- package/lib/components/MenuItem/helpers/index.js +14 -0
- package/lib/components/MenuItem/helpers/index.js.map +1 -1
- package/lib/components/MenuItem/helpers/isUrlPathMatch.js +18 -0
- package/lib/components/MenuItem/helpers/isUrlPathMatch.js.map +1 -0
- package/lib/hooks/index.js +7 -0
- package/lib/hooks/index.js.map +1 -1
- package/lib/hooks/use-app-event-hook.js +74 -0
- package/lib/hooks/use-app-event-hook.js.map +1 -0
- package/lib-es/components/Card/CardsRender.js +11 -2
- package/lib-es/components/Card/CardsRender.js.map +1 -1
- package/lib-es/components/List/helpers/build-query-booster.js +9 -6
- package/lib-es/components/List/helpers/build-query-booster.js.map +1 -1
- package/lib-es/components/Menu/Menu.js +4 -1
- package/lib-es/components/Menu/Menu.js.map +1 -1
- package/lib-es/components/Menu/MenuContext.js +2 -1
- package/lib-es/components/Menu/MenuContext.js.map +1 -1
- package/lib-es/components/MenuItem/MenuItemRender.js +25 -11
- package/lib-es/components/MenuItem/MenuItemRender.js.map +1 -1
- package/lib-es/components/MenuItem/helpers/has-active-child.js +5 -0
- package/lib-es/components/MenuItem/helpers/has-active-child.js.map +1 -0
- package/lib-es/components/MenuItem/helpers/index.js +3 -1
- package/lib-es/components/MenuItem/helpers/index.js.map +1 -1
- package/lib-es/components/MenuItem/helpers/isUrlPathMatch.js +8 -0
- package/lib-es/components/MenuItem/helpers/isUrlPathMatch.js.map +1 -0
- package/lib-es/hooks/index.js +1 -0
- package/lib-es/hooks/index.js.map +1 -1
- package/lib-es/hooks/use-app-event-hook.js +39 -0
- package/lib-es/hooks/use-app-event-hook.js.map +1 -0
- package/package.json +10 -10
- package/src/components/Card/CardsRender.js +11 -2
- package/src/components/List/helpers/build-query-booster.js +12 -6
- package/src/components/Menu/Menu.js +3 -1
- package/src/components/Menu/MenuContext.js +1 -1
- package/src/components/MenuItem/MenuItemRender.js +40 -12
- package/src/components/MenuItem/helpers/has-active-child.js +10 -0
- package/src/components/MenuItem/helpers/index.js +3 -1
- package/src/components/MenuItem/helpers/isUrlPathMatch.js +10 -0
- package/src/hooks/index.js +1 -0
- package/src/hooks/use-app-event-hook.js +40 -0
- package/tests/unit/src/components/Card/CardsRender.test.js +118 -0
- package/tests/unit/src/components/List/helpers/build-query-booster.test.js +2 -2
- package/tests/unit/src/components/MenuItem/MenuItem.test.js +5 -0
- package/tests/unit/src/components/MenuItem/MenuItemRender.test.js +11 -3
- package/tests/unit/src/components/MenuItem/helpers/constants.js +73 -0
- package/tests/unit/src/components/MenuItem/helpers/has-active-child.test.js +35 -0
- package/tests/unit/src/components/MenuItem/helpers/is-url-path-match.test.js +53 -0
- package/tests/unit/src/hooks/use-app-event-hook.test.js +60 -0
|
@@ -11,6 +11,7 @@ import BlazeLink from '../BlazeLink';
|
|
|
11
11
|
const Menu = ({
|
|
12
12
|
children,
|
|
13
13
|
collapse,
|
|
14
|
+
openActiveSubmenus,
|
|
14
15
|
modifier,
|
|
15
16
|
mobileMenuModifier,
|
|
16
17
|
mobileMenuChildrenModifier,
|
|
@@ -51,7 +52,7 @@ const Menu = ({
|
|
|
51
52
|
});
|
|
52
53
|
|
|
53
54
|
return (
|
|
54
|
-
<MenuContext.Provider value={{ showMobileMenu }}>
|
|
55
|
+
<MenuContext.Provider value={{ showMobileMenu, openActiveSubmenus }}>
|
|
55
56
|
<div className={menuWrapperClasses}>
|
|
56
57
|
{collapse && (
|
|
57
58
|
<div className="menu--mobile-wrapper">
|
|
@@ -99,6 +100,7 @@ const Menu = ({
|
|
|
99
100
|
|
|
100
101
|
Menu.propTypes = {
|
|
101
102
|
collapse: PropTypes.bool.isRequired,
|
|
103
|
+
openActiveSubmenus: PropTypes.bool.isRequired,
|
|
102
104
|
logoOnMobile: PropTypes.bool.isRequired,
|
|
103
105
|
logoOnDesktop: PropTypes.bool,
|
|
104
106
|
logoOnMobileUrl: PropTypes.string,
|
|
@@ -1,17 +1,23 @@
|
|
|
1
|
+
/* eslint-disable jsx-a11y/no-static-element-interactions */
|
|
1
2
|
import React, { useState, useContext, useEffect } from 'react';
|
|
2
3
|
import PropTypes from 'prop-types';
|
|
3
4
|
import { FaChevronDown, FaChevronUp } from 'react-icons/fa';
|
|
5
|
+
import classnames from 'classnames';
|
|
4
6
|
import { useRouter } from 'next/router';
|
|
5
7
|
import { useStringTemplate } from '@blaze-cms/utils-handlebars';
|
|
6
|
-
import { HOVER, MOUSE_ENTER, MOUSE_LEAVE, HIDDEN } from '../../constants';
|
|
8
|
+
import { HOVER, MOUSE_ENTER, MOUSE_LEAVE, HIDDEN, CLICK } from '../../constants';
|
|
7
9
|
import { hasChildren } from '../../helpers';
|
|
8
10
|
import BlazeLink from '../BlazeLink';
|
|
9
11
|
import MenuContext from '../Menu/MenuContext';
|
|
10
|
-
import { injectHelperIntoTemplate } from './helpers';
|
|
12
|
+
import { injectHelperIntoTemplate, isUrlPathMatch, hasActiveChild } from './helpers';
|
|
11
13
|
|
|
12
14
|
const MenuItemRender = ({ children, eventType, text, modifier, url, parent }) => {
|
|
13
|
-
const
|
|
14
|
-
const { showMobileMenu } = useContext(MenuContext);
|
|
15
|
+
const router = useRouter();
|
|
16
|
+
const { showMobileMenu, openActiveSubmenus } = useContext(MenuContext);
|
|
17
|
+
|
|
18
|
+
const isHoverEvent = eventType === HOVER;
|
|
19
|
+
const isClickEvent = eventType === CLICK;
|
|
20
|
+
|
|
15
21
|
const {
|
|
16
22
|
loading: loadingText,
|
|
17
23
|
data: [textToUse]
|
|
@@ -21,14 +27,24 @@ const MenuItemRender = ({ children, eventType, text, modifier, url, parent }) =>
|
|
|
21
27
|
data: [urlToUse]
|
|
22
28
|
} = useStringTemplate(parent, [injectHelperIntoTemplate(url, 'url_encode')]);
|
|
23
29
|
|
|
24
|
-
const
|
|
25
|
-
const
|
|
26
|
-
const
|
|
30
|
+
const isActive = router ? isUrlPathMatch(router.asPath, urlToUse) : false;
|
|
31
|
+
const isActiveParent = router ? hasActiveChild(router.asPath, children) : false;
|
|
32
|
+
const shouldPreOpen = openActiveSubmenus && isActiveParent && isClickEvent;
|
|
27
33
|
const hasValidChildren = hasChildren(children);
|
|
28
34
|
|
|
35
|
+
const [displayChildren, setDisplayChildren] = useState(shouldPreOpen);
|
|
36
|
+
|
|
29
37
|
useEffect(() => {
|
|
30
|
-
if (!showMobileMenu) setDisplayChildren(false);
|
|
31
|
-
}, [
|
|
38
|
+
if (!showMobileMenu && !shouldPreOpen) setDisplayChildren(false);
|
|
39
|
+
}, [
|
|
40
|
+
children,
|
|
41
|
+
isClickEvent,
|
|
42
|
+
loadingUrl,
|
|
43
|
+
openActiveSubmenus,
|
|
44
|
+
router,
|
|
45
|
+
shouldPreOpen,
|
|
46
|
+
showMobileMenu
|
|
47
|
+
]);
|
|
32
48
|
|
|
33
49
|
useEffect(() => {
|
|
34
50
|
if (isHoverEvent) {
|
|
@@ -38,6 +54,8 @@ const MenuItemRender = ({ children, eventType, text, modifier, url, parent }) =>
|
|
|
38
54
|
|
|
39
55
|
if (loadingUrl || loadingText) return '';
|
|
40
56
|
|
|
57
|
+
const childrenDisplayClass = displayChildren ? '' : HIDDEN;
|
|
58
|
+
|
|
41
59
|
const handleItemEvent = ({ type }) => {
|
|
42
60
|
if (isHoverEvent) {
|
|
43
61
|
if (type === MOUSE_ENTER) {
|
|
@@ -55,15 +73,25 @@ const MenuItemRender = ({ children, eventType, text, modifier, url, parent }) =>
|
|
|
55
73
|
}
|
|
56
74
|
};
|
|
57
75
|
|
|
76
|
+
const menuItemLinkClassname = classnames('menu--item--link', {
|
|
77
|
+
'menu--item--link--active': isActive,
|
|
78
|
+
'menu--item--link--active-parent': isActiveParent
|
|
79
|
+
});
|
|
80
|
+
|
|
58
81
|
return (
|
|
59
82
|
<li className={modifier} onMouseEnter={handleItemEvent} onMouseLeave={handleItemEvent}>
|
|
60
83
|
<div
|
|
61
|
-
className=
|
|
84
|
+
className={menuItemLinkClassname}
|
|
62
85
|
onClick={handleMobileClick}
|
|
63
86
|
role={!urlToUse && hasValidChildren ? 'button' : undefined}
|
|
64
87
|
tabIndex={!urlToUse && hasValidChildren ? 0 : undefined}>
|
|
65
|
-
{urlToUse ?
|
|
66
|
-
|
|
88
|
+
{urlToUse ? (
|
|
89
|
+
<BlazeLink href={urlToUse}>{textToUse}</BlazeLink>
|
|
90
|
+
) : (
|
|
91
|
+
<span role="button" onClick={handleItemEvent}>
|
|
92
|
+
{textToUse}
|
|
93
|
+
</span>
|
|
94
|
+
)}
|
|
67
95
|
{hasValidChildren && (
|
|
68
96
|
<i
|
|
69
97
|
role="button"
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import isUrlPathMatch from './isUrlPathMatch';
|
|
2
|
+
import { hasChildren } from '../../../helpers';
|
|
3
|
+
|
|
4
|
+
const hasActiveChild = (path, children) =>
|
|
5
|
+
hasChildren(children) &&
|
|
6
|
+
children.props.children[1][0].props.component.items.find(menuItem =>
|
|
7
|
+
isUrlPathMatch(path, menuItem.settings.url)
|
|
8
|
+
);
|
|
9
|
+
|
|
10
|
+
export default hasActiveChild;
|
|
@@ -1,3 +1,5 @@
|
|
|
1
1
|
import injectHelperIntoTemplate from './inject-helper-into-template';
|
|
2
|
+
import isUrlPathMatch from './isUrlPathMatch';
|
|
3
|
+
import hasActiveChild from './has-active-child';
|
|
2
4
|
|
|
3
|
-
export { injectHelperIntoTemplate };
|
|
5
|
+
export { injectHelperIntoTemplate, isUrlPathMatch, hasActiveChild };
|
package/src/hooks/index.js
CHANGED
|
@@ -9,3 +9,4 @@ export { default as useBannerInsertion } from './use-banner-insertion';
|
|
|
9
9
|
export { default as useAppSyncEventHook } from './use-app-sync-event-hook';
|
|
10
10
|
export { default as usePortal } from './use-portal';
|
|
11
11
|
export { default as useFilterAggregationValues } from './use-filter-aggregation-values';
|
|
12
|
+
export { default as useAppEventHook } from './use-app-event-hook';
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { AppContext } from '@blaze-cms/nextjs-components';
|
|
2
|
+
import { useContext, useEffect, useState, useMemo } from 'react';
|
|
3
|
+
|
|
4
|
+
const useAppEventHook = ({ eventName, data, props }) => {
|
|
5
|
+
const { blazeApp } = useContext(AppContext);
|
|
6
|
+
const hasListener =
|
|
7
|
+
blazeApp && blazeApp.events && blazeApp.events.hasListeners(`plugin:page-builder:${eventName}`);
|
|
8
|
+
const [loading, setLoading] = useState(hasListener);
|
|
9
|
+
const [updatedData, setUpdatedData] = useState(null);
|
|
10
|
+
|
|
11
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
12
|
+
const stableData = useMemo(() => data, [JSON.stringify(data)]);
|
|
13
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
14
|
+
const stableProps = useMemo(() => props, [JSON.stringify(props)]);
|
|
15
|
+
|
|
16
|
+
useEffect(
|
|
17
|
+
() => {
|
|
18
|
+
async function emitEvent() {
|
|
19
|
+
// setLoading(true);
|
|
20
|
+
await blazeApp.events.emitAsync(`plugin:page-builder:${eventName}`, {
|
|
21
|
+
data: updatedData,
|
|
22
|
+
props: stableProps
|
|
23
|
+
});
|
|
24
|
+
setLoading(false);
|
|
25
|
+
}
|
|
26
|
+
if (hasListener && updatedData) emitEvent();
|
|
27
|
+
},
|
|
28
|
+
[updatedData, eventName, hasListener, blazeApp.events, stableProps]
|
|
29
|
+
);
|
|
30
|
+
|
|
31
|
+
useEffect(
|
|
32
|
+
() => {
|
|
33
|
+
setUpdatedData(stableData);
|
|
34
|
+
},
|
|
35
|
+
[stableData]
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
return { data: updatedData || stableData, loading };
|
|
39
|
+
};
|
|
40
|
+
export default useAppEventHook;
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @jest-environment jsdom
|
|
3
|
+
*/
|
|
4
|
+
import React from 'react';
|
|
5
|
+
import { render } from '@testing-library/react'; // No hooks here
|
|
6
|
+
import { useQuery } from '@apollo/client';
|
|
7
|
+
import { MainContext } from '@blaze-cms/nextjs-components';
|
|
8
|
+
import CardsRender from '../../../../../src/components/Card/CardsRender';
|
|
9
|
+
import { useAppEventHook } from '../../../../../src/hooks';
|
|
10
|
+
import { buildQueryBooster } from '../../../../../src/components/List/helpers';
|
|
11
|
+
|
|
12
|
+
// Mocks
|
|
13
|
+
jest.mock('@apollo/client', () => ({
|
|
14
|
+
useQuery: jest.fn(),
|
|
15
|
+
gql: jest.fn(str => str)
|
|
16
|
+
}));
|
|
17
|
+
|
|
18
|
+
jest.mock('../../../../../src/hooks', () => ({
|
|
19
|
+
useGetEntitySchemasAsObj: jest.fn(() => ({})),
|
|
20
|
+
useGetImages: jest.fn(() => ({ data: [], loading: false })),
|
|
21
|
+
useAppEventHook: jest.fn()
|
|
22
|
+
}));
|
|
23
|
+
|
|
24
|
+
jest.mock('../../../../../src/components/List/helpers', () => ({
|
|
25
|
+
buildQueryBooster: jest.fn()
|
|
26
|
+
}));
|
|
27
|
+
|
|
28
|
+
// Mock dynamic import
|
|
29
|
+
jest.mock('next/dynamic', () => () => {
|
|
30
|
+
const DynamicComponent = () => null;
|
|
31
|
+
DynamicComponent.displayName = 'LoadableComponent';
|
|
32
|
+
DynamicComponent.preload = jest.fn();
|
|
33
|
+
return DynamicComponent;
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
describe('CardsRender', () => {
|
|
37
|
+
const mockProps = {
|
|
38
|
+
entity: { id: '1', kind: 'Article' },
|
|
39
|
+
entities: [],
|
|
40
|
+
entityFields: {},
|
|
41
|
+
itemsToDisplay: [{ id: '1', kind: 'Article' }], // Ensure itemsToDisplay has content so query runs
|
|
42
|
+
children: null,
|
|
43
|
+
VariantComponent: () => null,
|
|
44
|
+
recencyBoostProperty: ['publishedAt'], // Example prop intended for boosting
|
|
45
|
+
parent: { itemId: '1', itemEntity: 'Article' }
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
const mockContext = {
|
|
49
|
+
isPreview: false
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
beforeEach(() => {
|
|
53
|
+
jest.clearAllMocks();
|
|
54
|
+
useQuery.mockReturnValue({ data: {}, loading: false, error: null });
|
|
55
|
+
|
|
56
|
+
// Default mock implementation for app event hook (returns data unchanged)
|
|
57
|
+
useAppEventHook.mockImplementation(({ data }) => ({ data, loading: false }));
|
|
58
|
+
|
|
59
|
+
// Default mock for booster (returns data unchanged)
|
|
60
|
+
buildQueryBooster.mockImplementation((_, data) => data);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it('should trigger useAppEventHook with correct arguments', () => {
|
|
64
|
+
// We expect variables to be derived inside CardsRender (from filterQuerySetup helper logic)
|
|
65
|
+
// Since itemsToDisplay is passed, filterQuerySetup should produce some variables.
|
|
66
|
+
|
|
67
|
+
render(
|
|
68
|
+
<MainContext.Provider value={mockContext}>
|
|
69
|
+
<CardsRender {...mockProps} />
|
|
70
|
+
</MainContext.Provider>
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
expect(useAppEventHook).toHaveBeenCalledWith(
|
|
74
|
+
expect.objectContaining({
|
|
75
|
+
eventName: 'cards:query-variables',
|
|
76
|
+
// We can't easily predict 'data' (variables) without mocking filterQuerySetup heavily,
|
|
77
|
+
// but we can check the eventName is correct.
|
|
78
|
+
props: expect.objectContaining({
|
|
79
|
+
recencyBoostProperty: mockProps.recencyBoostProperty
|
|
80
|
+
})
|
|
81
|
+
})
|
|
82
|
+
);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it('should call buildQueryBooster with the result from useAppEventHook', () => {
|
|
86
|
+
const hookData = { someVar: 'hook-modified' };
|
|
87
|
+
useAppEventHook.mockReturnValue({ data: hookData, loading: false });
|
|
88
|
+
|
|
89
|
+
render(
|
|
90
|
+
<MainContext.Provider value={mockContext}>
|
|
91
|
+
<CardsRender {...mockProps} />
|
|
92
|
+
</MainContext.Provider>
|
|
93
|
+
);
|
|
94
|
+
|
|
95
|
+
expect(buildQueryBooster).toHaveBeenCalledWith(
|
|
96
|
+
expect.objectContaining({ recencyBoostProperty: mockProps.recencyBoostProperty }),
|
|
97
|
+
hookData
|
|
98
|
+
);
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it('should pass boosted variables to useQuery', () => {
|
|
102
|
+
const boostedVars = { boosted: true };
|
|
103
|
+
buildQueryBooster.mockReturnValue(boostedVars);
|
|
104
|
+
|
|
105
|
+
render(
|
|
106
|
+
<MainContext.Provider value={mockContext}>
|
|
107
|
+
<CardsRender {...mockProps} />
|
|
108
|
+
</MainContext.Provider>
|
|
109
|
+
);
|
|
110
|
+
|
|
111
|
+
expect(useQuery).toHaveBeenCalledWith(
|
|
112
|
+
expect.any(Object), // action
|
|
113
|
+
expect.objectContaining({
|
|
114
|
+
variables: boostedVars
|
|
115
|
+
})
|
|
116
|
+
);
|
|
117
|
+
});
|
|
118
|
+
});
|
|
@@ -13,7 +13,7 @@ describe('buildQueryBooster helper function', () => {
|
|
|
13
13
|
);
|
|
14
14
|
expect(noRecencyBoostProperty).toEqual({
|
|
15
15
|
rawQueryStringified:
|
|
16
|
-
'{"function_score":{"query":{"test":"t"},"functions":[{"
|
|
16
|
+
'{"function_score":{"query":{"test":"t"},"functions":[{"exp":{"date":{"scale":"14d","decay":0.99,"offset":"7d"}}},{"weight":0.01}],"score_mode":"sum","boost_mode":"multiply"}}'
|
|
17
17
|
});
|
|
18
18
|
});
|
|
19
19
|
|
|
@@ -27,7 +27,7 @@ describe('buildQueryBooster helper function', () => {
|
|
|
27
27
|
);
|
|
28
28
|
expect(noRecencyBoostProperty).toEqual({
|
|
29
29
|
rawQueryStringified:
|
|
30
|
-
'{"function_score":{"query":{"test":"t"},"functions":[{"gauss":{"test":{"origin":"now","scale":"30d","offset":"7d","decay":0.5}}},{"
|
|
30
|
+
'{"function_score":{"query":{"test":"t"},"functions":[{"gauss":{"test":{"origin":"now","scale":"30d","offset":"7d","decay":0.5}}},{"exp":{"date":{"scale":"14d","decay":0.99,"offset":"7d"}}},{"weight":0.01}],"score_mode":"sum","boost_mode":"multiply"}}'
|
|
31
31
|
});
|
|
32
32
|
});
|
|
33
33
|
});
|
|
@@ -19,6 +19,11 @@ jest.mock('next/router', () => ({
|
|
|
19
19
|
useRouter: () => ({ asPath: '/' })
|
|
20
20
|
}));
|
|
21
21
|
|
|
22
|
+
// todo: add extra tests to support this util
|
|
23
|
+
jest.mock('../../../../../src/components/MenuItem/helpers/has-active-child', () =>
|
|
24
|
+
jest.fn(() => false)
|
|
25
|
+
);
|
|
26
|
+
|
|
22
27
|
const componentProps = {
|
|
23
28
|
id: 'id',
|
|
24
29
|
name: 'mock name',
|
|
@@ -11,14 +11,22 @@ const MENU_ITEM_CHILDREN_CLASS = 'menu--item-children';
|
|
|
11
11
|
|
|
12
12
|
let mockAsPathValue = '/';
|
|
13
13
|
|
|
14
|
-
jest.mock('next/router', () =>
|
|
15
|
-
|
|
16
|
-
|
|
14
|
+
jest.mock('next/router', () => {
|
|
15
|
+
const router = { asPath: mockAsPathValue };
|
|
16
|
+
return {
|
|
17
|
+
useRouter: () => router
|
|
18
|
+
};
|
|
19
|
+
});
|
|
17
20
|
|
|
18
21
|
jest.mock('@blaze-cms/utils-handlebars', () => ({
|
|
19
22
|
useStringTemplate: jest.fn((parent, [title]) => ({ loading: false, data: [title] }))
|
|
20
23
|
}));
|
|
21
24
|
|
|
25
|
+
// todo: add extra tests to support this util
|
|
26
|
+
jest.mock('../../../../../src/components/MenuItem/helpers/has-active-child', () =>
|
|
27
|
+
jest.fn(() => false)
|
|
28
|
+
);
|
|
29
|
+
|
|
22
30
|
describe('MenuRender component', () => {
|
|
23
31
|
it('renders menu item with link when URL is provided', () => {
|
|
24
32
|
const { getByText } = render(<MenuRender eventType="click" text="Home" url="/home" />);
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
export const mockMenuProps = {
|
|
2
|
+
props: {
|
|
3
|
+
children: [
|
|
4
|
+
false,
|
|
5
|
+
[
|
|
6
|
+
{
|
|
7
|
+
props: {
|
|
8
|
+
component: {
|
|
9
|
+
items: [
|
|
10
|
+
{
|
|
11
|
+
type: 'menuitem',
|
|
12
|
+
settings: {
|
|
13
|
+
name: 'menuitem-card',
|
|
14
|
+
url: 'card',
|
|
15
|
+
text: 'Card'
|
|
16
|
+
},
|
|
17
|
+
id: 'menuitem-card-1',
|
|
18
|
+
items: [],
|
|
19
|
+
name: 'menuitem-card-1'
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
type: 'menuitem',
|
|
23
|
+
settings: {
|
|
24
|
+
name: 'menuitem-Card',
|
|
25
|
+
|
|
26
|
+
url: 'card',
|
|
27
|
+
text: 'Card'
|
|
28
|
+
},
|
|
29
|
+
id: 'menuitem-Card-1',
|
|
30
|
+
items: [],
|
|
31
|
+
name: 'menuitem-Card-1'
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
type: 'menuitem',
|
|
35
|
+
settings: {
|
|
36
|
+
name: 'menuitem-card-Carousel',
|
|
37
|
+
url: 'card-carousel',
|
|
38
|
+
text: 'Card Carousel'
|
|
39
|
+
},
|
|
40
|
+
id: 'menuitem-card-carousel-1',
|
|
41
|
+
items: [],
|
|
42
|
+
name: 'menuitem-card-carousel-1'
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
type: 'menuitem',
|
|
46
|
+
settings: {
|
|
47
|
+
eventType: 'hover',
|
|
48
|
+
name: 'menuitem-Content-Group',
|
|
49
|
+
modifier: null,
|
|
50
|
+
url: 'content-group',
|
|
51
|
+
text: 'Content Group',
|
|
52
|
+
variant: null,
|
|
53
|
+
gtmClassName: null,
|
|
54
|
+
entities: [],
|
|
55
|
+
filterByProperty: [],
|
|
56
|
+
filterByFeatured: 'off',
|
|
57
|
+
filterBySponsored: 'off',
|
|
58
|
+
operator: 'AND',
|
|
59
|
+
filterBy: [],
|
|
60
|
+
sortProperties: []
|
|
61
|
+
},
|
|
62
|
+
id: 'menuitem-Content-Group-1',
|
|
63
|
+
items: [],
|
|
64
|
+
name: 'menuitem-Content-Group-1'
|
|
65
|
+
}
|
|
66
|
+
]
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
]
|
|
71
|
+
]
|
|
72
|
+
}
|
|
73
|
+
};
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/* eslint-disable no-unused-vars */
|
|
2
|
+
import '@testing-library/jest-dom/extend-expect';
|
|
3
|
+
import { hasActiveChild } from '../../../../../../src/components/MenuItem/helpers';
|
|
4
|
+
import { mockMenuProps } from './constants';
|
|
5
|
+
|
|
6
|
+
describe('hasActiveChild', () => {
|
|
7
|
+
const structuredClone = val => JSON.parse(JSON.stringify(val));
|
|
8
|
+
|
|
9
|
+
it('should return true for finding active menu-item child', () => {
|
|
10
|
+
const activePath = '/card';
|
|
11
|
+
const menuProps = mockMenuProps;
|
|
12
|
+
const result = !!hasActiveChild(activePath, menuProps);
|
|
13
|
+
expect(result).toBe(true);
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it('shoul return false for findng no exact match', () => {
|
|
17
|
+
const activePath = '/carousel';
|
|
18
|
+
const menuProps = mockMenuProps;
|
|
19
|
+
|
|
20
|
+
const result = !!hasActiveChild(activePath, menuProps);
|
|
21
|
+
expect(result).toBe(false);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it('should return false for empty children-items', () => {
|
|
25
|
+
const activePath = '/carousel';
|
|
26
|
+
const menuWithNoChildrenProps = (() => {
|
|
27
|
+
const obj = structuredClone(mockMenuProps);
|
|
28
|
+
obj.props.children[1][0].props.component.items = [];
|
|
29
|
+
return obj;
|
|
30
|
+
}).call();
|
|
31
|
+
|
|
32
|
+
const result = !!hasActiveChild(activePath, menuWithNoChildrenProps);
|
|
33
|
+
expect(result).toBe(false);
|
|
34
|
+
});
|
|
35
|
+
});
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import '@testing-library/jest-dom/extend-expect';
|
|
2
|
+
import { isUrlPathMatch } from '../../../../../../src/components/MenuItem/helpers';
|
|
3
|
+
|
|
4
|
+
describe('isUrlPathMatch', () => {
|
|
5
|
+
it('should return true since path and itemUrl match', () => {
|
|
6
|
+
const path = '/button';
|
|
7
|
+
const itemUrl = 'button';
|
|
8
|
+
const result = isUrlPathMatch(path, itemUrl);
|
|
9
|
+
expect(result).toBeTruthy();
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
it('should match since path and itemUrl do not match', () => {
|
|
13
|
+
const path = '/card';
|
|
14
|
+
const itemUrl = 'button';
|
|
15
|
+
const result = isUrlPathMatch(path, itemUrl);
|
|
16
|
+
expect(result).toBeFalsy();
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it('should not match since path and itemUrl is a partial match', () => {
|
|
20
|
+
const path = '/button-card';
|
|
21
|
+
const itemUrl = 'button';
|
|
22
|
+
const result = isUrlPathMatch(path, itemUrl);
|
|
23
|
+
expect(result).toBeFalsy();
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it('should not match since nested path and itemUrl is a partial match', () => {
|
|
27
|
+
const path = '/button/child';
|
|
28
|
+
const itemUrl = 'button';
|
|
29
|
+
const result = isUrlPathMatch(path, itemUrl);
|
|
30
|
+
expect(result).toBeFalsy();
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it('should match with uri-with-fragment', () => {
|
|
34
|
+
const path = '/button#dark';
|
|
35
|
+
const itemUrl = 'button';
|
|
36
|
+
const result = isUrlPathMatch(path, itemUrl);
|
|
37
|
+
expect(result).toBeTruthy();
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it('should match with uri-with-parameters', () => {
|
|
41
|
+
const path = '/button?paramater=value';
|
|
42
|
+
const itemUrl = 'button';
|
|
43
|
+
const result = isUrlPathMatch(path, itemUrl);
|
|
44
|
+
expect(result).toBeTruthy();
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it('should not match with empty itemUrl', () => {
|
|
48
|
+
const path = '/';
|
|
49
|
+
const itemUrl = null;
|
|
50
|
+
const result = isUrlPathMatch(path, itemUrl);
|
|
51
|
+
expect(result).toBeFalsy();
|
|
52
|
+
});
|
|
53
|
+
});
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { renderHook } from '@testing-library/react-hooks';
|
|
3
|
+
import { AppContext } from '@blaze-cms/nextjs-components';
|
|
4
|
+
import { useAppEventHook } from '../../../../src/hooks';
|
|
5
|
+
|
|
6
|
+
describe('useAppEventHook', () => {
|
|
7
|
+
const eventName = 'test-event';
|
|
8
|
+
const initialData = { foo: 'bar' };
|
|
9
|
+
const props = { prop1: 'val1' };
|
|
10
|
+
let blazeAppMock;
|
|
11
|
+
|
|
12
|
+
beforeEach(() => {
|
|
13
|
+
blazeAppMock = {
|
|
14
|
+
events: {
|
|
15
|
+
hasListeners: jest.fn(),
|
|
16
|
+
emitAsync: jest.fn().mockResolvedValue()
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
const wrapper = ({ children }) => (
|
|
22
|
+
<AppContext.Provider value={{ blazeApp: blazeAppMock }}>{children}</AppContext.Provider>
|
|
23
|
+
);
|
|
24
|
+
|
|
25
|
+
it('should not emit event if there are no listeners', () => {
|
|
26
|
+
blazeAppMock.events.hasListeners.mockReturnValue(false);
|
|
27
|
+
|
|
28
|
+
const { result } = renderHook(() => useAppEventHook({ eventName, data: initialData, props }), {
|
|
29
|
+
wrapper
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
expect(result.current.loading).toBe(false);
|
|
33
|
+
expect(result.current.data).toEqual(initialData); // Returns initial data immediately
|
|
34
|
+
expect(blazeAppMock.events.emitAsync).not.toHaveBeenCalled();
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it('should emit async event if listeners exist', async () => {
|
|
38
|
+
blazeAppMock.events.hasListeners.mockReturnValue(true);
|
|
39
|
+
|
|
40
|
+
const { result, waitForNextUpdate } = renderHook(
|
|
41
|
+
() => useAppEventHook({ eventName, data: initialData, props }),
|
|
42
|
+
{ wrapper }
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
// Initial state: loading is true because hasListeners is true
|
|
46
|
+
expect(result.current.loading).toBe(true);
|
|
47
|
+
|
|
48
|
+
// Wait for the useEffect chain (setUpdatedData -> emitEvent -> setLoading)
|
|
49
|
+
await waitForNextUpdate();
|
|
50
|
+
|
|
51
|
+
expect(blazeAppMock.events.emitAsync).toHaveBeenCalledWith(`plugin:page-builder:${eventName}`, {
|
|
52
|
+
data: initialData,
|
|
53
|
+
props
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
// Loading should be false after emitAsync resolves
|
|
57
|
+
expect(result.current.loading).toBe(false);
|
|
58
|
+
expect(result.current.data).toEqual(initialData);
|
|
59
|
+
});
|
|
60
|
+
});
|