Skip to content

Commit 5e997a7

Browse files
author
Hector Arce De Las Heras
committed
Enhance Table component functionality and testing
This commit includes enhancements to the functionality and testing of the Table component and its associated helper functions. New test cases have been added, new types and components have been introduced, and existing components have been modified. The `truncatedValue` function has been tested with a default value of 1. The `Table` component testing has been enhanced with new imports, a new mock object, and new test cases to verify the behavior of the component when `formatListHeaderPriority` has `ROW_HEADER` and `COLUMN_HEADER`. New components `ListColumnHeaderPriority` and `ListRowHeaderPriority` have been added to handle the display of headers in a table when the priority is set to `COLUMN_HEADER` and `ROW_HEADER` respectively. The `ListRow` component has been renamed to `ListRowPriority` and modified to render expanded content as a list item. The `TableComponent` has been modified to replace the `EmptyRowHeader` with `TableEmptyColumnHeaderStyled` and to remove the `hasRowHeader` prop from the `TableRow` component. The `ITableRow` interface has been modified to remove the `hasRowHeader` property.
1 parent 5e21a2d commit 5e997a7

18 files changed

+793
-50
lines changed

src/components/table/__tests__/table.test.tsx

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import { windowMatchMedia } from '@/tests/windowMatchMedia';
1313
import { DeviceBreakpointsType, ROLES } from '@/types';
1414

1515
import { Table } from '../table';
16+
import { FormatListHeaderPriorityType } from '../types';
1617

