@reltio/pivoting 1.4.1585 → 1.4.1586-mui5

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.
@@ -0,0 +1,59 @@
1
+ /*
2
+ object-assign
3
+ (c) Sindre Sorhus
4
+ @license MIT
5
+ */
6
+
7
+ /*! decimal.js-light v2.5.1 https://github.com/MikeMcl/decimal.js-light/LICENCE */
8
+
9
+ /**
10
+ * @license React
11
+ * react-is.production.min.js
12
+ *
13
+ * Copyright (c) Facebook, Inc. and its affiliates.
14
+ *
15
+ * This source code is licensed under the MIT license found in the
16
+ * LICENSE file in the root directory of this source tree.
17
+ */
18
+
19
+ /**
20
+ * @mui/material v5.13.4
21
+ *
22
+ * @license MIT
23
+ * This source code is licensed under the MIT license found in the
24
+ * LICENSE file in the root directory of this source tree.
25
+ */
26
+
27
+ /**
28
+ * @mui/styled-engine v5.13.2
29
+ *
30
+ * @license MIT
31
+ * This source code is licensed under the MIT license found in the
32
+ * LICENSE file in the root directory of this source tree.
33
+ */
34
+
35
+ /**
36
+ * A better abstraction over CSS.
37
+ *
38
+ * @copyright Oleg Isonen (Slobodskoi) / Isonen 2014-present
39
+ * @website https://github.com/cssinjs/jss
40
+ * @license MIT
41
+ */
42
+
43
+ /** @license React v16.13.1
44
+ * react-is.production.min.js
45
+ *
46
+ * Copyright (c) Facebook, Inc. and its affiliates.
47
+ *
48
+ * This source code is licensed under the MIT license found in the
49
+ * LICENSE file in the root directory of this source tree.
50
+ */
51
+
52
+ /** @license React v17.0.2
53
+ * react-jsx-runtime.production.min.js
54
+ *
55
+ * Copyright (c) Facebook, Inc. and its affiliates.
56
+ *
57
+ * This source code is licensed under the MIT license found in the
58
+ * LICENSE file in the root directory of this source tree.
59
+ */
@@ -0,0 +1,25 @@
1
+ {
2
+ "name": "@reltio/pivoting",
3
+ "version": "1.4.1586-mui5",
4
+ "license": "SEE LICENSE IN LICENSE FILE",
5
+ "main": "bundle.js",
6
+ "dependencies": {
7
+ "@date-io/moment": "^1.3.5",
8
+ "@reltio/components": "^1.4.1586-mui5",
9
+ "@reltio/dashboard": "^1.4.1586-mui5",
10
+ "@reltio/mdm-module": "^1.4.1586-mui5",
11
+ "@reltio/mdm-sdk": "^1.4.1586-mui5",
12
+ "classnames": "^2.2.5",
13
+ "memoize-one": "^5.1.0",
14
+ "object-hash": "^2.1.1",
15
+ "prop-types": "^15.6.2",
16
+ "ramda": "^0.28.0",
17
+ "react-redux": "^7.2.3",
18
+ "react-resize-detector": "^4.2.0",
19
+ "redux": "^4.1.2",
20
+ "ui-i18n": "bitbucket:reltio-ondemand/ui-i18n#v1.4.0"
21
+ },
22
+ "publishConfig": {
23
+ "access": "public"
24
+ }
25
+ }
@@ -0,0 +1,20 @@
1
+ const path = require('path');
2
+ const fs = require('fs');
3
+
4
+ const outputDir = path.resolve(process.cwd(), 'public');
5
+ const generatePackageJson = () => {
6
+ const packageJson = require('../../package.json');
7
+ const result = {
8
+ name: packageJson.name,
9
+ version: packageJson.version,
10
+ license: 'SEE LICENSE IN LICENSE FILE',
11
+ main: 'bundle.js',
12
+ dependencies: packageJson.dependencies,
13
+ peerDependencies: packageJson.peerDependencies,
14
+ publishConfig: packageJson.publishConfig
15
+ };
16
+ const source = JSON.stringify(result, null, 2);
17
+ fs.writeFileSync(path.join(outputDir, 'package.json'), source);
18
+ };
19
+
20
+ generatePackageJson();
@@ -0,0 +1,34 @@
1
+ import React, {memo} from 'react';
2
+ import {ReltioGridLayoutItem} from '@reltio/mdm-sdk';
3
+ import {ReltioGridLayout} from '@reltio/components';
4
+ import {PivotingView} from '../../types';
5
+ import {PivotingLayoutItem} from '../PivotingLayoutItem/PivotingLayoutItem';
6
+ import {useStyles} from './styles';
7
+
8
+ type Props = {
9
+ views: PivotingView[];
10
+ layout: ReltioGridLayoutItem[];
11
+ onLayoutChanged: (layout: ReltioGridLayoutItem[]) => void;
12
+ };
13
+
14
+ const PivotingLayout = ({views, layout, onLayoutChanged}: Props) => {
15
+ const styles = useStyles();
16
+
17
+ return (
18
+ <div className={styles.pageWrapper}>
19
+ <div className={styles.content}>
20
+ <div className={styles.mainPanel}>
21
+ <ReltioGridLayout
22
+ LayoutItem={PivotingLayoutItem}
23
+ views={views}
24
+ layout={layout}
25
+ onLayoutChanged={onLayoutChanged}
26
+ draggableHandle=".viewDraggableHandle"
27
+ />
28
+ </div>
29
+ </div>
30
+ </div>
31
+ );
32
+ };
33
+
34
+ export default memo(PivotingLayout);
@@ -0,0 +1,31 @@
1
+ import React from 'react';
2
+ import {shallow} from 'enzyme';
3
+ import {act} from 'react-dom/test-utils';
4
+ import PivotingLayout from '../PivotingLayout';
5
+ import {ReltioGridLayout} from '@reltio/components';
6
+
7
+ describe('PivotingLayout tests', () => {
8
+ const onLayoutChangedSpy = jest.fn();
9
+ const defaultProps = {
10
+ views: [
11
+ {config: {id: 'view1', title: 'title1', component: 'Component'}},
12
+ {config: {id: 'view2', title: 'title2', component: 'Component'}},
13
+ {config: {id: 'view3', title: 'title3', component: 'Component'}}
14
+ ],
15
+ layout: [
16
+ {id: 'view1', height: 1, x: 0, y: 0, width: 1},
17
+ {id: 'view2', height: 1, x: 0, y: 1, width: 1}
18
+ ],
19
+ onLayoutChanged: onLayoutChangedSpy
20
+ };
21
+
22
+ it('should render PivotingLayout', () => {
23
+ const wrapper = shallow(<PivotingLayout {...defaultProps} />);
24
+ act(() => {
25
+ wrapper.update();
26
+ });
27
+ const grid = wrapper.find(ReltioGridLayout);
28
+ expect(grid.length).toBe(1);
29
+ expect(grid.prop('views')).toEqual(defaultProps.views);
30
+ });
31
+ });
@@ -0,0 +1,30 @@
1
+ import {makeStyles} from '@mui/styles';
2
+
3
+ const configWidth = 490;
4
+
5
+ export const useStyles = makeStyles({
6
+ pageWrapper: {
7
+ height: '100%',
8
+ overflow: 'hidden',
9
+ display: 'flex',
10
+ flexDirection: 'column'
11
+ },
12
+ content: {
13
+ display: 'flex',
14
+ overflow: 'hidden',
15
+ height: '100%'
16
+ },
17
+ rightPanel: {
18
+ overflowY: 'auto',
19
+ backgroundColor: '#FFF',
20
+ borderLeft: 'solid 1px rgba(0, 0, 0, 0.12)',
21
+ width: `${configWidth}px`,
22
+ minWidth: `${configWidth}px`,
23
+ maxWidth: `${configWidth}px`
24
+ },
25
+ mainPanel: {
26
+ flex: '1 1 auto',
27
+ overflow: 'auto',
28
+ backgroundColor: '#F9F9F9'
29
+ }
30
+ });
@@ -0,0 +1,46 @@
1
+ import React from 'react';
2
+ import {ViewsFactory} from '@reltio/dashboard';
3
+ import {ReactGridLayoutItem} from '@reltio/components';
4
+ import {PivotingView} from '../../types';
5
+
6
+ type Props = {
7
+ className?: string;
8
+ isFullscreen: boolean;
9
+ style: React.CSSProperties;
10
+ onToggleFullscreen: (id: string) => void;
11
+ onRemove: (id: string) => void;
12
+ views: PivotingView[];
13
+ layoutItemConfig: ReactGridLayoutItem;
14
+ children: React.ReactNode;
15
+ };
16
+
17
+ export const PivotingLayoutItem = ({
18
+ layoutItemConfig,
19
+ views,
20
+ onToggleFullscreen,
21
+ isFullscreen,
22
+ className,
23
+ onRemove,
24
+ children: ResizeComponent,
25
+ style,
26
+ ...props
27
+ }: Props) => {
28
+ const view = views.find(({config}) => layoutItemConfig.i === config.id);
29
+ if (!view) {
30
+ return null;
31
+ }
32
+
33
+ const {config} = view;
34
+ return (
35
+ <div style={style} className={className} {...props}>
36
+ {ViewsFactory.getComponent({
37
+ type: config.component,
38
+ config,
39
+ onRemove,
40
+ onToggleFullscreen,
41
+ isFullscreen
42
+ })}
43
+ {ResizeComponent}
44
+ </div>
45
+ );
46
+ };
@@ -0,0 +1,58 @@
1
+ import React from 'react';
2
+ import {shallow} from 'enzyme';
3
+
4
+ import {PivotingLayoutItem} from '../PivotingLayoutItem';
5
+ import {ViewsFactory} from '@reltio/dashboard';
6
+
7
+ jest.mock('@reltio/dashboard', () => ({
8
+ ...jest.requireActual<Record<string, unknown>>('@reltio/dashboard'),
9
+ ViewsFactory: {getComponent: jest.fn().mockImplementation(() => <div className="component">Component</div>)}
10
+ }));
11
+
12
+ describe('PivotingLayoutItem tests', () => {
13
+ const onToggleFullscreenMock = jest.fn();
14
+ const onRemoveMock = jest.fn();
15
+ const views = [
16
+ {
17
+ config: {
18
+ id: 'test1',
19
+ title: 'test',
20
+ component: 'Component',
21
+ config: 'config'
22
+ }
23
+ }
24
+ ];
25
+ const layoutItemConfig = {
26
+ i: 'test1',
27
+ x: 0,
28
+ y: 0,
29
+ w: 100,
30
+ h: 100
31
+ };
32
+ const props = {
33
+ views,
34
+ layoutItemConfig,
35
+ isFullscreen: false,
36
+ style: {},
37
+ onRemove: onRemoveMock,
38
+ onToggleFullscreen: onToggleFullscreenMock
39
+ };
40
+
41
+ it('should create layout item based on config', () => {
42
+ const wrapper = shallow(
43
+ <PivotingLayoutItem {...props}>
44
+ <div className="child">testChild</div>
45
+ </PivotingLayoutItem>
46
+ );
47
+ expect(wrapper.find('DashboardFacet'));
48
+ expect(ViewsFactory.getComponent).toBeCalledWith({
49
+ config: views[0].config,
50
+ isFullscreen: false,
51
+ onToggleFullscreen: onToggleFullscreenMock,
52
+ onRemove: onRemoveMock,
53
+ type: views[0].config.component
54
+ });
55
+ expect(wrapper.find('.component').text()).toBe('Component');
56
+ expect(wrapper.find('.child').text()).toBe('testChild');
57
+ });
58
+ });
@@ -0,0 +1,46 @@
1
+ import Avatar from '@mui/material/Avatar';
2
+ import Typography from '@mui/material/Typography';
3
+ import {AttributeType, getLabel} from '@reltio/mdm-sdk';
4
+ import classnames from 'classnames';
5
+ import {isNil} from 'ramda';
6
+ import React from 'react';
7
+ import useStyles from './styles';
8
+
9
+ type Props = {
10
+ label: string;
11
+ attributeType: AttributeType;
12
+ className?: string;
13
+ classes?: Record<string, string>;
14
+ children?: React.ReactNode;
15
+ };
16
+ const PivotingProfileBand = ({label, attributeType, className, classes, children}: Props) => {
17
+ const styles = useStyles({classes});
18
+
19
+ if (isNil(attributeType)) {
20
+ return null;
21
+ }
22
+ return (
23
+ <div className={classnames(styles.profileBandWrapper, className)}>
24
+ <div className={styles.profileBand}>
25
+ <Avatar className={classnames(styles.profileIcon, styles.imageProfileIcon)} />
26
+ <div className={styles.profileInfo}>
27
+ <div>
28
+ <Typography variant="h6" className={styles.label}>
29
+ {getLabel(label)}
30
+ </Typography>
31
+ </div>
32
+ <div className={styles.specialInfo}>
33
+ <div className={styles.attributeType}>
34
+ <Typography className={classnames(styles.badge, className)} component="div">
35
+ {getLabel(attributeType.label)}
36
+ </Typography>
37
+ </div>
38
+ </div>
39
+ </div>
40
+ {children && <div>{children}</div>}
41
+ </div>
42
+ </div>
43
+ );
44
+ };
45
+
46
+ export default PivotingProfileBand;
@@ -0,0 +1,25 @@
1
+ import React from 'react';
2
+ import {shallow} from 'enzyme';
3
+ import PivotingProfileBand from '../PivotingProfileBand';
4
+ import Avatar from '@mui/material/Avatar';
5
+
6
+ describe('Pivoting profile band tests', () => {
7
+ const attributeType = {
8
+ name: 'FirstName',
9
+ uri: 'configuration/entityTypes/HCP/attributes/FirstName',
10
+ type: 'String'
11
+ };
12
+
13
+ it('should render main parts', () => {
14
+ const label = 'testLabel';
15
+ const component = shallow(
16
+ <PivotingProfileBand attributeType={attributeType} label={label}>
17
+ <div className="content">Content</div>
18
+ </PivotingProfileBand>
19
+ );
20
+
21
+ expect(component.find(Avatar)).toHaveLength(1);
22
+ expect(component.find('.label').text()).toBe(label);
23
+ expect(component.find('.content').text()).toBe('Content');
24
+ });
25
+ });
@@ -0,0 +1,67 @@
1
+ import {makeStyles} from '@mui/styles';
2
+
3
+ const useStyles = makeStyles((theme) => ({
4
+ profileBandWrapper: {
5
+ // especially for IE (overflow fix)
6
+ flexShrink: 0
7
+ },
8
+ profileBand: {
9
+ display: 'flex',
10
+ padding: '16px 12px 16px 16px',
11
+ alignItems: 'flex-start',
12
+ backgroundColor: '#fff',
13
+ boxShadow: 'inset 0 -1px 0 0 rgba(0,114,206,0.06), inset 0 1px 0 0 rgba(0,114,206,0.06);',
14
+
15
+ '&:after': {
16
+ content: '""',
17
+ minHeight: 'inherit',
18
+ fontSize: 0
19
+ }
20
+ },
21
+ profileIcon: {
22
+ marginRight: '12px'
23
+ },
24
+ imageProfileIcon: {
25
+ width: '48px',
26
+ height: '48px'
27
+ },
28
+ profileInfo: {
29
+ display: 'flex',
30
+ flexDirection: 'column',
31
+ flex: '1 1 50%'
32
+ },
33
+ label: {
34
+ lineHeight: '1.2',
35
+ letterSpacing: '0.25px',
36
+ color: theme.palette.text.primary,
37
+ wordBreak: 'break-word',
38
+
39
+ '&+ $specialInfo': {
40
+ marginTop: '4px'
41
+ }
42
+ },
43
+ specialInfo: {
44
+ display: 'flex',
45
+ justifyContent: 'space-between',
46
+ flexWrap: 'wrap',
47
+ width: '100%',
48
+ marginTop: '8px'
49
+ },
50
+ attributeType: {
51
+ display: 'flex',
52
+ flexWrap: 'wrap'
53
+ },
54
+ badge: {
55
+ padding: '2px 8px 3px',
56
+ borderRadius: '2px',
57
+ fontSize: '13px',
58
+ fontWeight: 500,
59
+ lineHeight: '15px',
60
+ display: 'inline-flex',
61
+ marginRight: '16px',
62
+ backgroundColor: '#0071ce',
63
+ color: '#f5f5f5'
64
+ }
65
+ }));
66
+
67
+ export default useStyles;
package/src/index.ts ADDED
@@ -0,0 +1,3 @@
1
+ import {PivotingPerspective as PivotingView} from './perspective';
2
+
3
+ export {PivotingView};
@@ -0,0 +1,78 @@
1
+ import React, {memo, useCallback, useEffect, useMemo, useState} from 'react';
2
+ import {
3
+ AttributePresentations,
4
+ convertPivotingValueToSearchFilters,
5
+ getEntityUriFromAttributeUri,
6
+ PivotingPerspectiveConfig,
7
+ findAttributeTypeByUri,
8
+ getPivotingLabel,
9
+ isNested,
10
+ Metadata,
11
+ PivotingValue,
12
+ PrimitiveValue,
13
+ removeUnsupportedLayoutItems,
14
+ ReltioGridLayoutItem
15
+ } from '@reltio/mdm-sdk';
16
+ import {ReloadFacetProvider, SearchFiltersContext} from '@reltio/components';
17
+ import PivotingLayout from '../components/PivotingLayout/PivotingLayout';
18
+ import {useStyles} from './styles';
19
+ import PivotingProfileBand from '../components/PivotingProfileBand/PivotingProfileBand';
20
+ import {useSelector} from 'react-redux';
21
+ import mdmModule from '@reltio/mdm-module';
22
+
23
+ type Props = {
24
+ config: PivotingPerspectiveConfig;
25
+ attributeTypeUri: string;
26
+ value: PivotingValue[];
27
+ };
28
+
29
+ const PivotingPerspective = ({config, attributeTypeUri, value}: Props) => {
30
+ const styles = useStyles();
31
+ const [label, setLabel] = useState('');
32
+ const metadata: Metadata = useSelector(mdmModule.selectors.getMetadata);
33
+ const attributePresentations: AttributePresentations = useSelector(mdmModule.selectors.getAttributePresentations);
34
+ const attributeType = findAttributeTypeByUri(metadata, attributeTypeUri);
35
+
36
+ useEffect(() => {
37
+ getPivotingLabel({attributeType, attributePresentations, value}).then((label) => setLabel(label));
38
+ }, [attributeType, attributePresentations, value]);
39
+ const getFirstValue = (value: PivotingValue[]) => Object.values(value[0])[0];
40
+ const entityTypeUri = getEntityUriFromAttributeUri(attributeTypeUri);
41
+
42
+ const filters = convertPivotingValueToSearchFilters({
43
+ value: isNested(attributeType) ? value : (getFirstValue(value) as PrimitiveValue),
44
+ attributeType,
45
+ entityType: entityTypeUri
46
+ });
47
+
48
+ const pivotingViews = useMemo(
49
+ () =>
50
+ config.views.map((view) => ({
51
+ config: view
52
+ })),
53
+ [config]
54
+ );
55
+
56
+ const [layout, setLayout] = useState<ReltioGridLayoutItem[]>();
57
+ useEffect(() => {
58
+ const newLayout = removeUnsupportedLayoutItems(config.layout, config.views);
59
+ setLayout(newLayout);
60
+ }, [config]);
61
+
62
+ const handleLayoutChange = useCallback((layout: ReltioGridLayoutItem[]) => setLayout(layout), []);
63
+
64
+ return (
65
+ <div className={styles.perspectiveView}>
66
+ <SearchFiltersContext.Provider value={filters}>
67
+ <PivotingProfileBand label={label} attributeType={attributeType} />
68
+ <ReloadFacetProvider>
69
+ {layout && (
70
+ <PivotingLayout layout={layout} onLayoutChanged={handleLayoutChange} views={pivotingViews} />
71
+ )}
72
+ </ReloadFacetProvider>
73
+ </SearchFiltersContext.Provider>
74
+ </div>
75
+ );
76
+ };
77
+
78
+ export default memo(PivotingPerspective);
@@ -0,0 +1,112 @@
1
+ import React from 'react';
2
+ import {act} from 'react-dom/test-utils';
3
+ import {mount, shallow} from 'enzyme';
4
+ import PivotingLayout from '../../components/PivotingLayout/PivotingLayout';
5
+ import * as reactRedux from 'react-redux';
6
+ import * as mdmSdk from '@reltio/mdm-sdk';
7
+ import mdm from '@reltio/mdm-module';
8
+ import {EmptyStub} from '@reltio/components';
9
+ import {SearchFiltersContext} from '@reltio/components';
10
+ import PivotingPerspective from '../PivotingPerspective';
11
+
12
+ jest.mock('@reltio/mdm-sdk', () => ({
13
+ ...jest.requireActual<Record<string, unknown>>('@reltio/mdm-sdk'),
14
+ getPivotingLabel: jest.fn()
15
+ }));
16
+ describe('PivotingPerspective tests', () => {
17
+ const getPivotingLabelSpy = jest.spyOn(mdmSdk, 'getPivotingLabel');
18
+
19
+ const views = [
20
+ {
21
+ component: 'DashboardFacet',
22
+ attributeUri: 'configuration/entityTypes/HCP/attributes/CountryCode',
23
+ chartType: 'bar',
24
+ id: 'MyDashboard',
25
+ title: 'Country codes'
26
+ }
27
+ ];
28
+ const attrType = {
29
+ name: 'CountryCode',
30
+ lookupCode: 'COUNTRY_CD',
31
+ uri: 'configuration/entityTypes/Organization/attributes/CountryCode',
32
+ type: 'String'
33
+ };
34
+ const props = {
35
+ config: {
36
+ id: 'com.reltio.plugins.PivotingPerspective',
37
+ layout: [{minHeight: 10, x: 2, width: 2, y: 0, id: 'MyDashboard', height: 10}],
38
+ views,
39
+ margin: undefined
40
+ },
41
+ attributeTypeUri: 'configuration/entityTypes/HCP/attributes/CountryCode',
42
+ value: [{CountryCode: 'BE'}]
43
+ };
44
+ const metadata = {
45
+ entityTypes: [{uri: 'configuration/entityTypes/HCP', name: 'HCP', attributes: [attrType]}]
46
+ };
47
+ let resolveLabel;
48
+ beforeAll(() => {
49
+ jest.spyOn(PivotingLayout, 'type').mockImplementation(EmptyStub);
50
+ jest.spyOn(reactRedux, 'useSelector').mockImplementation((selector) => selector({}));
51
+ jest.spyOn(mdm.selectors, 'getMetadata').mockReturnValue(metadata);
52
+ jest.spyOn(mdm.selectors, 'getAttributePresentations').mockReturnValue({});
53
+ getPivotingLabelSpy.mockImplementation(
54
+ () =>
55
+ new Promise((resolve) => {
56
+ resolveLabel = (value) => resolve(value);
57
+ })
58
+ );
59
+ });
60
+
61
+ beforeEach(() => {
62
+ jest.clearAllMocks();
63
+ });
64
+
65
+ it('should render initial state correctly', () => {
66
+ const wrapper = mount(<PivotingPerspective {...props} />);
67
+ expect(wrapper.find('PivotingProfileBand')).toHaveLength(1);
68
+ expect(wrapper.find('PivotingLayout')).toHaveLength(0);
69
+ });
70
+
71
+ it('should pass correct props to profile band', async () => {
72
+ const wrapper = mount(<PivotingPerspective {...props} />);
73
+ await act(async () => {
74
+ resolveLabel('testLabel');
75
+ });
76
+ wrapper.update();
77
+ const profileBand = wrapper.find('PivotingProfileBand');
78
+ expect(profileBand).toHaveLength(1);
79
+ expect(profileBand.prop('label')).toEqual('testLabel');
80
+ expect(profileBand.prop('attributeType')).toEqual(attrType);
81
+ });
82
+
83
+ it('should pass correct props to context', async () => {
84
+ const wrapper = shallow(<PivotingPerspective {...props} />);
85
+ wrapper.update();
86
+ const contextProvider = wrapper.find(SearchFiltersContext.Provider);
87
+ expect(contextProvider.prop('value')).toEqual([
88
+ {fieldName: 'type', filter: 'equals', values: ['configuration/entityTypes/HCP']},
89
+ {fieldName: 'attributes.CountryCode', filter: 'equals', values: ['BE']}
90
+ ]);
91
+ });
92
+
93
+ it('should render dashboard layout correctly', async () => {
94
+ const wrapper = mount(<PivotingPerspective {...props} />);
95
+ wrapper.update();
96
+ expect(wrapper.find(PivotingLayout).props()).toMatchObject({
97
+ views: views.map((view) => ({config: view})),
98
+ layout: props.config.layout
99
+ });
100
+ });
101
+
102
+ it('should apply layout on change', async () => {
103
+ const wrapper = mount(<PivotingPerspective {...props} />);
104
+ wrapper.update();
105
+ const newLayout = [{minHeight: 10, x: 0, width: 2, y: 2, id: 'MyDashboard', height: 10}];
106
+ act(() => {
107
+ wrapper.find(PivotingLayout).prop('onLayoutChanged')(newLayout);
108
+ });
109
+ wrapper.update();
110
+ expect(wrapper.find(PivotingLayout).prop('layout')).toEqual(newLayout);
111
+ });
112
+ });