Skip to content

Commit 39dc044

Browse files
committed
GridCore: add tests for new functionality
1 parent 34b3519 commit 39dc044

File tree

6 files changed

+438
-16
lines changed

6 files changed

+438
-16
lines changed
Lines changed: 353 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,353 @@
1+
import {
2+
afterEach, beforeEach, describe, expect, it, jest,
3+
} from '@jest/globals';
4+
import $ from '@js/core/renderer';
5+
import type { ToolbarItem } from '@ts/grids/new/grid_core/toolbar/types';
6+
7+
import {
8+
afterTest,
9+
beforeTest,
10+
createDataGrid,
11+
} from '../../__tests__/__mock__/helpers/utils';
12+
13+
const getHeaderPanel = (instance): any => instance.getView('headerPanel');
14+
15+
describe('HeaderPanel', () => {
16+
beforeEach(() => {
17+
beforeTest();
18+
});
19+
afterEach(afterTest);
20+
21+
describe('addToolbarItem', () => {
22+
it('should add a toolbar item and make header panel visible', async () => {
23+
const { instance } = await createDataGrid({
24+
dataSource: [{ id: 1 }],
25+
});
26+
const headerPanel = getHeaderPanel(instance);
27+
28+
expect(headerPanel.isVisible()).toBe(false);
29+
30+
headerPanel.addToolbarItem('customButton', {
31+
widget: 'dxButton',
32+
options: { text: 'Custom' },
33+
location: 'after',
34+
name: 'customButton',
35+
});
36+
jest.runAllTimers();
37+
headerPanel.render();
38+
39+
expect(headerPanel.isVisible()).toBe(true);
40+
const $toolbar = $(instance.element()).find('.dx-toolbar');
41+
expect($toolbar.length).toBe(1);
42+
});
43+
44+
it('should replace an existing item when adding with the same name', async () => {
45+
const { instance } = await createDataGrid({
46+
dataSource: [{ id: 1 }],
47+
});
48+
const headerPanel = getHeaderPanel(instance);
49+
50+
headerPanel.addToolbarItem('myItem', {
51+
text: 'First',
52+
location: 'before',
53+
name: 'myItem',
54+
sortIndex: 10,
55+
});
56+
57+
headerPanel.addToolbarItem('myItem', {
58+
text: 'Replaced',
59+
location: 'after',
60+
name: 'myItem',
61+
sortIndex: 10,
62+
});
63+
64+
const items = headerPanel._getToolbarItems();
65+
66+
expect(items).toHaveLength(1);
67+
expect(items[0].text).toBe('Replaced');
68+
});
69+
70+
it('should call _invalidate when adding item after first render', async () => {
71+
const { instance } = await createDataGrid({
72+
dataSource: [{ id: 1 }],
73+
searchPanel: { visible: true },
74+
});
75+
76+
const headerPanel = getHeaderPanel(instance);
77+
const invalidateSpy = jest.spyOn(headerPanel, '_invalidate');
78+
79+
headerPanel.addToolbarItem('customButton', {
80+
text: 'Custom',
81+
location: 'after',
82+
name: 'customButton',
83+
});
84+
85+
expect(invalidateSpy).toHaveBeenCalled();
86+
});
87+
88+
it('should not call _invalidate when adding item before first render', async () => {
89+
const { instance } = await createDataGrid({
90+
dataSource: [{ id: 1 }],
91+
});
92+
93+
const headerPanel = getHeaderPanel(instance);
94+
const originalElement = headerPanel._$element;
95+
headerPanel._$element = undefined;
96+
97+
const invalidateSpy = jest.spyOn(headerPanel, '_invalidate');
98+
99+
headerPanel.addToolbarItem('test', {
100+
text: 'Test',
101+
name: 'test',
102+
});
103+
104+
expect(invalidateSpy).not.toHaveBeenCalled();
105+
106+
headerPanel._$element = originalElement;
107+
});
108+
});
109+
110+
describe('removeToolbarItem', () => {
111+
it('should remove a previously added item', async () => {
112+
const { instance } = await createDataGrid({
113+
dataSource: [{ id: 1 }],
114+
});
115+
116+
const headerPanel = getHeaderPanel(instance);
117+
118+
headerPanel.addToolbarItem('toRemove', {
119+
text: 'Remove me',
120+
location: 'after',
121+
name: 'toRemove',
122+
});
123+
124+
expect(headerPanel._getToolbarItems()).toHaveLength(1);
125+
126+
headerPanel.removeToolbarItem('toRemove');
127+
128+
expect(headerPanel._getToolbarItems()).toHaveLength(0);
129+
});
130+
131+
it('should not call _invalidate when removing non-existent item', async () => {
132+
const { instance } = await createDataGrid({
133+
dataSource: [{ id: 1 }],
134+
searchPanel: { visible: true },
135+
});
136+
137+
const headerPanel = getHeaderPanel(instance);
138+
const invalidateSpy = jest.spyOn(headerPanel, '_invalidate');
139+
140+
headerPanel.removeToolbarItem('nonExistent');
141+
142+
expect(invalidateSpy).not.toHaveBeenCalled();
143+
});
144+
145+
it('should call _invalidate when removing existing item after render', async () => {
146+
const { instance } = await createDataGrid({
147+
dataSource: [{ id: 1 }],
148+
searchPanel: { visible: true },
149+
});
150+
151+
const headerPanel = getHeaderPanel(instance);
152+
153+
headerPanel.addToolbarItem('temp', {
154+
text: 'Temp',
155+
name: 'temp',
156+
});
157+
jest.runAllTimers();
158+
159+
const invalidateSpy = jest.spyOn(headerPanel, '_invalidate');
160+
161+
headerPanel.removeToolbarItem('temp');
162+
163+
expect(invalidateSpy).toHaveBeenCalled();
164+
});
165+
});
166+
167+
describe('_sortToolbarItems', () => {
168+
it('should sort items by sortIndex ascending', async () => {
169+
const { instance } = await createDataGrid({
170+
dataSource: [{ id: 1 }],
171+
});
172+
173+
const headerPanel = getHeaderPanel(instance);
174+
175+
headerPanel.addToolbarItem('c', {
176+
text: 'C', name: 'c', sortIndex: 30, location: 'after',
177+
});
178+
headerPanel.addToolbarItem('a', {
179+
text: 'A', name: 'a', sortIndex: 10, location: 'after',
180+
});
181+
headerPanel.addToolbarItem('b', {
182+
text: 'B', name: 'b', sortIndex: 20, location: 'after',
183+
});
184+
185+
headerPanel.render();
186+
const toolbarItems: ToolbarItem[] = headerPanel._toolbarOptions?.items;
187+
188+
const names = toolbarItems?.map((item) => item.name);
189+
expect(names).toEqual(['a', 'b', 'c']);
190+
});
191+
192+
it('should treat missing sortIndex as 0', async () => {
193+
const { instance } = await createDataGrid({
194+
dataSource: [{ id: 1 }],
195+
});
196+
197+
const headerPanel = getHeaderPanel(instance);
198+
199+
headerPanel.addToolbarItem('withIndex', {
200+
text: 'With', name: 'withIndex', sortIndex: 10, location: 'after',
201+
});
202+
headerPanel.addToolbarItem('noIndex', {
203+
text: 'No', name: 'noIndex', location: 'before',
204+
});
205+
206+
headerPanel.render();
207+
const toolbarItems: ToolbarItem[] = headerPanel._toolbarOptions?.items;
208+
209+
const names = toolbarItems?.map((item) => item.name);
210+
expect(names).toEqual(['noIndex', 'withIndex']);
211+
});
212+
213+
it('should not mutate the original items array', async () => {
214+
const { instance } = await createDataGrid({
215+
dataSource: [{ id: 1 }],
216+
});
217+
218+
const headerPanel = getHeaderPanel(instance);
219+
220+
headerPanel.addToolbarItem('b', {
221+
text: 'B', name: 'b', sortIndex: 20, location: 'after',
222+
});
223+
headerPanel.addToolbarItem('a', {
224+
text: 'A', name: 'a', sortIndex: 10, location: 'after',
225+
});
226+
227+
const itemsBefore = headerPanel._getToolbarItems();
228+
const firstItemNameBefore = itemsBefore[0].name;
229+
230+
headerPanel.render();
231+
232+
const itemsAfter = headerPanel._getToolbarItems();
233+
expect(itemsAfter[0].name).toBe(firstItemNameBefore);
234+
});
235+
});
236+
237+
describe('_getToolbarItems', () => {
238+
it('should return items added via addToolbarItem', async () => {
239+
const { instance } = await createDataGrid({
240+
dataSource: [{ id: 1 }],
241+
});
242+
243+
const headerPanel = getHeaderPanel(instance);
244+
245+
headerPanel.addToolbarItem('item1', {
246+
text: 'Item 1', name: 'item1', location: 'before',
247+
});
248+
headerPanel.addToolbarItem('item2', {
249+
text: 'Item 2', name: 'item2', location: 'after',
250+
});
251+
252+
const items: ToolbarItem[] = headerPanel._getToolbarItems();
253+
254+
expect(items).toHaveLength(2);
255+
expect(items.map((i) => i.name)).toEqual(
256+
expect.arrayContaining(['item1', 'item2']),
257+
);
258+
});
259+
260+
it('should return empty array when no items registered', async () => {
261+
const { instance } = await createDataGrid({
262+
dataSource: [{ id: 1 }],
263+
});
264+
265+
const headerPanel = getHeaderPanel(instance);
266+
267+
expect(headerPanel._getToolbarItems()).toEqual([]);
268+
});
269+
});
270+
271+
describe('items from extensions and addToolbarItem combined', () => {
272+
it('should include items from both extensions and addToolbarItem', async () => {
273+
const { instance } = await createDataGrid({
274+
dataSource: [{ id: 1 }],
275+
columnChooser: { enabled: true },
276+
});
277+
278+
const headerPanel = getHeaderPanel(instance);
279+
280+
headerPanel.addToolbarItem('customItem', {
281+
text: 'Custom',
282+
name: 'customItem',
283+
location: 'after',
284+
sortIndex: 100,
285+
});
286+
287+
const items: ToolbarItem[] = headerPanel._getToolbarItems();
288+
const names = items.map((i) => i.name);
289+
290+
expect(names).toContain('columnChooserButton');
291+
expect(names).toContain('customItem');
292+
});
293+
294+
it('should sort extension items and registered items together by sortIndex', async () => {
295+
const { instance } = await createDataGrid({
296+
dataSource: [{ id: 1 }],
297+
columnChooser: { enabled: true },
298+
searchPanel: { visible: true },
299+
});
300+
301+
const headerPanel = getHeaderPanel(instance);
302+
303+
headerPanel.addToolbarItem('middleItem', {
304+
text: 'Middle',
305+
name: 'middleItem',
306+
location: 'after',
307+
sortIndex: 45,
308+
});
309+
310+
headerPanel.render();
311+
312+
const toolbarItems: ToolbarItem[] = headerPanel._toolbarOptions?.items ?? [];
313+
const names = toolbarItems.map((i) => i.name);
314+
315+
const columnChooserIdx = names.indexOf('columnChooserButton');
316+
const middleIdx = names.indexOf('middleItem');
317+
const searchIdx = names.indexOf('searchPanel');
318+
319+
expect(columnChooserIdx).toBeGreaterThanOrEqual(0);
320+
expect(middleIdx).toBeGreaterThanOrEqual(0);
321+
expect(searchIdx).toBeGreaterThanOrEqual(0);
322+
323+
expect(columnChooserIdx).toBeLessThan(middleIdx);
324+
expect(middleIdx).toBeLessThan(searchIdx);
325+
});
326+
327+
it('should remove only the registered item without affecting extension items', async () => {
328+
const { instance } = await createDataGrid({
329+
dataSource: [{ id: 1 }],
330+
columnChooser: { enabled: true },
331+
});
332+
333+
const headerPanel = getHeaderPanel(instance);
334+
335+
headerPanel.addToolbarItem('toRemove', {
336+
text: 'Remove',
337+
name: 'toRemove',
338+
location: 'after',
339+
});
340+
341+
let items: ToolbarItem[] = headerPanel._getToolbarItems();
342+
expect(items.map((i) => i.name)).toContain('toRemove');
343+
expect(items.map((i) => i.name)).toContain('columnChooserButton');
344+
345+
// act
346+
headerPanel.removeToolbarItem('toRemove');
347+
348+
items = headerPanel._getToolbarItems();
349+
expect(items.map((i) => i.name)).not.toContain('toRemove');
350+
expect(items.map((i) => i.name)).toContain('columnChooserButton');
351+
});
352+
});
353+
});

