@centreon/ui 24.4.37 → 24.4.39
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/package.json +1 -1
- package/src/Graph/LineChart/Legend/index.tsx +7 -8
- package/src/Graph/LineChart/LineChart.tsx +4 -1
- package/src/Graph/LineChart/index.tsx +4 -1
- package/src/api/useGraphQuery/index.ts +26 -5
- package/src/api/useGraphQuery/models.ts +5 -0
- package/src/components/CollapsibleItem/CollapsibleItem.cypress.spec.tsx +76 -0
- package/src/components/CollapsibleItem/CollapsibleItem.stories.tsx +26 -0
- package/src/components/CollapsibleItem/CollapsibleItem.tsx +43 -14
- package/src/components/CollapsibleItem/useCollapsibleItemStyles.ts +24 -1
- package/src/components/ItemComposition/Item.tsx +1 -1
- package/src/components/ItemComposition/ItemComposition.cypress.spec.tsx +116 -0
- package/src/components/ItemComposition/ItemComposition.styles.ts +8 -1
- package/src/components/ItemComposition/ItemComposition.tsx +26 -16
package/package.json
CHANGED
|
@@ -7,7 +7,6 @@ import { Box, alpha, useTheme } from '@mui/material';
|
|
|
7
7
|
|
|
8
8
|
import { useMemoComponent } from '@centreon/ui';
|
|
9
9
|
|
|
10
|
-
import { maxLinesDisplayedLegend } from '../common';
|
|
11
10
|
import { formatMetricValue } from '../../common/timeSeries';
|
|
12
11
|
import { Line, TimeValue } from '../../common/timeSeries/models';
|
|
13
12
|
import { labelAvg, labelMax, labelMin } from '../translatedLabels';
|
|
@@ -24,7 +23,7 @@ import LegendContent from './LegendContent';
|
|
|
24
23
|
interface Props {
|
|
25
24
|
base: number;
|
|
26
25
|
displayAnchor?: boolean;
|
|
27
|
-
|
|
26
|
+
limitLegend?: false | number;
|
|
28
27
|
lines: Array<Line>;
|
|
29
28
|
renderExtraComponent?: ReactNode;
|
|
30
29
|
setLinesGraph: Dispatch<SetStateAction<Array<Line> | null>>;
|
|
@@ -39,14 +38,14 @@ const MainLegend = ({
|
|
|
39
38
|
timeSeries,
|
|
40
39
|
base,
|
|
41
40
|
toggable = true,
|
|
42
|
-
|
|
41
|
+
limitLegend = false,
|
|
43
42
|
renderExtraComponent,
|
|
44
43
|
displayAnchor = true,
|
|
45
44
|
setLinesGraph,
|
|
46
45
|
xScale,
|
|
47
46
|
shouldDisplayLegendInCompactMode
|
|
48
47
|
}: Props): JSX.Element => {
|
|
49
|
-
const { classes, cx } = useStyles({ limitLegendRows });
|
|
48
|
+
const { classes, cx } = useStyles({ limitLegendRows: Boolean(limitLegend) });
|
|
50
49
|
const theme = useTheme();
|
|
51
50
|
|
|
52
51
|
const { selectMetricLine, clearHighlight, highlightLine, toggleMetricLine } =
|
|
@@ -61,8 +60,8 @@ const MainLegend = ({
|
|
|
61
60
|
|
|
62
61
|
const sortedData = sortBy(prop('metric_id'), lines);
|
|
63
62
|
|
|
64
|
-
const displayedLines =
|
|
65
|
-
? slice(0,
|
|
63
|
+
const displayedLines = limitLegend
|
|
64
|
+
? slice(0, limitLegend, sortedData)
|
|
66
65
|
: sortedData;
|
|
67
66
|
|
|
68
67
|
const getMetricValue = ({ value, unit }: GetMetricValueProps): string =>
|
|
@@ -174,7 +173,7 @@ const MainLegend = ({
|
|
|
174
173
|
const Legend = (props: Props): JSX.Element => {
|
|
175
174
|
const {
|
|
176
175
|
toggable,
|
|
177
|
-
|
|
176
|
+
limitLegend,
|
|
178
177
|
timeSeries,
|
|
179
178
|
lines,
|
|
180
179
|
base,
|
|
@@ -191,7 +190,7 @@ const Legend = (props: Props): JSX.Element => {
|
|
|
191
190
|
lines,
|
|
192
191
|
base,
|
|
193
192
|
toggable,
|
|
194
|
-
|
|
193
|
+
limitLegend,
|
|
195
194
|
displayAnchor,
|
|
196
195
|
shouldDisplayLegendInCompactMode
|
|
197
196
|
]
|
|
@@ -43,6 +43,7 @@ interface Props extends LineChartProps {
|
|
|
43
43
|
graphInterval: GraphInterval;
|
|
44
44
|
graphRef: MutableRefObject<HTMLDivElement | null>;
|
|
45
45
|
legend?: LegendModel;
|
|
46
|
+
limitLegend?: false | number;
|
|
46
47
|
marginBottom: number;
|
|
47
48
|
shapeLines?: GlobalAreaLines;
|
|
48
49
|
thresholdUnit?: string;
|
|
@@ -73,7 +74,8 @@ const LineChart = ({
|
|
|
73
74
|
curve,
|
|
74
75
|
marginBottom,
|
|
75
76
|
thresholds,
|
|
76
|
-
thresholdUnit
|
|
77
|
+
thresholdUnit,
|
|
78
|
+
limitLegend
|
|
77
79
|
}: Props): JSX.Element => {
|
|
78
80
|
const { classes } = useStyles();
|
|
79
81
|
|
|
@@ -301,6 +303,7 @@ const LineChart = ({
|
|
|
301
303
|
<Legend
|
|
302
304
|
base={baseAxis}
|
|
303
305
|
displayAnchor={displayAnchor?.displayGuidingLines ?? true}
|
|
306
|
+
limitLegend={limitLegend}
|
|
304
307
|
lines={newLines}
|
|
305
308
|
renderExtraComponent={legend?.renderExtraComponent}
|
|
306
309
|
setLinesGraph={setLinesGraph}
|
|
@@ -28,6 +28,7 @@ interface Props extends Partial<LineChartProps> {
|
|
|
28
28
|
data?: LineChartData;
|
|
29
29
|
end: string;
|
|
30
30
|
legend: LegendModel;
|
|
31
|
+
limitLegend?: false | number;
|
|
31
32
|
loading: boolean;
|
|
32
33
|
marginBottom?: number;
|
|
33
34
|
shapeLines?: GlobalAreaLines;
|
|
@@ -55,7 +56,8 @@ const WrapperLineChart = ({
|
|
|
55
56
|
curve = Curve.curveLinear,
|
|
56
57
|
marginBottom = 0,
|
|
57
58
|
thresholds,
|
|
58
|
-
thresholdUnit
|
|
59
|
+
thresholdUnit,
|
|
60
|
+
limitLegend
|
|
59
61
|
}: Props): JSX.Element | null => {
|
|
60
62
|
const { adjustedData } = useLineChartData({ data, end, start });
|
|
61
63
|
const lineChartRef = useRef<HTMLDivElement | null>(null);
|
|
@@ -95,6 +97,7 @@ const WrapperLineChart = ({
|
|
|
95
97
|
header={header}
|
|
96
98
|
height={height || responsiveHeight}
|
|
97
99
|
legend={legend}
|
|
100
|
+
limitLegend={limitLegend}
|
|
98
101
|
loading={loading}
|
|
99
102
|
marginBottom={marginBottom}
|
|
100
103
|
shapeLines={shapeLines}
|
|
@@ -1,11 +1,20 @@
|
|
|
1
1
|
import { useRef } from 'react';
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
equals,
|
|
5
|
+
flatten,
|
|
6
|
+
has,
|
|
7
|
+
includes,
|
|
8
|
+
isEmpty,
|
|
9
|
+
not,
|
|
10
|
+
pipe,
|
|
11
|
+
pluck
|
|
12
|
+
} from 'ramda';
|
|
4
13
|
import dayjs from 'dayjs';
|
|
5
14
|
|
|
6
15
|
import { LineChartData, buildListingEndpoint, useFetchQuery } from '../..';
|
|
7
16
|
|
|
8
|
-
import { Resource, WidgetResourceType } from './models';
|
|
17
|
+
import { Metric, Resource, WidgetResourceType } from './models';
|
|
9
18
|
|
|
10
19
|
interface CustomTimePeriod {
|
|
11
20
|
end: string;
|
|
@@ -14,7 +23,9 @@ interface CustomTimePeriod {
|
|
|
14
23
|
|
|
15
24
|
interface UseMetricsQueryProps {
|
|
16
25
|
baseEndpoint: string;
|
|
17
|
-
|
|
26
|
+
bypassMetricsExclusion?: boolean;
|
|
27
|
+
includeAllResources?: boolean;
|
|
28
|
+
metrics: Array<Metric>;
|
|
18
29
|
refreshCount?: number;
|
|
19
30
|
refreshInterval?: number | false;
|
|
20
31
|
resources?: Array<Resource>;
|
|
@@ -71,6 +82,7 @@ const areResourcesFullfilled = (value: Array<Resource>): boolean =>
|
|
|
71
82
|
);
|
|
72
83
|
|
|
73
84
|
const useGraphQuery = ({
|
|
85
|
+
bypassMetricsExclusion,
|
|
74
86
|
metrics,
|
|
75
87
|
resources = [],
|
|
76
88
|
baseEndpoint,
|
|
@@ -93,7 +105,7 @@ const useGraphQuery = ({
|
|
|
93
105
|
|
|
94
106
|
const definedMetrics = metrics.filter((metric) => metric);
|
|
95
107
|
const formattedDefinedMetrics = definedMetrics.map((metric) =>
|
|
96
|
-
encodeURIComponent(metric)
|
|
108
|
+
encodeURIComponent(metric.name)
|
|
97
109
|
);
|
|
98
110
|
|
|
99
111
|
const {
|
|
@@ -145,7 +157,16 @@ const useGraphQuery = ({
|
|
|
145
157
|
base: data.current.base,
|
|
146
158
|
title: ''
|
|
147
159
|
},
|
|
148
|
-
metrics:
|
|
160
|
+
metrics: bypassMetricsExclusion
|
|
161
|
+
? data.current.metrics
|
|
162
|
+
: data.current.metrics.filter(({ metric_id }) => {
|
|
163
|
+
return pipe(
|
|
164
|
+
pluck('excludedMetrics'),
|
|
165
|
+
flatten,
|
|
166
|
+
includes(metric_id),
|
|
167
|
+
not
|
|
168
|
+
)(metrics);
|
|
169
|
+
}),
|
|
149
170
|
times: data.current.times
|
|
150
171
|
}
|
|
151
172
|
: undefined;
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { CollapsibleItem, Props } from './CollapsibleItem';
|
|
2
|
+
|
|
3
|
+
const title = 'Title';
|
|
4
|
+
|
|
5
|
+
const customizedTitle = <div>Customized title</div>;
|
|
6
|
+
|
|
7
|
+
const initialize = (props: Omit<Props, 'children'>): void => {
|
|
8
|
+
cy.mount({
|
|
9
|
+
Component: <CollapsibleItem {...props}>Content</CollapsibleItem>
|
|
10
|
+
});
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
describe('CollapsibleItem', () => {
|
|
14
|
+
it('displays the component collapsed by default', () => {
|
|
15
|
+
initialize({ title });
|
|
16
|
+
|
|
17
|
+
cy.contains(title).should('be.visible');
|
|
18
|
+
cy.contains('Content').should('not.be.visible');
|
|
19
|
+
cy.get('div[aria-expanded="false"]').should('exist');
|
|
20
|
+
|
|
21
|
+
cy.makeSnapshot();
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it('displays the component expanded when the corresponding prop is set to true', () => {
|
|
25
|
+
initialize({ defaultExpanded: true, title });
|
|
26
|
+
|
|
27
|
+
cy.contains(title).should('be.visible');
|
|
28
|
+
cy.contains('Content').should('be.visible');
|
|
29
|
+
cy.get('div[aria-expanded="true"]').should('exist');
|
|
30
|
+
|
|
31
|
+
cy.makeSnapshot();
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it('displays a customized title', () => {
|
|
35
|
+
initialize({ title: customizedTitle });
|
|
36
|
+
|
|
37
|
+
cy.contains('Customized title').should('be.visible');
|
|
38
|
+
cy.get('div[aria-expanded="false"]').should('exist');
|
|
39
|
+
|
|
40
|
+
cy.makeSnapshot();
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it('displays the component as compact', () => {
|
|
44
|
+
initialize({ compact: true, title });
|
|
45
|
+
|
|
46
|
+
cy.contains(title).should('be.visible');
|
|
47
|
+
cy.get('div[aria-expanded="false"]').should('exist');
|
|
48
|
+
cy.get('div[data-compact="true"]').should('exist');
|
|
49
|
+
|
|
50
|
+
cy.makeSnapshot();
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it('displays the component as compact and expanded when the icon is clicked', () => {
|
|
54
|
+
initialize({ compact: true, title });
|
|
55
|
+
|
|
56
|
+
cy.contains(title).should('be.visible');
|
|
57
|
+
cy.get('div[aria-expanded="false"]').should('exist');
|
|
58
|
+
|
|
59
|
+
cy.get('div[aria-expanded="false"]').click();
|
|
60
|
+
|
|
61
|
+
cy.get('div[aria-expanded="true"]').should('exist');
|
|
62
|
+
cy.contains('Content').should('be.visible');
|
|
63
|
+
|
|
64
|
+
cy.makeSnapshot();
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it('displays the component as compact and a customized title', () => {
|
|
68
|
+
initialize({ compact: true, title: customizedTitle });
|
|
69
|
+
|
|
70
|
+
cy.contains('Customized title').should('be.visible');
|
|
71
|
+
cy.get('div[aria-expanded="false"]').should('exist');
|
|
72
|
+
cy.get('div[data-compact="true"]').should('exist');
|
|
73
|
+
|
|
74
|
+
cy.makeSnapshot();
|
|
75
|
+
});
|
|
76
|
+
});
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { Meta, StoryObj } from '@storybook/react';
|
|
2
2
|
|
|
3
|
+
import { Checkbox, Typography } from '@mui/material';
|
|
4
|
+
|
|
3
5
|
import { CollapsibleItem } from './CollapsibleItem';
|
|
4
6
|
|
|
5
7
|
const meta: Meta<typeof CollapsibleItem> = {
|
|
@@ -23,3 +25,27 @@ export const ExpandedByDefault: Story = {
|
|
|
23
25
|
title: 'Title'
|
|
24
26
|
}
|
|
25
27
|
};
|
|
28
|
+
|
|
29
|
+
export const customizedTitle: Story = {
|
|
30
|
+
args: {
|
|
31
|
+
children: 'Label',
|
|
32
|
+
defaultExpanded: false,
|
|
33
|
+
title: <Typography>Title</Typography>
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export const customizedTitleAndCompact: Story = {
|
|
38
|
+
args: {
|
|
39
|
+
children: 'Label',
|
|
40
|
+
compact: true,
|
|
41
|
+
defaultExpanded: false,
|
|
42
|
+
title: (
|
|
43
|
+
<div
|
|
44
|
+
style={{ alignItems: 'center', display: 'flex', flexDirection: 'row' }}
|
|
45
|
+
>
|
|
46
|
+
<Checkbox size="small" />
|
|
47
|
+
<Typography>Title compact</Typography>
|
|
48
|
+
</div>
|
|
49
|
+
)
|
|
50
|
+
}
|
|
51
|
+
};
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { ReactNode } from 'react';
|
|
2
2
|
|
|
3
|
+
import { equals, type } from 'ramda';
|
|
4
|
+
|
|
3
5
|
import {
|
|
4
6
|
AccordionDetails,
|
|
5
7
|
AccordionSummary,
|
|
@@ -10,36 +12,63 @@ import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
|
|
|
10
12
|
|
|
11
13
|
import { useCollapsibleItemStyles } from './useCollapsibleItemStyles';
|
|
12
14
|
|
|
13
|
-
interface Props {
|
|
15
|
+
export interface Props {
|
|
14
16
|
children: ReactNode;
|
|
17
|
+
compact?: boolean;
|
|
18
|
+
dataTestId?: string;
|
|
15
19
|
defaultExpanded?: boolean;
|
|
16
|
-
title: string;
|
|
20
|
+
title: string | JSX.Element;
|
|
17
21
|
}
|
|
18
22
|
|
|
19
23
|
export const CollapsibleItem = ({
|
|
20
24
|
title,
|
|
21
25
|
children,
|
|
22
|
-
defaultExpanded
|
|
26
|
+
defaultExpanded,
|
|
27
|
+
compact = false,
|
|
28
|
+
dataTestId = ''
|
|
23
29
|
}: Props): JSX.Element => {
|
|
24
|
-
const { classes } = useCollapsibleItemStyles();
|
|
30
|
+
const { classes, cx } = useCollapsibleItemStyles();
|
|
31
|
+
|
|
32
|
+
const isStringTitle = equals(type(title), 'String');
|
|
25
33
|
|
|
26
34
|
return (
|
|
27
35
|
<Accordion
|
|
28
36
|
disableGutters
|
|
29
37
|
className={classes.accordion}
|
|
38
|
+
data-compact={compact}
|
|
39
|
+
data-testid={`${dataTestId}-accordion`}
|
|
30
40
|
defaultExpanded={defaultExpanded}
|
|
31
41
|
>
|
|
32
|
-
<
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
42
|
+
<div className={classes.summaryContainer}>
|
|
43
|
+
<div className={classes.customTitle}>{!isStringTitle && title}</div>
|
|
44
|
+
<AccordionSummary
|
|
45
|
+
classes={{
|
|
46
|
+
content: cx(
|
|
47
|
+
compact
|
|
48
|
+
? classes.accordionSummaryCompactContent
|
|
49
|
+
: classes.accordionSummary
|
|
50
|
+
),
|
|
51
|
+
root: cx(
|
|
52
|
+
compact
|
|
53
|
+
? classes.accordionSummaryCompactRoot
|
|
54
|
+
: classes.accordionSummaryRoot
|
|
55
|
+
)
|
|
56
|
+
}}
|
|
57
|
+
data-testid={`${dataTestId}-summary`}
|
|
58
|
+
expandIcon={<ExpandMoreIcon color="primary" />}
|
|
59
|
+
>
|
|
60
|
+
{isStringTitle && (
|
|
61
|
+
<Typography color="primary" variant="h6">
|
|
62
|
+
{title}
|
|
63
|
+
</Typography>
|
|
64
|
+
)}
|
|
65
|
+
</AccordionSummary>
|
|
66
|
+
</div>
|
|
67
|
+
<AccordionDetails
|
|
68
|
+
className={cx(
|
|
69
|
+
compact ? classes.accordionDetailsCompact : classes.accordionDetails
|
|
70
|
+
)}
|
|
37
71
|
>
|
|
38
|
-
<Typography color="primary" variant="h6">
|
|
39
|
-
{title}
|
|
40
|
-
</Typography>
|
|
41
|
-
</AccordionSummary>
|
|
42
|
-
<AccordionDetails className={classes.accordionDetails}>
|
|
43
72
|
{children}
|
|
44
73
|
</AccordionDetails>
|
|
45
74
|
</Accordion>
|
|
@@ -4,12 +4,35 @@ export const useCollapsibleItemStyles = makeStyles()((theme) => ({
|
|
|
4
4
|
accordion: {
|
|
5
5
|
backgroundColor: 'transparent',
|
|
6
6
|
border: 'none',
|
|
7
|
-
borderBottom: `1px solid ${theme.palette.divider}
|
|
7
|
+
borderBottom: `1px solid ${theme.palette.divider}`,
|
|
8
|
+
width: '100%'
|
|
8
9
|
},
|
|
9
10
|
accordionDetails: {
|
|
10
11
|
padding: theme.spacing(0, 2, 2)
|
|
11
12
|
},
|
|
13
|
+
accordionDetailsCompact: {
|
|
14
|
+
padding: theme.spacing(0)
|
|
15
|
+
},
|
|
12
16
|
accordionSummary: {
|
|
13
17
|
margin: theme.spacing(1.5, 0)
|
|
18
|
+
},
|
|
19
|
+
accordionSummaryCompactContent: {
|
|
20
|
+
margin: theme.spacing(0, 0, 0.5)
|
|
21
|
+
},
|
|
22
|
+
accordionSummaryCompactRoot: {
|
|
23
|
+
minHeight: theme.spacing(1),
|
|
24
|
+
width: '100%'
|
|
25
|
+
},
|
|
26
|
+
accordionSummaryRoot: {
|
|
27
|
+
width: '100%'
|
|
28
|
+
},
|
|
29
|
+
customTitle: {
|
|
30
|
+
whiteSpace: 'nowrap'
|
|
31
|
+
},
|
|
32
|
+
summaryContainer: {
|
|
33
|
+
alignItems: 'center',
|
|
34
|
+
display: 'flex',
|
|
35
|
+
flexDirection: 'row',
|
|
36
|
+
width: '100%'
|
|
14
37
|
}
|
|
15
38
|
}));
|
|
@@ -7,7 +7,7 @@ import { IconButton } from '..';
|
|
|
7
7
|
import { useItemStyles } from './ItemComposition.styles';
|
|
8
8
|
|
|
9
9
|
type Props = {
|
|
10
|
-
children: Array<ReactElement>;
|
|
10
|
+
children: ReactElement | Array<ReactElement>;
|
|
11
11
|
className?: string;
|
|
12
12
|
deleteButtonHidden?: boolean;
|
|
13
13
|
labelDelete: string;
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { ItemComposition } from '.';
|
|
2
|
+
|
|
3
|
+
interface Props {
|
|
4
|
+
addButtonHidden?: boolean;
|
|
5
|
+
addbuttonDisabled?: boolean;
|
|
6
|
+
deleteButtonHidden?: boolean;
|
|
7
|
+
secondaryLabel?: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const initialize = ({
|
|
11
|
+
addButtonHidden,
|
|
12
|
+
addbuttonDisabled,
|
|
13
|
+
secondaryLabel,
|
|
14
|
+
deleteButtonHidden
|
|
15
|
+
}: Props): unknown => {
|
|
16
|
+
const addItem = cy.stub();
|
|
17
|
+
const deleteItem = cy.stub();
|
|
18
|
+
|
|
19
|
+
cy.mount({
|
|
20
|
+
Component: (
|
|
21
|
+
<ItemComposition
|
|
22
|
+
addButtonHidden={addButtonHidden}
|
|
23
|
+
addbuttonDisabled={addbuttonDisabled}
|
|
24
|
+
labelAdd="Add"
|
|
25
|
+
secondaryLabel={secondaryLabel}
|
|
26
|
+
onAddItem={addItem}
|
|
27
|
+
>
|
|
28
|
+
<ItemComposition.Item
|
|
29
|
+
deleteButtonHidden={deleteButtonHidden}
|
|
30
|
+
labelDelete="Delete"
|
|
31
|
+
onDeleteItem={deleteItem}
|
|
32
|
+
>
|
|
33
|
+
<div>Item 1</div>
|
|
34
|
+
</ItemComposition.Item>
|
|
35
|
+
<ItemComposition.Item
|
|
36
|
+
deleteButtonHidden={deleteButtonHidden}
|
|
37
|
+
labelDelete="Delete"
|
|
38
|
+
onDeleteItem={deleteItem}
|
|
39
|
+
>
|
|
40
|
+
<div>Item 2</div>
|
|
41
|
+
</ItemComposition.Item>
|
|
42
|
+
</ItemComposition>
|
|
43
|
+
)
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
return {
|
|
47
|
+
addItem,
|
|
48
|
+
deleteItem
|
|
49
|
+
};
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
describe('ItemComposition', () => {
|
|
53
|
+
it('displays the component', () => {
|
|
54
|
+
initialize({});
|
|
55
|
+
|
|
56
|
+
cy.contains('Item 1').should('be.visible');
|
|
57
|
+
cy.contains('Item 2').should('be.visible');
|
|
58
|
+
cy.findAllByTestId('Delete').should('have.length', 2);
|
|
59
|
+
cy.findByTestId('Add').should('be.enabled');
|
|
60
|
+
|
|
61
|
+
cy.makeSnapshot();
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it('displays the component with a secondary label', () => {
|
|
65
|
+
initialize({ secondaryLabel: 'Secondary label' });
|
|
66
|
+
|
|
67
|
+
cy.contains('Secondary label').should('be.visible');
|
|
68
|
+
|
|
69
|
+
cy.makeSnapshot();
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it('displays add button as hidden when the prop is set to true', () => {
|
|
73
|
+
initialize({ addbuttonDisabled: true });
|
|
74
|
+
|
|
75
|
+
cy.findByTestId('Add').should('be.disabled');
|
|
76
|
+
|
|
77
|
+
cy.makeSnapshot();
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it('does not display the add button when the prop is set to true', () => {
|
|
81
|
+
initialize({ addButtonHidden: true });
|
|
82
|
+
|
|
83
|
+
cy.findByTestId('Add').should('not.exist');
|
|
84
|
+
|
|
85
|
+
cy.makeSnapshot();
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it('does not display the delete button when the prop is set to true', () => {
|
|
89
|
+
initialize({ deleteButtonHidden: true });
|
|
90
|
+
|
|
91
|
+
cy.findByTestId('Delete').should('not.exist');
|
|
92
|
+
|
|
93
|
+
cy.makeSnapshot();
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it('calls the add function when the button is clicked', () => {
|
|
97
|
+
const { addItem } = initialize({});
|
|
98
|
+
|
|
99
|
+
cy.findByTestId('Add')
|
|
100
|
+
.click()
|
|
101
|
+
.then(() => {
|
|
102
|
+
expect(addItem).to.have.been.calledWith();
|
|
103
|
+
});
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it('calls the delete function when the button is clicked', () => {
|
|
107
|
+
const { deleteItem } = initialize({});
|
|
108
|
+
|
|
109
|
+
cy.findAllByTestId('Delete')
|
|
110
|
+
.eq(1)
|
|
111
|
+
.click()
|
|
112
|
+
.then(() => {
|
|
113
|
+
expect(deleteItem).to.have.been.calledWith();
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
});
|
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
import { makeStyles } from 'tss-react/mui';
|
|
2
2
|
|
|
3
3
|
export const useItemCompositionStyles = makeStyles()((theme) => ({
|
|
4
|
+
buttonAndSecondaryLabel: {
|
|
5
|
+
alignItems: 'center',
|
|
6
|
+
display: 'flex',
|
|
7
|
+
flexDirection: 'row',
|
|
8
|
+
justifyContent: 'space-between',
|
|
9
|
+
width: '100%'
|
|
10
|
+
},
|
|
4
11
|
itemCompositionContainer: {
|
|
5
12
|
alignItems: 'flex-start',
|
|
6
13
|
display: 'flex',
|
|
@@ -24,6 +31,6 @@ export const useItemStyles = makeStyles()((theme) => ({
|
|
|
24
31
|
width: '100%'
|
|
25
32
|
},
|
|
26
33
|
visibilityHiden: {
|
|
27
|
-
|
|
34
|
+
display: 'none'
|
|
28
35
|
}
|
|
29
36
|
}));
|
|
@@ -1,18 +1,20 @@
|
|
|
1
1
|
import { ReactElement } from 'react';
|
|
2
2
|
|
|
3
3
|
import AddIcon from '@mui/icons-material/Add';
|
|
4
|
+
import { Typography } from '@mui/material';
|
|
4
5
|
|
|
5
6
|
import { Button } from '..';
|
|
6
7
|
|
|
7
8
|
import { useItemCompositionStyles } from './ItemComposition.styles';
|
|
8
9
|
|
|
9
|
-
type Props = {
|
|
10
|
+
export type Props = {
|
|
10
11
|
IconAdd?;
|
|
11
12
|
addButtonHidden?: boolean;
|
|
12
13
|
addbuttonDisabled?: boolean;
|
|
13
14
|
children: Array<ReactElement>;
|
|
14
15
|
labelAdd: string;
|
|
15
16
|
onAddItem: () => void;
|
|
17
|
+
secondaryLabel?: string;
|
|
16
18
|
};
|
|
17
19
|
|
|
18
20
|
export const ItemComposition = ({
|
|
@@ -21,27 +23,35 @@ export const ItemComposition = ({
|
|
|
21
23
|
labelAdd,
|
|
22
24
|
addbuttonDisabled,
|
|
23
25
|
addButtonHidden,
|
|
24
|
-
IconAdd
|
|
26
|
+
IconAdd,
|
|
27
|
+
secondaryLabel
|
|
25
28
|
}: Props): JSX.Element => {
|
|
26
29
|
const { classes } = useItemCompositionStyles();
|
|
27
30
|
|
|
28
31
|
return (
|
|
29
32
|
<div className={classes.itemCompositionContainer}>
|
|
30
33
|
{children}
|
|
31
|
-
{
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
34
|
+
<div className={classes.buttonAndSecondaryLabel}>
|
|
35
|
+
{!addButtonHidden && (
|
|
36
|
+
<Button
|
|
37
|
+
aria-label={labelAdd}
|
|
38
|
+
data-testid={labelAdd}
|
|
39
|
+
disabled={addbuttonDisabled}
|
|
40
|
+
icon={IconAdd || <AddIcon />}
|
|
41
|
+
iconVariant="start"
|
|
42
|
+
size="small"
|
|
43
|
+
variant="ghost"
|
|
44
|
+
onClick={onAddItem}
|
|
45
|
+
>
|
|
46
|
+
{labelAdd}
|
|
47
|
+
</Button>
|
|
48
|
+
)}
|
|
49
|
+
{secondaryLabel && (
|
|
50
|
+
<Typography sx={{ color: 'text.secondary' }}>
|
|
51
|
+
{secondaryLabel}
|
|
52
|
+
</Typography>
|
|
53
|
+
)}
|
|
54
|
+
</div>
|
|
45
55
|
</div>
|
|
46
56
|
);
|
|
47
57
|
};
|