Skip to content

Commit 9527db1

Browse files
authored
feat(AnalyticalTable - useIndeterminateRowSelection): add onIndeterminateChange param (#3797)
Fixes #3775 Closes #3612
1 parent 5fe2022 commit 9527db1

File tree

6 files changed

+224
-1606
lines changed

6 files changed

+224
-1606
lines changed

packages/main/src/components/AnalyticalTable/AnalyticalTable.cy.tsx

Lines changed: 125 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { useEffect, useRef, useState } from 'react';
2-
import { AnalyticalTable, Button, Input } from '../..';
2+
import { AnalyticalTable, AnalyticalTableHooks, Button, Input } from '../..';
33
import { TableSelectionMode, TableVisibleRowCountMode, ValueState } from '../../enums';
44

55
const generateMoreData = (count) => {
@@ -431,6 +431,130 @@ describe('AnalyticalTable', () => {
431431
cy.findByTestId('selectedFlatRowsLength').should('have.text', '1');
432432
cy.findByTestId('isSelected').should('have.text', 'false');
433433
});
434+
435+
it('useIndeterminateRowSelection - select subRows', () => {
436+
const indeterminateChange = cy.spy().as('onIndeterminateChangeSpy');
437+
cy.mount(
438+
<AnalyticalTable
439+
selectionMode={TableSelectionMode.MultiSelect}
440+
data={dataTree}
441+
columns={columns}
442+
isTreeTable
443+
tableHooks={[AnalyticalTableHooks.useIndeterminateRowSelection(indeterminateChange)]}
444+
reactTableOptions={{ selectSubRows: true }}
445+
/>
446+
);
447+
448+
// select all
449+
cy.get('#__ui5wcr__internal_selection_column').click();
450+
451+
// expand
452+
cy.get('[aria-rowindex="2"] > [aria-colindex="2"] > [title="Expand Node"] > [ui5-icon]').click();
453+
cy.get('[aria-rowindex="3"] > [aria-colindex="2"] > [title="Expand Node"] > [ui5-icon]').click();
454+
cy.get('[aria-rowindex="4"] > [aria-colindex="2"] > [title="Expand Node"] > [ui5-icon]').click();
455+
456+
// deselect row
457+
cy.findByText('Wiggins Cotton').click();
458+
cy.get('@onIndeterminateChangeSpy').should('have.callCount', 1);
459+
460+
cy.get('[aria-rowindex="4"] > [aria-colindex="1"] [ui5-checkbox]').should('have.attr', 'indeterminate', 'true');
461+
cy.get('[aria-rowindex="3"] > [aria-colindex="1"] [ui5-checkbox]').should('have.attr', 'indeterminate', 'true');
462+
cy.get('[aria-rowindex="2"] > [aria-colindex="1"] [ui5-checkbox]').should('have.attr', 'indeterminate', 'true');
463+
cy.get('#__ui5wcr__internal_selection_column [ui5-checkbox]').should('have.attr', 'indeterminate', 'true');
464+
465+
// deselect all
466+
cy.get('#__ui5wcr__internal_selection_column').click();
467+
cy.get('#__ui5wcr__internal_selection_column').click();
468+
cy.get('@onIndeterminateChangeSpy').should('have.callCount', 2);
469+
470+
// select leaf row
471+
cy.findByText('Wiggins Cotton').click();
472+
cy.get('@onIndeterminateChangeSpy').should('have.callCount', 3);
473+
474+
cy.get('[aria-rowindex="4"] > [aria-colindex="1"] [ui5-checkbox]').should('have.attr', 'indeterminate', 'true');
475+
cy.get('[aria-rowindex="3"] > [aria-colindex="1"] [ui5-checkbox]').should('have.attr', 'indeterminate', 'true');
476+
cy.get('[aria-rowindex="2"] > [aria-colindex="1"] [ui5-checkbox]').should('have.attr', 'indeterminate', 'true');
477+
cy.get('#__ui5wcr__internal_selection_column [ui5-checkbox]').should('have.attr', 'indeterminate', 'true');
478+
479+
// deselect all
480+
cy.get('#__ui5wcr__internal_selection_column').click();
481+
cy.get('#__ui5wcr__internal_selection_column').click();
482+
cy.get('@onIndeterminateChangeSpy').should('have.callCount', 4);
483+
484+
// select row with subRows
485+
cy.findByText('Diann Alvarado').click();
486+
cy.get('@onIndeterminateChangeSpy').should('have.callCount', 5);
487+
488+
cy.get('[aria-rowindex="4"] > [aria-colindex="1"]').should('have.attr', 'aria-selected', 'true');
489+
cy.get('[aria-rowindex="5"] > [aria-colindex="1"]').should('have.attr', 'aria-selected', 'true');
490+
cy.get('[aria-rowindex="6"] > [aria-colindex="1"]').should('have.attr', 'aria-selected', 'true');
491+
cy.get('[aria-rowindex="7"] > [aria-colindex="1"]').should('have.attr', 'aria-selected', 'true');
492+
cy.get('[aria-rowindex="8"] > [aria-colindex="1"]').should('have.attr', 'aria-selected', 'true');
493+
cy.get('[aria-rowindex="3"] > [aria-colindex="1"] [ui5-checkbox]').should('have.attr', 'indeterminate', 'true');
494+
cy.get('[aria-rowindex="2"] > [aria-colindex="1"] [ui5-checkbox]').should('have.attr', 'indeterminate', 'true');
495+
cy.get('#__ui5wcr__internal_selection_column [ui5-checkbox]').should('have.attr', 'indeterminate', 'true');
496+
});
497+
498+
it('useIndeterminateRowSelection', () => {
499+
const indeterminateChange = cy.spy().as('onIndeterminateChangeSpy');
500+
cy.mount(
501+
<AnalyticalTable
502+
selectionMode={TableSelectionMode.MultiSelect}
503+
data={dataTree}
504+
columns={columns}
505+
isTreeTable
506+
tableHooks={[AnalyticalTableHooks.useIndeterminateRowSelection(indeterminateChange)]}
507+
/>
508+
);
509+
// select all
510+
cy.get('#__ui5wcr__internal_selection_column').click();
511+
512+
// expand
513+
cy.get('[aria-rowindex="2"] > [aria-colindex="2"] > [title="Expand Node"] > [ui5-icon]').click();
514+
cy.get('[aria-rowindex="3"] > [aria-colindex="2"] > [title="Expand Node"] > [ui5-icon]').click();
515+
cy.get('[aria-rowindex="4"] > [aria-colindex="2"] > [title="Expand Node"] > [ui5-icon]').click();
516+
517+
// deselect row
518+
cy.findByText('Wiggins Cotton').click();
519+
cy.get('@onIndeterminateChangeSpy').should('have.callCount', 1);
520+
521+
cy.get('[aria-rowindex="4"] > [aria-colindex="1"] [ui5-checkbox]').should('have.attr', 'indeterminate', 'true');
522+
cy.get('[aria-rowindex="3"] > [aria-colindex="1"] [ui5-checkbox]').should('have.attr', 'indeterminate', 'true');
523+
cy.get('[aria-rowindex="2"] > [aria-colindex="1"] [ui5-checkbox]').should('have.attr', 'indeterminate', 'true');
524+
cy.get('#__ui5wcr__internal_selection_column [ui5-checkbox]').should('have.attr', 'indeterminate', 'true');
525+
526+
// deselect all
527+
cy.get('#__ui5wcr__internal_selection_column').click();
528+
cy.get('#__ui5wcr__internal_selection_column').click();
529+
cy.get('@onIndeterminateChangeSpy').should('have.callCount', 2);
530+
531+
// select leaf row
532+
cy.findByText('Wiggins Cotton').click();
533+
cy.get('@onIndeterminateChangeSpy').should('have.callCount', 3);
534+
535+
cy.get('[aria-rowindex="4"] > [aria-colindex="1"] [ui5-checkbox]').should('have.attr', 'indeterminate', 'true');
536+
cy.get('[aria-rowindex="3"] > [aria-colindex="1"] [ui5-checkbox]').should('have.attr', 'indeterminate', 'true');
537+
cy.get('[aria-rowindex="2"] > [aria-colindex="1"] [ui5-checkbox]').should('have.attr', 'indeterminate', 'true');
538+
cy.get('#__ui5wcr__internal_selection_column [ui5-checkbox]').should('have.attr', 'indeterminate', 'true');
539+
540+
// deselect all
541+
cy.get('#__ui5wcr__internal_selection_column').click();
542+
cy.get('#__ui5wcr__internal_selection_column').click();
543+
cy.get('@onIndeterminateChangeSpy').should('have.callCount', 4);
544+
545+
// select row with subRows
546+
cy.findByText('Diann Alvarado').click();
547+
cy.get('@onIndeterminateChangeSpy').should('have.callCount', 5);
548+
549+
cy.get('[aria-rowindex="4"] > [aria-colindex="1"]').should('have.attr', 'aria-selected', 'true');
550+
cy.get('[aria-rowindex="5"] > [aria-colindex="1"]').should('have.attr', 'aria-selected', 'false');
551+
cy.get('[aria-rowindex="6"] > [aria-colindex="1"]').should('have.attr', 'aria-selected', 'false');
552+
cy.get('[aria-rowindex="7"] > [aria-colindex="1"]').should('have.attr', 'aria-selected', 'false');
553+
cy.get('[aria-rowindex="8"] > [aria-colindex="1"]').should('have.attr', 'aria-selected', 'false');
554+
cy.get('[aria-rowindex="3"] > [aria-colindex="1"] [ui5-checkbox]').should('have.attr', 'indeterminate', 'true');
555+
cy.get('[aria-rowindex="2"] > [aria-colindex="1"] [ui5-checkbox]').should('have.attr', 'indeterminate', 'true');
556+
cy.get('#__ui5wcr__internal_selection_column [ui5-checkbox]').should('have.attr', 'indeterminate', 'true');
557+
});
434558
});
435559