packages/devextreme/js/__internal/grids/grid_core/header_panel/m_header_panel.ts

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ export class HeaderPanel extends ColumnsView {
2626

2727
private _toolbarOptions?: ToolbarProperties;
2828

29-
private _registeredToolbarItems: Record<string, ToolbarItem> = {};
29+
private readonly _registeredToolbarItems = new Map<string, ToolbarItem>();
3030

3131
protected _editingController!: EditingController;
3232

@@ -42,17 +42,16 @@ export class HeaderPanel extends ColumnsView {
4242
}
4343

4444
public addToolbarItem(name: string, item: ToolbarItem): void {
45-
this._registeredToolbarItems[name] = item;
45+
this._registeredToolbarItems.set(name, item);
4646

4747
if (this._$element) {
4848
this._invalidate();
4949
}
5050
}
5151

5252
public removeToolbarItem(name: string): void {
53-
if (this._registeredToolbarItems[name]) {
54-
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete
55-
delete this._registeredToolbarItems[name];
53+
if (this._registeredToolbarItems.has(name)) {
54+
this._registeredToolbarItems.delete(name);
5655

5756
if (this._$element) {
5857
this._invalidate();
@@ -64,7 +63,7 @@ export class HeaderPanel extends ColumnsView {
6463
* @extended: column_chooser, editing, filter_row
6564
*/
6665
protected _getToolbarItems(): ToolbarItem[] {
67-
return Object.values(this._registeredToolbarItems);
66+
return Array.from(this._registeredToolbarItems.values());
6867
}
6968

7069
// eslint-disable-next-line class-methods-use-this

0 commit comments

Comments
 (0)