1718
const mockBaseNoOneExpanded = {
1819
variant: 'DEFAULT',
@@ -291,6 +292,10 @@ const mockExpanded = {
291292
</div>
292293
),
293294
},
295+
rowHeader: {
296+
label: 'Row header 1',
297+
variant: 'CUSTOMIZABLE_SECONDARY',
298+
},
294299
},
295300
],
296301
accordionIcon: { icon: 'UNICORN' },
@@ -502,6 +507,72 @@ describe('Table component', () => {
502507
const results = await axe(container);
503508
const uls = screen.getAllByRole(ROLES.LIST);
504509

510+
expect(uls.length).not.toBe(0);
511+
expect(container).toHTMLValidate();
512+
expect(results).toHaveNoViolations();
513+
});
514+
it('When formatListHeaderPriority has ROW_HEADER', async () => {
515+
window.matchMedia = windowMatchMedia('onlyMobile');
516+
jest
517+
.spyOn(useMediaDevice, 'useMediaDevice')
518+
.mockImplementation(() => DeviceBreakpointsType.MOBILE);
519+
const { container } = renderProvider(
520+
<Table
521+
captionDescription={'caption description'}
522+
formatList={{
523+
[DeviceBreakpointsType.TABLET]: true,
524+
[DeviceBreakpointsType.MOBILE]: true,
525+
}}
526+
formatListHeaderPriority={FormatListHeaderPriorityType.ROW_HEADER}
527+
{...mockBase}
528+
/>
529+
);
530+
const results = await axe(container);
531+
const uls = screen.getAllByRole(ROLES.LIST);
532+
533+
expect(uls.length).not.toBe(0);
534+
expect(container).toHTMLValidate();
535+
expect(results).toHaveNoViolations();
536+
});
537+
538+
it('ROW_HEADER can have expansible content', async () => {
539+
renderProvider(
540+
<Table
541+
captionDescription={'caption description'}
542+
formatList={{
543+
[DeviceBreakpointsType.TABLET]: true,
544+
[DeviceBreakpointsType.MOBILE]: true,
545+
}}
546+
formatListHeaderPriority={FormatListHeaderPriorityType.ROW_HEADER}
547+
{...mockExpanded}
548+
/>
549+
);
550+
551+
const buttonToExpand = screen.getByLabelText('Expand current last cell');
552+
await userEvent.click(buttonToExpand);
553+
const expantedContent = screen.getByText('113456789');
554+
expect(expantedContent).toBeDefined();
555+
});
556+
557+
it('When formatListHeaderPriority has COLUMN_HEADER', async () => {
558+
window.matchMedia = windowMatchMedia('onlyMobile');
559+
jest
560+
.spyOn(useMediaDevice, 'useMediaDevice')
561+
.mockImplementation(() => DeviceBreakpointsType.MOBILE);
562+
const { container } = renderProvider(
563+
<Table
564+
captionDescription={'caption description'}
565+
formatList={{
566+
[DeviceBreakpointsType.TABLET]: true,
567+
[DeviceBreakpointsType.MOBILE]: true,
568+
}}
569+
formatListHeaderPriority={FormatListHeaderPriorityType.COLUMN_HEADER}
570+
{...mockBase}
571+
/>
572+
);
573+
const results = await axe(container);
574+
const uls = screen.getAllByRole(ROLES.LIST);
575+
505576
expect(uls.length).not.toBe(0);
506577
expect(container).toHTMLValidate();
507578
expect(results).toHaveNoViolations();

src/components/table/component/list.tsx

Lines changed: 39 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,19 +3,50 @@ import * as React from 'react';
33

44
import { Footer } from '@/components/footer';
55

6-
import { ListContainerStyled } from '../table.styled';
6+
import { ListContainerHeaderPriorityStyled, ListContainerStyled } from '../table.styled';
7+
import { FormatListHeaderPriorityType } from '../types/formatListHeaderPriority';
78
import { IListComponent } from '../types/table';
8-
import { ListRow } from './listRow';
9+
import { ListColumnHeaderPriority } from './listColumnHeaderPriority';
10+
import { ListRowHeaderPriority } from './listRowHeaderPriority';
11+
import { ListRowPriority } from './listRowPriority';
912

10-
export const List = (props: IListComponent): JSX.Element => {
13+
export const List = ({
14+
formatListHeaderPriority = FormatListHeaderPriorityType.ROW,
15+
...props
16+
}: IListComponent): JSX.Element => {
1117
const footerVariant = props.footer?.variant ?? props.styles?.footerVariant;
1218
return (
1319
<>
14-
<ListContainerStyled data-testid={`${props.dataTestId}Table`} styles={props.styles}>
15-
{props.values.map((value, indexValue) => {
16-
return <ListRow key={indexValue} {...props} index={indexValue} value={value} />;
17-
})}
18-
</ListContainerStyled>
20+
{formatListHeaderPriority === FormatListHeaderPriorityType.ROW && (
21+
<ListContainerStyled data-testid={`${props.dataTestId}Table`} styles={props.styles}>
22+
{formatListHeaderPriority === FormatListHeaderPriorityType.ROW &&
23+
props.values.map((value, indexValue) => {
24+
return (
25+
<ListRowPriority key={indexValue} {...props} index={indexValue} value={value} />
26+
);
27+
})}
28+
</ListContainerStyled>
29+
)}
30+
{formatListHeaderPriority === FormatListHeaderPriorityType.ROW_HEADER && (
31+
<ListContainerHeaderPriorityStyled
32+
data-testid={`${props.dataTestId}Table`}
33+
styles={props.styles}
34+
>
35+
{props.values.map((value, indexValue) => {
36+
return (
37+
<ListRowHeaderPriority key={indexValue} {...props} index={indexValue} value={value} />
38+
);
39+
})}
40+
</ListContainerHeaderPriorityStyled>
41+
)}
42+
{formatListHeaderPriority === FormatListHeaderPriorityType.COLUMN_HEADER && (
43+
<ListContainerHeaderPriorityStyled
44+
data-testid={`${props.dataTestId}Table`}
45+
styles={props.styles}
46+
>
47+
<ListColumnHeaderPriority {...props} />
48+
</ListContainerHeaderPriorityStyled>
49+
)}
1950
{props.footer?.content && footerVariant && (
2051
<Footer
2152
dataTestId={`${props.dataTestId}Navbar`}
Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
/* eslint-disable complexity */
2+
import * as React from 'react';
3+
4+
import { ButtonType } from '@/components/button';
5+
import { ElementOrIcon } from '@/components/elementOrIcon';
6+
import { Text, TextComponentType } from '@/components/text';
7+
8+
import { useContent } from '../hooks/useContent';
9+
import {
10+
ListHeaderItemStylesStyled,
11+
ListItemExpandedStyled,
12+
ListItemHeaderPriorityStyled,
13+
ListRowContainerHeaderPriorityStyled,
14+
ListRowHeaderPriorityStyled,
15+
TableExpandedButton,
16+
} from '../table.styled';
17+
import { IListComponent, ITableHeader, IValue } from '../types';
18+
19+
export const ListColumnHeaderPriority = (props: IListComponent): JSX.Element => {
20+
return (
21+
<React.Fragment>
22+
{props.headers
23+
// By now you can't have a divider in a column header priority
24+
.filter(header => !header.config?.hasDivider)
25+
.map((header: ITableHeader, indexHeader: number) => {
26+
return (
27+
<ListRowContainerHeaderPriorityStyled
28+
key={indexHeader}
29+
// By now you can't have a divider in a column header priority
30+
hasDivider={false}
31+
lineSeparatorLineStyles={props.lineSeparatorLineStyles}
32+
styles={props.styles}
33+
>
34+
{header.label && (
35+
<ListHeaderItemStylesStyled
36+
customAlign={
37+
header?.config?.alignHeader?.[props.device] || header?.config?.alignHeader
38+
}
39+
customBackgroundColor={header?.config?.backgroundColor}
40+
customWidth={header?.config?.width}
41+
flexWidth={header?.config?.flexWidth}
42+
hidden={props.hiddenHeaderOn?.[props.device]}
43+
lineSeparatorBottomOnHeader={props.lineSeparatorBottomOnHeader}
44+
lineSeparatorLineStyles={props.lineSeparatorLineStyles}
45+
lineSeparatorTopOnHeader={props.lineSeparatorTopOnHeader}
46+
styles={props.styles.header?.[props.headerVariant]}
47+
>
48+
<Text
49+
component={TextComponentType.SPAN}
50+
customTypography={props.styles.header?.[props.headerVariant]?.typography}
51+
dataTestId={`${props.dataTestId}ItemListRowHeader${header.label}`}
52+
>
53+
{header.label}
54+
</Text>
55+
</ListHeaderItemStylesStyled>
56+
)}
57+
<ListRowHeaderPriorityStyled styles={props.styles}>
58+
{/* If rowHeader, render it first */}
59+
{props.values.map((value, indexValue) => {
60+
return (
61+
<ListColumnHeaderValue
62+
key={indexValue}
63+
{...props}
64+
hasExpandedIcon={indexValue === 0}
65+
header={header}
66+
indexHeader={indexHeader}
67+
value={value}
68+
/>
69+
);
70+
})}
71+
</ListRowHeaderPriorityStyled>
72+
</ListRowContainerHeaderPriorityStyled>
73+
);
74+
})}
75+
</React.Fragment>
76+
);
77+
};
78+
79+
interface IListColumnHeaderValue extends IListComponent {
80+
hasExpandedIcon: boolean;
81+
header: ITableHeader;
82+
indexHeader: number;
83+
value: IValue;
84+
}
85+
86+
const ListColumnHeaderValue = (props: IListColumnHeaderValue): JSX.Element => {
87+
const {
88+
rowHeader,
89+
getExpandedAria,
90+
getValue,
91+
getBackgroundColorCellValue,
92+
handleShowExpandedContent,
93+
hasExpandedContentRow,
94+
showExpandedContent,
95+
rowVariant,
96+
} = useContent({ ...props });
97+
98+
return (
99+
<li>
100+
{rowHeader && (
101+
<ListHeaderItemStylesStyled
102+
customAlign={
103+
rowHeader?.config?.alignHeader?.[props.device] || rowHeader?.config?.alignHeader
104+
}
105+
customBackgroundColor={rowHeader?.config?.backgroundColor}
106+
customWidth={rowHeader?.config?.width}
107+
flexWidth={rowHeader?.config?.flexWidth}
108+
hidden={props.hiddenHeaderOn?.[props.device]}
109+
lineSeparatorBottomOnHeader={props.lineSeparatorBottomOnHeader}
110+
lineSeparatorLineStyles={props.lineSeparatorLineStyles}
111+
styles={props.styles.header?.[rowHeader.variant]}
112+
>
113+
<Text
114+
component={TextComponentType.SPAN}
115+
customTypography={props.styles.header?.[rowHeader.variant]?.typography}
116+
dataTestId={`${props.dataTestId}ItemListRowHeader${rowHeader.label}`}
117+
>
118+
{rowHeader.label}
119+
</Text>
120+
</ListHeaderItemStylesStyled>
121+
)}
122+
<ul>
123+
<ListItemHeaderPriorityStyled
124+
borderPosition={props.value.rowBorderPosition}
125+
customAlign={
126+
props.header?.config?.alignValue?.[props.device] ||
127+
props.header?.config?.alignValue ||
128+
props.header?.config?.alignHeader?.[props.device] ||
129+
props.header?.config?.alignHeader
130+
}
131+
customBackgroundColor={
132+
getBackgroundColorCellValue(props.header) ?? props.value.backgroundColor
133+
}
134+
customWidth={props.header?.config?.width}
135+
flexWidth={props.header?.config?.flexWidth}
136+
hasSomeExpandedContent={props.hasSomeExpandedContent}
137+
lineSeparatorLineStyles={props.lineSeparatorLineStyles}
138+
styles={props.styles.bodyRows?.[rowVariant]}
139+
>
140+
{props.hasExpandedIcon && hasExpandedContentRow && (
141+
<TableExpandedButton
142+
aria-label={getExpandedAria()}
143+
styles={props.styles.bodyRows?.[rowVariant]}
144+
type={ButtonType.BUTTON}
145+
onClick={() => {
146+
props.onExpandedContentOpen?.(
147+
!showExpandedContent,
148+
getValue(props.header),
149+
props.indexHeader
150+
);
151+
handleShowExpandedContent(!showExpandedContent);
152+
}}
153+
>
154+
<ElementOrIcon
155+
customIconStyles={props.styles.bodyRows?.[rowVariant]?.accordionIcon}
156+
rotate={showExpandedContent ? '180deg' : '0deg'}
157+
{...props.accordionIcon}
158+
/>
159+
</TableExpandedButton>
160+
)}
161+
{getValue(props.header) as string | JSX.Element}
162+
{props.hasExpandedIcon && hasExpandedContentRow && (
163+
<ListItemExpandedStyled
164+
showExpandedContent={showExpandedContent}
165+
styles={props.styles.bodyRows?.[rowVariant]}
166+
>
167+
{showExpandedContent && props.value.expandedContent
168+
? props.value.expandedContent[props.device]
169+
: props.expandedContentHelpMessage}
170+
</ListItemExpandedStyled>
171+
)}
172+
</ListItemHeaderPriorityStyled>
173+
</ul>
174+
</li>
175+
);
176+
};

0 commit comments

Comments
 (0)