436560
const columns = [

packages/main/src/components/AnalyticalTable/AnalyticalTable.stories.mdx

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -474,6 +474,12 @@ const data = [
474474
The `useIndeterminateRowSelection` plugin hook allows marking parent rows as indeterminate when a child row is selected.
475475
When using this hook, it is recommended to also select all sub-rows when selecting a row. (`reactTableOptions={{ selectSubRows: true }}`)
476476
477+
<MessageStrip hideCloseButton>
478+
The indeterminate state has a higher priority than the selected state. Therefore, a row can be selected and
479+
indeterminate at the same time. This can for example happen, if `selectSubRows: true` is set and a row with sub-rows
480+
is selected and then a sub-row is unselected.
481+
</MessageStrip>
482+
477483
_ES6 Import: `import { AnalyticalTableHooks } from '@ui5/webcomponents-react';`_
478484
479485
<Canvas withSource="none">
@@ -519,6 +525,17 @@ _ES6 Import: `import { AnalyticalTableHooks } from '@ui5/webcomponents-react';`_
519525
/>
520526
```
521527
528+
#### Optional function parameter
529+
530+
The plugin hook allows passing a callback as parameter with the following structure:
531+
532+
```js
533+
({indeterminateRowsById, tableInstance}) => void;
534+
```
535+
536+
The callback is fired, every time the internal `indeterminateRows` state is changed.
537+
The event parameter is an object, with all indeterminate rows by id (e.g. `{"0.1":true}`) and the table instance.
538+
522539
### Manual control of selected lines
523540
524541
The `useManualRowSelect` plugin hook allows controlling the selected rows manually. It accepts a parameter (`manualRowSelectedKey`) which defaults to `isSelected`.

packages/main/src/components/AnalyticalTable/AnalyticalTable.test.tsx

Lines changed: 1 addition & 180 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,7 @@ import React, { createRef } from 'react';
44
import { TableSelectionBehavior, TableSelectionMode, ValueState } from '../../enums';
55
import { Button } from '../../webComponents';
66
import { AnalyticalTable } from './index';
7-
import {
8-
useIndeterminateRowSelection,
9-
useManualRowSelect,
10-
useRowDisableSelection
11-
} from './pluginHooks/AnalyticalTableHooks';
7+
import { useManualRowSelect, useRowDisableSelection } from './pluginHooks/AnalyticalTableHooks';
128

139
const columns = [
1410
{
@@ -153,101 +149,6 @@ const moreData = [
153149
}
154150
];
155151

156-
const dataTreeUnique = [
157-
{
158-
name: 'Fra',
159-
age: 40,
160-
friend: {
161-
name: 'MAR',
162-
age: 28
163-
},
164-
subRows: [
165-
{
166-
name: 'asd',
167-
age: 40,
168-
friend: {
169-
name: 'longlonglong',
170-
age: 28
171-
},
172-
subRows: [
173-
{
174-
name: 'ABC',
175-
age: 40,
176-
friend: {
177-
name: 'DEF',
178-
age: 28
179-
},
180-
subRows: [
181-
{
182-
name: 'GHijkl',
183-
age: 40,
184-
friend: {
185-
name: 'mnop',
186-
age: 28
187-
},
188-
subRows: [
189-
{
190-
name: 'Marc',
191-
age: 40,
192-
friend: {
193-
name: 'Peter',
194-
age: 28
195-
},
196-
subRows: [
197-
{
198-
name: 'Paula',
199-
age: 40,
200-
friend: {
201-
name: 'May',
202-
age: 28
203-
}
204-
}
205-
]
206-
}
207-
]
208-
}
209-
]
210-
}
211-
]
212-
},
213-
{
214-
name: 'Charles',
215-
age: 40,
216-
friend: {
217-
name: 'Leela',
218-
age: 28
219-
}
220-
},
221-
{
222-
name: 'Farnsworth',
223-
age: 40,
224-
friend: {
225-
name: 'Fry',
226-
age: 28
227-
},
228-
subRows: [
229-
{
230-
name: 'Zoidberg',
231-
age: 40,
232-
friend: {
233-
name: 'Bender',
234-
age: 28
235-
}
236-
}
237-
]
238-
}
239-
]
240-
},
241-
{
242-
name: 'Amy',
243-
age: 20,
244-
friend: {
245-
name: 'Philip',
246-
age: 50
247-
}
248-
}
249-
];
250-
251152
const dataTree = [
252153
{
253154
name: 'Fra',
@@ -1048,86 +949,6 @@ describe('AnalyticalTable', () => {
1048949
expect(asFragment()).toMatchSnapshot();
1049950
});
1050951

1051-
test('plugin hook: useIndeterminateRowSelection', async () => {
1052-
const cb = jest.fn();
1053-
const { rerender, getAllByTitle, getByTitle, getByText, container, unmount, asFragment } = render(
1054-
<AnalyticalTable
1055-
data={dataTreeUnique}
1056-
columns={columns}
1057-
selectionMode={TableSelectionMode.MultiSelect}
1058-
isTreeTable={true}
1059-
tableHooks={[useIndeterminateRowSelection()]}
1060-
onRowSelect={cb}
1061-
selectedRowIds={{ '1': true }}
1062-
/>
1063-
);
1064-
const checkboxes = container.querySelectorAll('ui5-checkbox');
1065-
1066-
expect(checkboxes[0]).toHaveAttribute('indeterminate', 'true');
1067-
expect(checkboxes[1]).not.toHaveAttribute('indeterminate', 'true');
1068-
expect(checkboxes[1]).not.toHaveAttribute('checked', 'true');
1069-
expect(checkboxes[2]).not.toHaveAttribute('indeterminate', 'true');
1070-
expect(checkboxes[2]).toHaveAttribute('checked', 'true');
1071-
1072-
fireEvent.click(getByText('Amy'));
1073-
expect(cb).toHaveBeenCalled();
1074-
1075-
expect(checkboxes[0]).not.toHaveAttribute('indeterminate', 'true');
1076-
expect(checkboxes[1]).not.toHaveAttribute('indeterminate', 'true');
1077-
expect(checkboxes[2]).not.toHaveAttribute('indeterminate', 'true');
1078-
expect(checkboxes[0]).not.toHaveAttribute('checked', 'true');
1079-
expect(checkboxes[1]).not.toHaveAttribute('checked', 'true');
1080-
expect(checkboxes[2]).not.toHaveAttribute('checked', 'true');
1081-
1082-
// expand all rows
1083-
fireEvent.click(getByTitle('Expand Node').querySelector('[ui5-icon]'));
1084-
fireEvent.click(getAllByTitle('Expand Node')[0].querySelector('[ui5-icon]'));
1085-
fireEvent.click(getAllByTitle('Expand Node')[0].querySelector('[ui5-icon]'));
1086-
fireEvent.click(getAllByTitle('Expand Node')[0].querySelector('[ui5-icon]'));
1087-
fireEvent.click(getAllByTitle('Expand Node')[0].querySelector('[ui5-icon]'));
1088-
fireEvent.click(getAllByTitle('Expand Node')[0].querySelector('[ui5-icon]'));
1089-
1090-
fireEvent.click(getByText('GHijkl'));
1091-
1092-
Array.from(container.querySelectorAll('ui5-checkbox'))
1093-
.slice(0, 4)
1094-
.forEach((item, index) => {
1095-
expect(item).toHaveAttribute('indeterminate', 'true');
1096-
});
1097-
expect(container.querySelectorAll('ui5-checkbox')[4]).not.toHaveAttribute('indeterminate', 'true');
1098-
expect(container.querySelectorAll('ui5-checkbox')[4]).toHaveAttribute('checked', 'true');
1099-
1100-
Array.from(container.querySelectorAll('ui5-checkbox'))
1101-
.slice(5)
1102-
.forEach((item, index) => {
1103-
expect(item).not.toHaveAttribute('indeterminate', 'true');
1104-
expect(item).not.toHaveAttribute('checked', 'true');
1105-
});
1106-
1107-
expect(asFragment()).toMatchSnapshot();
1108-
1109-
unmount();
1110-
const { container: newContainer } = render(
1111-
<AnalyticalTable
1112-
data={dataTreeUnique}
1113-
columns={columns}
1114-
selectionMode={TableSelectionMode.MultiSelect}
1115-
isTreeTable={true}
1116-
tableHooks={[useIndeterminateRowSelection()]}
1117-
onRowSelect={cb}
1118-
selectedRowIds={{
1119-
'0.0.0.0.0': true,
1120-
'1': true
1121-
}}
1122-
/>
1123-
);
1124-
1125-
expect(newContainer.querySelectorAll('ui5-checkbox')[0]).toHaveAttribute('indeterminate', 'true');
1126-
expect(newContainer.querySelectorAll('ui5-checkbox')[1]).toHaveAttribute('indeterminate', 'true');
1127-
expect(newContainer.querySelectorAll('ui5-checkbox')[2]).not.toHaveAttribute('indeterminate', 'true');
1128-
expect(newContainer.querySelectorAll('ui5-checkbox')[2]).toHaveAttribute('checked', 'true');
1129-
});
1130-
1131952
test('body scroll', () => {
1132953
const data100 = new Array(100).fill({
1133954
name: 'Chris P.',

0 commit comments

Comments
 (0)