Skip to content

Commit 6a28319

Browse files
authored
T1264312: TreeList - restore selection after the search panel has cleared (DevExpress#28898)
Co-authored-by: CORP\vladimir.bushmanov <[email protected]>
1 parent f28960c commit 6a28319

File tree

9 files changed

+285
-2
lines changed

9 files changed

+285
-2
lines changed
Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
import { RequestMock } from 'testcafe';
2+
3+
export const tasksApiMock = RequestMock()
4+
.onRequestTo(/\/api\/data\?filter=%5B%22Task_Parent_ID%22%2C%22%3D%22%2C0%5D/)
5+
.respond(
6+
{
7+
data: [
8+
{
9+
Task_ID: 1,
10+
Task_Parent_ID: 0,
11+
Task_Assigned_Employee_ID: 1,
12+
Task_Assigned_Employee: null,
13+
Task_Owner_ID: 1,
14+
Task_Subject: 'Plans 2015',
15+
Task_Start_Date: '2015-01-01T00:00:00',
16+
Task_Due_Date: '2015-04-01T00:00:00',
17+
Task_Status: 'Completed',
18+
Task_Priority: 3,
19+
Task_Completion: 100,
20+
Has_Items: true,
21+
},
22+
{
23+
Task_ID: 2,
24+
Task_Parent_ID: 0,
25+
Task_Assigned_Employee_ID: 2,
26+
Task_Assigned_Employee: null,
27+
Task_Owner_ID: 1,
28+
Task_Subject: 'Health Insurance',
29+
Task_Start_Date: '2015-02-12T00:00:00',
30+
Task_Due_Date: '2015-05-30T00:00:00',
31+
Task_Status: 'In Progress',
32+
Task_Priority: 3,
33+
Task_Completion: 75,
34+
Has_Items: true,
35+
},
36+
],
37+
totalCount: -1,
38+
groupCount: -1,
39+
summary: null,
40+
},
41+
200,
42+
{
43+
'access-control-allow-origin': '*',
44+
},
45+
)
46+
.onRequestTo(/\/api\/data\?filter=%5B%22Task_Parent_ID%22%2C%22%3D%22%2C1%5D/)
47+
.respond(
48+
{
49+
data: [
50+
{
51+
Task_ID: 28,
52+
Task_Parent_ID: 1,
53+
Task_Assigned_Employee_ID: 7,
54+
Task_Assigned_Employee: null,
55+
Task_Owner_ID: 1,
56+
Task_Subject: 'Prepare 2015 Financial',
57+
Task_Start_Date: '2015-01-15T00:00:00',
58+
Task_Due_Date: '2015-01-31T00:00:00',
59+
Task_Status: 'Completed',
60+
Task_Priority: 3,
61+
Task_Completion: 100,
62+
Has_Items: false,
63+
},
64+
{
65+
Task_ID: 29,
66+
Task_Parent_ID: 1,
67+
Task_Assigned_Employee_ID: 4,
68+
Task_Assigned_Employee: null,
69+
Task_Owner_ID: 1,
70+
Task_Subject: 'Prepare 2015 Marketing Plan',
71+
Task_Start_Date: '2015-01-01T00:00:00',
72+
Task_Due_Date: '2015-01-31T00:00:00',
73+
Task_Status: 'Completed',
74+
Task_Priority: 3,
75+
Task_Completion: 100,
76+
Has_Items: true,
77+
},
78+
{
79+
Task_ID: 42,
80+
Task_Parent_ID: 1,
81+
Task_Assigned_Employee_ID: 3,
82+
Task_Assigned_Employee: null,
83+
Task_Owner_ID: 1,
84+
Task_Subject: 'Deliver R&D Plans for 2015',
85+
Task_Start_Date: '2015-03-01T00:00:00',
86+
Task_Due_Date: '2015-03-10T00:00:00',
87+
Task_Status: 'Completed',
88+
Task_Priority: 3,
89+
Task_Completion: 100,
90+
Has_Items: true,
91+
},
92+
],
93+
totalCount: -1,
94+
groupCount: -1,
95+
summary: null,
96+
},
97+
200,
98+
{
99+
'access-control-allow-origin': '*',
100+
},
101+
)
102+
.onRequestTo(/\/api\/data\?filter=%5B%5B%22Task_Subject%22%2C%22contains%22%2C%22google%22%5D%2C%22or%22%2C%5B%22Task_Status%22%2C%22contains%22%2C%22google%22%5D%5D/)
103+
.respond(
104+
{
105+
data: [
106+
{
107+
Task_ID: 32,
108+
Task_Parent_ID: 29,
109+
Task_Assigned_Employee_ID: 1,
110+
Task_Assigned_Employee: null,
111+
Task_Owner_ID: 4,
112+
Task_Subject: 'Google AdWords Strategy',
113+
Task_Start_Date: '2015-02-16T00:00:00',
114+
Task_Due_Date: '2015-02-28T00:00:00',
115+
Task_Status: 'Completed',
116+
Task_Priority: 3,
117+
Task_Completion: 100,
118+
Has_Items: false,
119+
},
120+
],
121+
totalCount: -1,
122+
groupCount: -1,
123+
summary: null,
124+
},
125+
200,
126+
{
127+
'access-control-allow-origin': '*',
128+
},
129+
)
130+
.onRequestTo(/\/api\/data\?filter=%5B%22Task_ID%22%2C%22%3D%22%2C29%5D/)
131+
.respond(
132+
{
133+
data: [
134+
{
135+
Task_ID: 29,
136+
Task_Parent_ID: 1,
137+
Task_Assigned_Employee_ID: 4,
138+
Task_Assigned_Employee: null,
139+
Task_Owner_ID: 1,
140+
Task_Subject: 'Prepare 2015 Marketing Plan',
141+
Task_Start_Date: '2015-01-01T00:00:00',
142+
Task_Due_Date: '2015-01-31T00:00:00',
143+
Task_Status: 'Completed',
144+
Task_Priority: 3,
145+
Task_Completion: 100,
146+
Has_Items: true,
147+
},
148+
],
149+
totalCount: -1,
150+
groupCount: -1,
151+
summary: null,
152+
},
153+
200,
154+
{
155+
'access-control-allow-origin': '*',
156+
},
157+
)
158+
.onRequestTo(/\/api\/data\?filter=%5B%22Task_ID%22%2C%22%3D%22%2C1%5D/)
159+
.respond(
160+
{
161+
data: [
162+
{
163+
Task_ID: 1,
164+
Task_Parent_ID: 0,
165+
Task_Assigned_Employee_ID: 1,
166+
Task_Assigned_Employee: null,
167+
Task_Owner_ID: 1,
168+
Task_Subject: 'Plans 2015',
169+
Task_Start_Date: '2015-01-01T00:00:00',
170+
Task_Due_Date: '2015-04-01T00:00:00',
171+
Task_Status: 'Completed',
172+
Task_Priority: 3,
173+
Task_Completion: 100,
174+
Has_Items: true,
175+
},
176+
],
177+
totalCount: -1,
178+
groupCount: -1,
179+
summary: null,
180+
},
181+
200,
182+
{
183+
'access-control-allow-origin': '*',
184+
},
185+
);
45.2 KB
Loading
32.8 KB
Loading
44.3 KB
Loading
31.9 KB
Loading

e2e/testcafe-devextreme/tests/treeList/selection.ts

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
import { createScreenshotsComparer } from 'devextreme-screenshot-comparer';
22
import TreeList from 'devextreme-testcafe-models/treeList';
3+
import ExpandableCell from 'devextreme-testcafe-models/treeList/expandableCell';
4+
35
import url from '../../helpers/getPageUrl';
46
import { createWidget } from '../../helpers/createWidget';
7+
import { tasksApiMock } from './apiMocks/tasksApiMock';
58

69
fixture`Selection`
710
.page(url(__dirname, '../container.html'));
@@ -57,3 +60,81 @@ test('TreeList with selection and boolean data in first column should render rig
5760
mode: 'multiple',
5861
},
5962
}));
63+
64+
// T1264312
65+
test('TreeList restore selection after the search panel has cleared', async (t) => {
66+
const { takeScreenshot, compareResults } = createScreenshotsComparer(t);
67+
const treeList = new TreeList('#container');
68+
const dataRow = treeList.getDataRow(0);
69+
const expandableCell = new ExpandableCell(dataRow.getDataCell(0));
70+
const searchBox = treeList.getSearchBox();
71+
72+
await t
73+
.click(dataRow.getSelectCheckBox())
74+
.expect(dataRow.isSelected).ok();
75+
await t
76+
.click(expandableCell.getExpandButton())
77+
.expect(expandableCell.isExpanded()).ok();
78+
await t.expect(await takeScreenshot('T1264312-selection-checked-all', treeList.element)).ok();
79+
80+
await t
81+
.click(expandableCell.getCollapseButton())
82+
.typeText(searchBox.input, 'google')
83+
.expect(expandableCell.isExpanded()).ok();
84+
await t.expect(await takeScreenshot('T1264312-selection-checked-searched', treeList.element)).ok();
85+
86+
await t
87+
.click(dataRow.getSelectCheckBox())
88+
.expect(dataRow.isSelected).notOk();
89+
await t.expect(await takeScreenshot('T1264312-selection-unchecked-searched', treeList.element)).ok();
90+
91+
await t
92+
.click(searchBox.getClearButton())
93+
.click(expandableCell.getExpandButton())
94+
.expect(expandableCell.isExpanded()).ok();
95+
await t.expect(await takeScreenshot('T1264312-selection-unchecked-all', treeList.element)).ok();
96+
97+
await t
98+
.expect(compareResults.isValid())
99+
.ok(compareResults.errorMessages());
100+
}).before(async (t) => {
101+
await t.addRequestHooks(tasksApiMock);
102+
return createWidget('dxTreeList', () => ({
103+
dataSource: (window as any).DevExpress.data.AspNet.createStore({
104+
key: 'Task_ID',
105+
loadUrl: 'https://api/data',
106+
}),
107+
selection: { mode: 'multiple', recursive: true, allowSelectAll: false },
108+
remoteOperations: { filtering: true, sorting: true, grouping: true },
109+
parentIdExpr: 'Task_Parent_ID',
110+
hasItemsExpr: 'Has_Items',
111+
searchPanel: {
112+
visible: true,
113+
},
114+
headerFilter: {
115+
visible: true,
116+
},
117+
showRowLines: true,
118+
showBorders: true,
119+
columnWidth: 180,
120+
columns: [{
121+
dataField: 'Task_Subject',
122+
width: 300,
123+
}, {
124+
dataField: 'Task_Assigned_Employee_ID',
125+
caption: 'Assigned',
126+
}, {
127+
dataField: 'Task_Status',
128+
caption: 'Status',
129+
}, {
130+
dataField: 'Task_Start_Date',
131+
caption: 'Start Date',
132+
dataType: 'date',
133+
}, {
134+
dataField: 'Task_Due_Date',
135+
caption: 'Due Date',
136+
dataType: 'date',
137+
},
138+
],
139+
}));
140+
});

