Skip to content

Commit 7d8f63d

Browse files
DataGrid: fix rows on collapse group and virtual scroll (T1325181) (#33151)
1 parent bd4ecf1 commit 7d8f63d

File tree

8 files changed

+608
-43
lines changed

8 files changed

+608
-43
lines changed

e2e/testcafe-devextreme/tests/dataGrid/common/scrolling.ts

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1495,6 +1495,54 @@ test.meta({ runInTheme: Themes.genericLight })('New virtual mode. Navigation to
14951495
});
14961496
});
14971497

1498+
// T1284002
1499+
test('Last group should not disappear after collapsing another subgroup with virtual scrolling, local grouping and remote operations (T1284002)', async (t) => {
1500+
const dataGrid = new DataGrid('#container');
1501+
await t.expect(dataGrid.isReady()).ok();
1502+
1503+
await dataGrid.apiCollapseRow(['Cell phones', 'Touch Screen Phones']);
1504+
1505+
const visibleRows = await dataGrid.apiGetVisibleRows();
1506+
const dataRows = visibleRows.filter((r) => r.rowType === 'data');
1507+
1508+
await t.expect(dataRows.length).eql(2, 'Computers category should still show 2 data rows');
1509+
}).before(async () => createWidget('dxDataGrid', {
1510+
height: 500,
1511+
dataSource: [
1512+
{
1513+
Id: 1, Category: 'Cell phones', Subcategory: 'Touch Screen Phones', Store: 'Europe Online Store', Date: '2024-01-10',
1514+
},
1515+
{
1516+
Id: 2, Category: 'Cell phones', Subcategory: 'Touch Screen Phones', Store: 'Europe Online Store', Date: '2024-02-15',
1517+
},
1518+
{
1519+
Id: 3, Category: 'Computers', Subcategory: 'Computers Accessories', Store: 'North America Reseller', Date: '2024-03-20',
1520+
},
1521+
{
1522+
Id: 4, Category: 'Computers', Subcategory: 'Computers Accessories', Store: 'North America Online Store', Date: '2024-04-25',
1523+
},
1524+
],
1525+
keyExpr: 'Id',
1526+
remoteOperations: {
1527+
filtering: true,
1528+
sorting: true,
1529+
paging: true,
1530+
},
1531+
scrolling: {
1532+
mode: 'virtual',
1533+
},
1534+
grouping: {
1535+
autoExpandAll: true,
1536+
},
1537+
columns: [
1538+
{ dataField: 'Id', dataType: 'number' },
1539+
{ dataField: 'Category', dataType: 'string', groupIndex: 0 },
1540+
{ dataField: 'Subcategory', dataType: 'string', groupIndex: 1 },
1541+
{ dataField: 'Store', dataType: 'string', groupIndex: 2 },
1542+
{ dataField: 'Date', dataType: 'date', format: 'yyyy-MM-dd' },
1543+
],
1544+
}));
1545+
14981546
// T1152498
14991547
// TODO: fix unstable tests
15001548
// ['infinite', 'virtual'].forEach((scrollingMode) => {
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
import { GroupingHelper, updateGroupOffsets } from '../m_grouping_expanded';
2+
import type { DataItem, GroupInfoData, GroupItemData } from '../types';
3+
4+
export interface GroupConfig {
5+
key: string;
6+
count: number;
7+
serverCount?: number;
8+
}
9+
10+
/**
11+
* Test helper that simulates the grid's expand/collapse flow for grouped data.
12+
*
13+
* Constructed from a configuration array — each entry generates a group
14+
* with `count` leaf items (ids like `"A0"`, `"A1"`, …).
15+
* When `serverCount` is provided it is used as the group's total instead of
16+
* the visible leaf count, mirroring the real server-side paging scenario.
17+
*
18+
* - `collapse(path)` — nullifies `group.items` and registers the group as collapsed.
19+
* - `expand(path)` — restores the saved children and marks the group as expanded.
20+
*
21+
* After each operation all group offsets are recalculated via `updateGroupOffsets`,
22+
* exactly matching the `changeRowExpand` flow in the production code.
23+
*/
24+
25+
export class GroupingTestHelper {
26+
public readonly grouping: GroupingHelper;
27+
28+
public readonly items: DataItem[];
29+
30+
private readonly groupsByKey: Map<string, GroupItemData>;
31+
32+
private readonly leafCounts: Map<string, number>;
33+
34+
/** Saved children snapshots so expand can restore them. */
35+
private readonly savedChildren = new Map<string, GroupItemData[] | null>();
36+
37+
constructor(groups: GroupConfig[]) {
38+
this.grouping = new GroupingHelper({ option: (): undefined => undefined });
39+
this.groupsByKey = new Map();
40+
this.leafCounts = new Map();
41+
42+
// Initialize group items
43+
this.items = groups.map(({ key, count, serverCount }) => {
44+
const items = Array.from({ length: count }, (_, i) => ({ id: `${key}${i}` }));
45+
const group = { key, items } as unknown as GroupItemData;
46+
this.groupsByKey.set(key, group);
47+
this.leafCounts.set(key, serverCount ?? count);
48+
49+
return group;
50+
});
51+
}
52+
53+
public collapse(path: string[]): void {
54+
const key = path[0];
55+
const group = this.groupsByKey.get(key);
56+
const count = this.leafCounts.get(key) ?? 0;
57+
58+
this.simulateChangeRowExpand(path, count);
59+
60+
if (group) {
61+
this.savedChildren.set(key, group.items);
62+
group.items = null;
63+
}
64+
}
65+
66+
public expand(path: string[]): void {
67+
const groupInfo = this.grouping.findGroupInfo(path);
68+
const count = groupInfo ? groupInfo.count : 0;
69+
70+
this.simulateChangeRowExpand(path, count);
71+
72+
const key = path[0];
73+
const group = this.groupsByKey.get(key);
74+
if (group) {
75+
group.items = this.savedChildren.get(key) ?? [];
76+
}
77+
}
78+
79+
private simulateChangeRowExpand(
80+
path: unknown[],
81+
count: number,
82+
): void {
83+
const groupInfo = this.grouping.findGroupInfo(path);
84+
85+
const pendingGroupInfo: GroupInfoData = {
86+
offset: groupInfo ? groupInfo.offset : -1,
87+
path: groupInfo ? groupInfo.path : path,
88+
isExpanded: groupInfo ? !groupInfo.isExpanded : false,
89+
count,
90+
};
91+
92+
updateGroupOffsets(this.grouping, this.items, [], 0, pendingGroupInfo);
93+
94+
if (groupInfo) {
95+
groupInfo.isExpanded = !groupInfo.isExpanded;
96+
groupInfo.count = count;
97+
} else if (pendingGroupInfo.offset >= 0) {
98+
this.grouping.addGroupInfo(pendingGroupInfo);
99+
}
100+
}
101+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import { GroupingHelper } from '../m_grouping_expanded';
2+
3+
/** Subclass that exposes the protected handleDataLoading method for testing. */
4+
export class GroupingHelperMock extends GroupingHelper {
5+
public testHandleDataLoading(options: unknown): void {
6+
this.handleDataLoading(options);
7+
}
8+
}

0 commit comments

Comments
 (0)