packages/devextreme/js/__internal/grids/tree_list/selection/m_selection.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -260,6 +260,7 @@ const selection = (Base: ModuleType<SelectionController>) => class SelectionCont
260260
private _updateSelectionStateCore(keys, isSelected) {
261261
const dataController = this._dataController;
262262

263+
this._selectionStateByKey = {};
263264
for (let i = 0; i < keys.length; i++) {
264265
this._selectionStateByKey[keys[i]] = isSelected;
265266
// @ts-expect-error

packages/testcafe-models/textBox.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ const CLASS = {
66
input: 'dx-texteditor-input',
77
isInvalid: 'dx-invalid',
88
label: 'dx-label',
9+
buttonsContainer: 'dx-texteditor-buttons-container',
10+
clearButton: 'dx-clear-button-area',
911
};
1012
export default class TextBox extends Widget {
1113
input: Selector;
@@ -34,6 +36,10 @@ export default class TextBox extends Widget {
3436
return new ActionButton(this.element, index);
3537
}
3638

39+
getClearButton(): Selector {
40+
return this.element.find(`.${CLASS.buttonsContainer}`).find(`.${CLASS.clearButton}`);
41+
}
42+
3743
getLabel(): Selector {
3844
return this.element.find(`.${CLASS.label}`);
3945
}

packages/testcafe-models/treeList/expandableCell.ts

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@ import FocusableElement from "../internal/focusable";
22
import DataCell from "../dataGrid/data/cell";
33

44
const CLASS = {
5-
expandButton: 'dx-treelist-icon-container',
5+
actionContainer: 'dx-treelist-icon-container',
6+
expandButton: 'dx-treelist-collapsed',
7+
collapseButton: 'dx-treelist-expanded',
68
};
79

810
export default class ExpandableCell extends FocusableElement {
@@ -11,6 +13,14 @@ export default class ExpandableCell extends FocusableElement {
1113
}
1214

1315
getExpandButton(): Selector {
14-
return this.element.find(`.${CLASS.expandButton}`);
16+
return this.element.find(`.${CLASS.actionContainer}`).find(`.${CLASS.expandButton}`);
17+
}
18+
19+
getCollapseButton(): Selector {
20+
return this.element.find(`.${CLASS.actionContainer}`).find(`.${CLASS.collapseButton}`);
21+
}
22+
23+
isExpanded(): Promise<boolean> {
24+
return this.getCollapseButton().exists;
1525
}
1626
}

0 commit comments

Comments
 (0)