@zeedhi/teknisa-components-common 3.0.0 → 3.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (103) hide show
  1. package/.package.json +4 -1
  2. package/dist/teknisa-components-common.js +3722 -32
  3. package/dist/teknisa-components-common.min.js +3722 -32
  4. package/dist/types/components/index.d.ts +5 -0
  5. package/dist/types/components/tek-datasource/index.d.ts +3 -0
  6. package/dist/types/components/tek-datasource/interfaces.d.ts +16 -0
  7. package/dist/types/components/tek-datasource/tek-memory-datasource.d.ts +93 -0
  8. package/dist/types/components/tek-datasource/tek-rest-datasource.d.ts +95 -0
  9. package/dist/types/components/tek-grid/columns-searcher.d.ts +5 -0
  10. package/dist/types/components/tek-grid/dynamic-filter-datasource-factory.d.ts +6 -0
  11. package/dist/types/components/tek-grid/filter-helper.d.ts +7 -0
  12. package/dist/types/components/tek-grid/grid-filter-button.d.ts +29 -0
  13. package/dist/types/components/tek-grid/grouped-data-manager.d.ts +82 -0
  14. package/dist/types/components/tek-grid/grouped-data-selector.d.ts +7 -0
  15. package/dist/types/components/tek-grid/grouped-view-navigator.d.ts +14 -0
  16. package/dist/types/components/tek-grid/index.d.ts +18 -0
  17. package/dist/types/components/tek-grid/interfaces.d.ts +259 -0
  18. package/dist/types/components/tek-grid/keymap-grouped.d.ts +6 -0
  19. package/dist/types/components/tek-grid/layout-options.d.ts +39 -0
  20. package/dist/types/components/tek-grid/tek-grid-column.d.ts +42 -0
  21. package/dist/types/components/tek-grid/tek-grid-columns-button/tek-grid-columns-button-controller.d.ts +8 -0
  22. package/dist/types/components/tek-grid/tek-grid-columns-button/tek-grid-columns-button.d.ts +13 -0
  23. package/dist/types/components/tek-grid/tek-grid-controller.d.ts +31 -0
  24. package/dist/types/components/tek-grid/tek-grid-events.d.ts +31 -0
  25. package/dist/types/components/tek-grid/tek-grid-toolbar-provider/export-options/button-option.d.ts +17 -0
  26. package/dist/types/components/tek-grid/tek-grid-toolbar-provider/export-options/index.d.ts +3 -0
  27. package/dist/types/components/tek-grid/tek-grid-toolbar-provider/export-options/interfaces.d.ts +5 -0
  28. package/dist/types/components/tek-grid/tek-grid-toolbar-provider/export-options/multi-option.d.ts +12 -0
  29. package/dist/types/components/tek-grid/tek-grid-toolbar-provider/index.d.ts +2 -0
  30. package/dist/types/components/tek-grid/tek-grid-toolbar-provider/tek-grid-toolbar-provider.d.ts +22 -0
  31. package/dist/types/components/tek-grid/tek-grid.d.ts +216 -0
  32. package/dist/types/components/tek-user-info/TekUserInfoController.d.ts +22 -0
  33. package/dist/types/components/tek-user-info/interfaces.d.ts +27 -0
  34. package/dist/types/components/tek-user-info/tek-user-info-list.d.ts +32 -0
  35. package/dist/types/components/tek-user-info/tek-user-info.d.ts +37 -0
  36. package/dist/types/error/tek-grid-delete-rows-error.d.ts +7 -0
  37. package/dist/types/error/teknisa-common-error.d.ts +6 -0
  38. package/dist/types/index.d.ts +1 -0
  39. package/dist/types/utils/config/config.d.ts +7 -0
  40. package/dist/types/utils/index.d.ts +3 -0
  41. package/dist/types/utils/is-filled-object/is-filled-object.d.ts +2 -0
  42. package/dist/types/utils/is-nil.d.ts +1 -0
  43. package/package.json +2 -2
  44. package/src/components/index.ts +5 -12
  45. package/src/components/tek-datasource/index.ts +3 -0
  46. package/src/components/tek-datasource/interfaces.ts +36 -0
  47. package/src/components/tek-datasource/tek-memory-datasource.ts +314 -0
  48. package/src/components/tek-datasource/tek-rest-datasource.ts +224 -0
  49. package/src/components/tek-grid/columns-searcher.ts +22 -0
  50. package/src/components/tek-grid/dynamic-filter-datasource-factory.ts +20 -0
  51. package/src/components/tek-grid/filter-helper.ts +20 -0
  52. package/src/components/tek-grid/grid-filter-button.ts +419 -0
  53. package/src/components/tek-grid/grouped-data-manager.ts +448 -0
  54. package/src/components/tek-grid/grouped-data-selector.ts +40 -0
  55. package/src/components/tek-grid/grouped-view-navigator.ts +84 -0
  56. package/src/components/tek-grid/index.ts +18 -0
  57. package/src/components/tek-grid/interfaces.ts +329 -0
  58. package/src/components/tek-grid/keymap-grouped.ts +20 -0
  59. package/src/components/tek-grid/layout-options.ts +248 -0
  60. package/src/components/tek-grid/tek-grid-column.ts +193 -0
  61. package/src/components/tek-grid/tek-grid-columns-button/tek-grid-columns-button-controller.ts +28 -0
  62. package/src/components/tek-grid/tek-grid-columns-button/tek-grid-columns-button.ts +38 -0
  63. package/src/components/tek-grid/tek-grid-controller.ts +140 -0
  64. package/src/components/tek-grid/tek-grid-events.ts +105 -0
  65. package/src/components/tek-grid/tek-grid-toolbar-provider/export-options/button-option.ts +26 -0
  66. package/src/components/tek-grid/tek-grid-toolbar-provider/export-options/index.ts +3 -0
  67. package/src/components/tek-grid/tek-grid-toolbar-provider/export-options/interfaces.ts +6 -0
  68. package/src/components/tek-grid/tek-grid-toolbar-provider/export-options/multi-option.ts +85 -0
  69. package/src/components/tek-grid/tek-grid-toolbar-provider/index.ts +2 -0
  70. package/src/components/tek-grid/tek-grid-toolbar-provider/tek-grid-toolbar-provider.ts +365 -0
  71. package/src/components/tek-grid/tek-grid.ts +1118 -0
  72. package/src/components/tek-user-info/TekUserInfoController.ts +87 -0
  73. package/src/components/tek-user-info/interfaces.ts +21 -0
  74. package/src/components/tek-user-info/tek-user-info-list.ts +64 -0
  75. package/src/components/tek-user-info/tek-user-info.ts +337 -0
  76. package/src/error/tek-grid-delete-rows-error.ts +15 -0
  77. package/src/error/teknisa-common-error.ts +8 -0
  78. package/src/index.ts +1 -0
  79. package/src/utils/config/config.ts +8 -0
  80. package/src/utils/index.ts +3 -0
  81. package/src/utils/is-filled-object/is-filled-object.ts +5 -0
  82. package/src/utils/is-nil.ts +3 -0
  83. package/tests/unit/components/tek-grid/button-option.spec.ts +49 -0
  84. package/tests/unit/components/tek-grid/columns-searcher.spec.ts +112 -0
  85. package/tests/unit/components/tek-grid/dynamic-filter-datasource-factory.spec.ts +90 -0
  86. package/tests/unit/components/tek-grid/filter-helper.spec.ts +34 -130
  87. package/tests/unit/components/tek-grid/grid-filter-button.spec.ts +110 -241
  88. package/tests/unit/components/tek-grid/grouped-data-manager.spec.ts +593 -0
  89. package/tests/unit/components/tek-grid/grouped-data-selector.spec.ts +136 -0
  90. package/tests/unit/components/tek-grid/grouped-view-navigator.spec.ts +244 -0
  91. package/tests/unit/components/tek-grid/keymap-grouped.spec.ts +20 -0
  92. package/tests/unit/components/tek-grid/{layout_options.spec.ts → layout-options.spec.ts} +77 -35
  93. package/tests/unit/components/tek-grid/multi-option.spec.ts +139 -0
  94. package/tests/unit/components/tek-grid/{grid-column.spec.ts → tek-grid-column.spec.ts} +48 -6
  95. package/tests/unit/components/tek-grid/{grid-columns-button.spec.ts → tek-grid-columns-button.spec.ts} +42 -9
  96. package/tests/unit/components/tek-grid/tek-grid-controller.spec.ts +253 -0
  97. package/tests/unit/components/tek-grid/tek-grid-events.spec.ts +186 -0
  98. package/tests/unit/components/tek-grid/tek-grid-toolbar-provider.spec.ts +34 -0
  99. package/tests/unit/components/tek-grid/tek-grid.spec.ts +895 -0
  100. package/tests/unit/components/tek-grid/tek-memory-datasource.spec.ts +482 -0
  101. package/tests/unit/components/tek-grid/tek-rest-datasource.spec.ts +422 -0
  102. package/src/error/delete-rows-error.ts +0 -11
  103. package/tests/unit/components/tek-grid/grid.spec.ts +0 -2701
@@ -0,0 +1,593 @@
1
+ import { IDictionary } from 'packages/core/dist/types';
2
+ import { GroupedDataManager, TekGridColumn } from '../../../../src';
3
+
4
+ jest.mock('lodash.debounce', () => jest.fn((fn) => fn));
5
+
6
+ // 🟡 PARTIAL MOCK for I18n.translate from @zeedhi/core
7
+ import { I18n } from '@zeedhi/core';
8
+ jest.spyOn(I18n, 'translate').mockImplementation((label: string | string[]) => `translated_${label}`);
9
+
10
+ const createMockGrid = (): any => ({
11
+ columns: [],
12
+ datasource: {
13
+ order: [],
14
+ loadAll: false,
15
+ limit: 100,
16
+ registerGetCallback: jest.fn(),
17
+ },
18
+ getColumn(name: string) { return this.columns.find((column: any) => column.name === name); },
19
+ getData: jest.fn(() => []),
20
+ setOrder: jest.fn(),
21
+ showSummaryTotal: false,
22
+ virtualScroll: false,
23
+ groupsOpened: true,
24
+ events: {},
25
+ reload: jest.fn(),
26
+ reapplyConditions: jest.fn(),
27
+ getAppliedConditions: jest.fn(() => ({})),
28
+ calcSummary: jest.fn()
29
+ });
30
+
31
+ describe('GroupedDataManager', () => {
32
+ let mockGrid: any;
33
+ let manager: GroupedDataManager;
34
+
35
+ beforeEach(() => {
36
+ mockGrid = createMockGrid();
37
+ manager = new GroupedDataManager(mockGrid);
38
+ manager.initializeGrouping(false);
39
+ });
40
+
41
+ describe('registerTask', () => {
42
+ it('should store tasks to be executed before loading', async () => {
43
+ const task = Promise.resolve('ok');
44
+ manager.registerTask(task);
45
+ await manager.loadAfterTasks();
46
+ expect(mockGrid.reload).toHaveBeenCalled();
47
+ });
48
+
49
+ it('should ignore errors from tasks and still reload', async () => {
50
+ const failingTask = Promise.reject(new Error('Task failed'));
51
+ const successfulTask = Promise.resolve('success');
52
+
53
+ manager.registerTask(failingTask);
54
+ manager.registerTask(successfulTask);
55
+
56
+ await manager.loadAfterTasks();
57
+
58
+ expect(mockGrid.reload).toHaveBeenCalled();
59
+ expect(manager['tasksBeforeLoad']).toHaveLength(0);
60
+ });
61
+ });
62
+
63
+ describe('loadAfterTasks', () => {
64
+ it('should throw when manager is not initialized', async () => {
65
+ const manager = new GroupedDataManager(mockGrid);
66
+
67
+ expect(async () => await manager.loadAfterTasks()).rejects.toThrow();
68
+ });
69
+ });
70
+
71
+ describe('initializeGrouping', () => {
72
+ it('should initialize group and summary columns and register callback', () => {
73
+ mockGrid.columns = [
74
+ new TekGridColumn({ grouped: true, name: 'foo', label: 'Foo', groupOpened: true }, mockGrid),
75
+ new TekGridColumn({ grouped: false, name: 'bar', aggregation: 'SUM' }, mockGrid)
76
+ ];
77
+ manager.initializeGrouping(false);
78
+ expect(mockGrid.datasource.registerGetCallback).toHaveBeenCalled();
79
+ expect(manager['groupColumns'].length).toBe(1);
80
+ expect(manager['summaryColumns'].length).toBe(1);
81
+ });
82
+
83
+ it('should register callback for datasource.get', () => {
84
+ mockGrid.showSummaryTotal = true;
85
+ mockGrid.columns = [
86
+ new TekGridColumn({ grouped: true, name: 'foo', label: 'Foo', groupOpened: true }, mockGrid),
87
+ new TekGridColumn({ grouped: false, name: 'bar', aggregation: 'SUM' }, mockGrid)
88
+ ];
89
+ mockGrid.datasource.registerGetCallback = jest.fn().mockImplementation((fn: (data: IDictionary[]) => unknown) => {
90
+ fn([]);
91
+ });
92
+ manager.buildGroupedData = jest.fn();
93
+
94
+ manager.initializeGrouping(false);
95
+
96
+ expect(mockGrid.datasource.registerGetCallback).toHaveBeenCalled();
97
+
98
+ expect(manager.buildGroupedData).toHaveBeenCalled();
99
+ });
100
+ });
101
+
102
+ describe('updateGroupedData', () => {
103
+ it('should reload data and call view updates', async () => {
104
+ const scrollMock = jest.fn();
105
+ const fixedMock = jest.fn();
106
+
107
+ manager.setViewUpdateScrollData(scrollMock);
108
+ manager.setViewUpdateFixedColumns(fixedMock);
109
+
110
+ await manager.updateGroupedData(false);
111
+ expect(mockGrid.reload).toHaveBeenCalled();
112
+ expect(fixedMock).toHaveBeenCalled();
113
+ });
114
+
115
+ it('should call viewUpdateScrollData when using virtual scroll', async () => {
116
+ const scrollMock = jest.fn();
117
+ manager.setViewUpdateScrollData(scrollMock);
118
+ mockGrid.virtualScroll = true;
119
+
120
+ await manager.updateGroupedData(false);
121
+ expect(scrollMock).toHaveBeenCalled();
122
+ });
123
+ });
124
+
125
+ describe('getOrder', () => {
126
+ it('should prioritize group columns in the order list', () => {
127
+ const order = manager['getOrder'](['a'], ['b.desc', 'a.desc']);
128
+ expect(order).toEqual(['a.asc', 'b.desc']);
129
+ });
130
+ });
131
+
132
+ describe('getSummaryData', () => {
133
+ it('should return extracted summary values for SUM aggregation', () => {
134
+ const column = new TekGridColumn({ name: 'amount', aggregation: 'SUM' }, mockGrid);
135
+ manager.summaryColumns = [column];
136
+
137
+ const footer = {
138
+ amount: { sum: 100, count: 5 }
139
+ };
140
+
141
+ const result = manager.getSummaryData(footer as any);
142
+ expect(result).toEqual({ amount: 100 });
143
+ });
144
+
145
+ it('should return extracted summary values for COUNT aggregation', () => {
146
+ const column = new TekGridColumn({ name: 'items', aggregation: 'COUNT' }, mockGrid);
147
+ manager.summaryColumns = [column];
148
+
149
+ const footer = {
150
+ items: { sum: 10, count: 3 }
151
+ };
152
+
153
+ const result = manager.getSummaryData(footer as any);
154
+ expect(result).toEqual({ items: 3 });
155
+ });
156
+
157
+ it('should return 0 if summary key is missing in footer', () => {
158
+ const column = new TekGridColumn({ name: 'missing', aggregation: 'SUM' }, mockGrid);
159
+ manager.summaryColumns = [column];
160
+
161
+ const footer = {
162
+ someOtherField: { sum: 99, count: 1 }
163
+ };
164
+
165
+ const result = manager.getSummaryData(footer as any);
166
+ expect(result).toEqual({ missing: 0 });
167
+ });
168
+
169
+ it('should handle multiple columns with different aggregations', () => {
170
+ const amountCol = new TekGridColumn({ name: 'amount', aggregation: 'SUM' }, mockGrid);
171
+ const itemsCol = new TekGridColumn({ name: 'items', aggregation: 'COUNT' }, mockGrid);
172
+
173
+ manager.summaryColumns = [amountCol, itemsCol];
174
+
175
+ const footer = {
176
+ amount: { sum: 200, count: 2 },
177
+ items: { sum: 50, count: 10 }
178
+ };
179
+
180
+ const result = manager.getSummaryData(footer as any);
181
+ expect(result).toEqual({ amount: 200, items: 10 });
182
+ });
183
+
184
+ it('should return AVG when aggregation is AVG', () => {
185
+ const column = new TekGridColumn({ name: 'score', aggregation: 'AVG' }, mockGrid);
186
+ manager.summaryColumns = [column];
187
+
188
+ const footer = {
189
+ score: { sum: 90, count: 3 }
190
+ };
191
+
192
+ const result = manager.getSummaryData(footer as any);
193
+ expect(result).toEqual({ score: 30 });
194
+ });
195
+
196
+ it('should return AVG when avg prop is defined in summary', () => {
197
+ const column = new TekGridColumn({ name: 'score', aggregation: 'AVG' }, mockGrid);
198
+ manager.summaryColumns = [column];
199
+
200
+ const footer = {
201
+ score: { sum: 90, count: 10, avg: 9 }
202
+ };
203
+
204
+ const result = manager.getSummaryData(footer as any);
205
+ expect(result).toEqual({ score: 9 });
206
+ });
207
+
208
+ it('should return MIN when aggregation is MIN', () => {
209
+ const column = new TekGridColumn({ name: 'score', aggregation: 'MIN' }, mockGrid);
210
+ manager.summaryColumns = [column];
211
+
212
+ const footer = {
213
+ score: { sum: 100, count: 2, min: 40 }
214
+ };
215
+
216
+ const result = manager.getSummaryData(footer as any);
217
+ expect(result).toEqual({ score: 40 });
218
+ });
219
+
220
+ it('should return MAX when aggregation is MAX', () => {
221
+ const column = new TekGridColumn({ name: 'score', aggregation: 'MAX' }, mockGrid);
222
+ manager.summaryColumns = [column];
223
+
224
+ const footer = {
225
+ score: { sum: 100, count: 2, max: 60 }
226
+ };
227
+
228
+ const result = manager.getSummaryData(footer as any);
229
+ expect(result).toEqual({ score: 60 });
230
+ });
231
+ });
232
+
233
+ describe('buildGroupedData', () => {
234
+ it('should initialize groups and build grouped data structure', () => {
235
+ mockGrid.showSummaryTotal = true;
236
+ mockGrid.columns = [
237
+ new TekGridColumn({ name: 'group1Col', grouped: true, label: 'Group' }, mockGrid),
238
+ new TekGridColumn({ name: 'group2Col', grouped: true, label: 'Group' }, mockGrid),
239
+ new TekGridColumn({ name: 'valueCol', aggregation: 'SUM' }, mockGrid)
240
+ ];
241
+ mockGrid.getData.mockReturnValue([
242
+ { group1Col: 'A', group2Col: 'B', valueCol: 10 },
243
+ { group1Col: 'A', group2Col: 'B', valueCol: 20 },
244
+ ]);
245
+
246
+ manager.initializeGrouping(false);
247
+ manager.buildGroupedData();
248
+ const data = manager.getGroupedData();
249
+ expect(data).toMatchObject([
250
+ { group: true, groupHeader: true, groupColumnName: 'group1Col' },
251
+ { group: true, groupHeader: true, groupColumnName: 'group2Col' },
252
+ { group1Col: 'A', group2Col: 'B', valueCol: 10 },
253
+ { group1Col: 'A', group2Col: 'B', valueCol: 20 },
254
+ { groupFooter: true, groupValue: 'B' },
255
+ { groupFooter: true, groupValue: 'A' },
256
+ { groupFooter: true, groupSummary: true, valueCol: 30 },
257
+ ]);
258
+ });
259
+
260
+ it('should initialize groups without summary', () => {
261
+ mockGrid.columns = [
262
+ new TekGridColumn({ name: 'groupCol', grouped: true, label: 'Group' }, mockGrid),
263
+ ];
264
+ mockGrid.getData.mockReturnValue([
265
+ { groupCol: 'A' },
266
+ { groupCol: 'A' },
267
+ ]);
268
+
269
+ manager.initializeGrouping(false);
270
+ manager.buildGroupedData();
271
+ const data = manager.getGroupedData();
272
+ expect(data.length).toBeGreaterThan(0);
273
+ });
274
+
275
+ it('should not calculate when row value is Nil', () => {
276
+ mockGrid.showSummaryTotal = true;
277
+ mockGrid.columns = [
278
+ new TekGridColumn({ name: 'groupCol', grouped: true, label: 'Group' }, mockGrid),
279
+ new TekGridColumn({ name: 'valueCol', aggregation: 'SUM' }, mockGrid)
280
+ ];
281
+ mockGrid.getData.mockReturnValue([
282
+ { groupCol: 'A', valueCol: null },
283
+ { groupCol: 'A', valueCol: 20 },
284
+ ]);
285
+
286
+ manager.initializeGrouping(false);
287
+ manager.buildGroupedData();
288
+ const data = manager.getGroupedData();
289
+ expect(data).toMatchObject([
290
+ { group: true, groupHeader: true, groupColumnName: 'groupCol' },
291
+ { groupCol: 'A', valueCol: null },
292
+ { groupCol: 'A', valueCol: 20 },
293
+ { groupFooter: true, groupValue: 'A' },
294
+ { groupFooter: true, groupSummary: true, valueCol: 20 },
295
+ ]);
296
+ });
297
+
298
+ it('when calcSummary event is defined, should call the event', () => {
299
+ const spy = jest.fn();
300
+ mockGrid.events.calcSummary = spy;
301
+ mockGrid.showSummaryTotal = true;
302
+ mockGrid.columns = [
303
+ new TekGridColumn({ name: 'groupCol', grouped: true, label: 'Group' }, mockGrid),
304
+ new TekGridColumn({ name: 'valueCol', aggregation: 'SUM' }, mockGrid)
305
+ ];
306
+ mockGrid.getData.mockReturnValue([
307
+ { groupCol: 'A', valueCol: 10 },
308
+ { groupCol: 'A', valueCol: 20 },
309
+ ]);
310
+
311
+ manager.initializeGrouping(false);
312
+ manager.buildGroupedData();
313
+ expect(mockGrid.calcSummary).toHaveBeenCalled();
314
+ });
315
+
316
+ it('should not add a summary footer when grid has no summary columns', () => {
317
+ mockGrid.showSummaryTotal = true;
318
+ mockGrid.columns = [
319
+ new TekGridColumn({ name: 'groupCol', grouped: true, label: 'Group' }, mockGrid),
320
+ new TekGridColumn({ name: 'valueCol' }, mockGrid)
321
+ ];
322
+ mockGrid.getData.mockReturnValue([
323
+ { groupCol: 'A', valueCol: 10 },
324
+ { groupCol: 'A', valueCol: 20 },
325
+ ]);
326
+
327
+ manager.initializeGrouping(false);
328
+ manager.buildGroupedData();
329
+ const data = manager.getGroupedData();
330
+ expect(data).toMatchObject([
331
+ { group: true, groupHeader: true, groupColumnName: 'groupCol' },
332
+ { groupCol: 'A', valueCol: 10 },
333
+ { groupCol: 'A', valueCol: 20 },
334
+ ]);
335
+ });
336
+
337
+ it('should use groupLabelForEmptyValue when group header value is empty', () => {
338
+ mockGrid.showSummaryTotal = true;
339
+ mockGrid.columns = [
340
+ new TekGridColumn({ name: 'groupCol', groupLabelForEmptyValue: 'Empty', grouped: true }, mockGrid),
341
+ new TekGridColumn({ name: 'valueCol', aggregation: 'SUM' }, mockGrid)
342
+ ];
343
+ mockGrid.getData.mockReturnValue([
344
+ { groupCol: '', valueCol: 10 },
345
+ ]);
346
+
347
+ manager.initializeGrouping(false);
348
+ manager.buildGroupedData();
349
+ const data = manager.getGroupedData();
350
+ expect(data).toMatchObject([
351
+ { group: true, groupHeader: true, groupColumnName: 'groupCol', groupValue: 'Empty' },
352
+ { groupCol: '', valueCol: 10 },
353
+ { groupFooter: true, groupValue: 'Empty' },
354
+ { groupFooter: true, groupSummary: true, valueCol: 10 },
355
+ ]);
356
+ });
357
+
358
+ it('when using a min aggregation, should use the min value', () => {
359
+ mockGrid.showSummaryTotal = true;
360
+ mockGrid.columns = [
361
+ new TekGridColumn({ name: 'groupCol', grouped: true, label: 'Group' }, mockGrid),
362
+ new TekGridColumn({ name: 'valueCol', aggregation: 'MIN' }, mockGrid)
363
+ ];
364
+ mockGrid.getData.mockReturnValue([
365
+ { groupCol: 'A', valueCol: 20 },
366
+ { groupCol: 'A', valueCol: 10 },
367
+ ]);
368
+
369
+ manager.initializeGrouping(false);
370
+ manager.buildGroupedData();
371
+ const data = manager.getGroupedData();
372
+ expect(data).toMatchObject([
373
+ { group: true, groupHeader: true, groupColumnName: 'groupCol' },
374
+ { groupCol: 'A', valueCol: 20 },
375
+ { groupCol: 'A', valueCol: 10 },
376
+ { groupFooter: true, groupValue: 'A' },
377
+ { groupFooter: true, groupSummary: true, valueCol: 10 },
378
+ ]);
379
+ });
380
+ });
381
+
382
+ describe('updateGrouping', () => {
383
+ it('when lazyLoad is undefined, should call updateGroupedData with lazyLoad=false', () => {
384
+ const updateGroupedDataSpy = jest.spyOn(manager, 'updateGroupedData');
385
+ manager.updateGrouping();
386
+ expect(updateGroupedDataSpy).toHaveBeenCalledWith(false);
387
+ });
388
+
389
+ it('should throw when manager is not initialized', () => {
390
+ const manager = new GroupedDataManager(mockGrid);
391
+ expect(() => manager.updateGrouping()).toThrow();
392
+ });
393
+ });
394
+
395
+ describe('openGroup', () => {
396
+ it('should toggle groupOpened from false to true', () => {
397
+ const group = { groupOpened: false };
398
+ manager.openGroup(group);
399
+ expect(group.groupOpened).toBe(true);
400
+ });
401
+
402
+ it('should toggle groupOpened from true to false', () => {
403
+ const group = { groupOpened: true };
404
+ manager.openGroup(group);
405
+ expect(group.groupOpened).toBe(false);
406
+ });
407
+
408
+ it('should not call viewUpdateScrollData if virtualScroll is false', () => {
409
+ const group = { groupOpened: false };
410
+ const spy = jest.fn();
411
+ manager.setViewUpdateScrollData(spy);
412
+ manager.openGroup(group);
413
+ expect(spy).not.toHaveBeenCalled();
414
+ });
415
+
416
+ it('should call viewUpdateScrollData if virtualScroll is true and method is defined', () => {
417
+ manager = new GroupedDataManager({ virtualScroll: true } as any);
418
+ const spy = jest.fn();
419
+ manager.setViewUpdateScrollData(spy);
420
+
421
+ const group = { groupOpened: false };
422
+ manager.openGroup(group);
423
+ expect(spy).toHaveBeenCalledTimes(1);
424
+ });
425
+
426
+ it('should not throw if viewUpdateScrollData is undefined and virtualScroll is true', () => {
427
+ manager = new GroupedDataManager({ virtualScroll: true } as any);
428
+ const group = { groupOpened: false };
429
+ expect(() => manager.openGroup(group)).not.toThrow();
430
+ });
431
+ });
432
+
433
+ describe('isItemVisible', () => {
434
+ it('should return true if row.groupHeaders is undefined', () => {
435
+ const row = {};
436
+ expect(manager.isItemVisible(row)).toBe(true);
437
+ });
438
+
439
+ it('should return true if row.groupHeaders is null', () => {
440
+ const row = { groupHeaders: null };
441
+ expect(manager.isItemVisible(row)).toBe(true);
442
+ });
443
+
444
+ it('should return true if row.groupHeaders is an empty array', () => {
445
+ const row = { groupHeaders: [] };
446
+ expect(manager.isItemVisible(row)).toBe(true);
447
+ });
448
+
449
+ it('should return true if all groupHeaders are opened', () => {
450
+ const row = {
451
+ groupHeaders: [
452
+ { groupOpened: true },
453
+ { groupOpened: true },
454
+ ]
455
+ };
456
+ expect(manager.isItemVisible(row)).toBe(true);
457
+ });
458
+
459
+ it('should return false if at least one groupHeader is closed', () => {
460
+ const row = {
461
+ groupHeaders: [
462
+ { groupOpened: true },
463
+ { groupOpened: false },
464
+ ]
465
+ };
466
+ expect(manager.isItemVisible(row)).toBe(false);
467
+ });
468
+
469
+ it('should return false if all groupHeaders are closed', () => {
470
+ const row = {
471
+ groupHeaders: [
472
+ { groupOpened: false },
473
+ { groupOpened: false },
474
+ ]
475
+ };
476
+ expect(manager.isItemVisible(row)).toBe(false);
477
+ });
478
+ });
479
+
480
+ describe('isColumnSearchable', () => {
481
+ const makeColumn = (props: Partial<TekGridColumn> = {}) => ({
482
+ isVisible: false,
483
+ grouped: false,
484
+ ...props,
485
+ } as TekGridColumn);
486
+
487
+ it('should return true if searchVisibleOnly is false (regardless of column)', () => {
488
+ mockGrid.searchVisibleOnly = false;
489
+ const column = makeColumn({ isVisible: false, grouped: false });
490
+ expect(manager.isColumnSearchable(column)).toBe(true);
491
+ });
492
+
493
+ it('should return true if column.isVisible is true', () => {
494
+ const column = makeColumn({ isVisible: true, grouped: false });
495
+ expect(manager.isColumnSearchable(column)).toBe(true);
496
+ });
497
+
498
+ it('should return true if column.grouped is true', () => {
499
+ const column = makeColumn({ isVisible: false, grouped: true });
500
+ expect(manager.isColumnSearchable(column)).toBe(true);
501
+ });
502
+
503
+ it('should return false if searchVisibleOnly is true and column is neither visible nor grouped', () => {
504
+ mockGrid.searchVisibleOnly = true;
505
+ const column = makeColumn({ isVisible: false, grouped: false });
506
+ expect(manager.isColumnSearchable(column)).toBe(false);
507
+ });
508
+ });
509
+
510
+ describe('directionalLeft', () => {
511
+ let mockSetCurrentRow: jest.Mock;
512
+
513
+ beforeEach(() => {
514
+ mockSetCurrentRow = jest.fn();
515
+
516
+ mockGrid.setCurrentRow = mockSetCurrentRow;
517
+ });
518
+
519
+ it('should close the group if current row is a group and is open', () => {
520
+ const currentRow = { group: true, groupOpened: true };
521
+ mockGrid.datasource.currentRow = currentRow;
522
+
523
+ manager.directionalLeft();
524
+
525
+ expect(mockGrid.datasource.currentRow.groupOpened).toBe(false);
526
+ expect(mockSetCurrentRow).not.toHaveBeenCalled();
527
+ });
528
+
529
+ it('should do nothing if grid.cellSelection is true', () => {
530
+ mockGrid.cellSelection = true;
531
+ mockGrid.datasource.currentRow = {
532
+ groupHeaders: [{}],
533
+ };
534
+
535
+ manager.directionalLeft();
536
+
537
+ expect(mockSetCurrentRow).not.toHaveBeenCalled();
538
+ });
539
+
540
+ it('should do nothing if there are no groupHeaders', () => {
541
+ mockGrid.datasource.currentRow = {
542
+ groupHeaders: [],
543
+ };
544
+
545
+ manager.directionalLeft();
546
+
547
+ expect(mockSetCurrentRow).not.toHaveBeenCalled();
548
+ });
549
+
550
+ it('should move to the last groupHeader if not in a group and no cell selection', () => {
551
+ const groupHeader1 = { id: 1 };
552
+ const groupHeader2 = { id: 2 };
553
+ const currentRow = {
554
+ groupHeaders: [groupHeader1, groupHeader2],
555
+ };
556
+
557
+ mockGrid.datasource.currentRow = currentRow;
558
+
559
+ manager.directionalLeft();
560
+
561
+ expect(mockSetCurrentRow).toHaveBeenCalledWith(groupHeader2);
562
+ });
563
+ });
564
+
565
+ describe('directionalRight', () => {
566
+ it('should do nothing if currentRow.group is falsy', () => {
567
+ const row = { group: false, groupOpened: true };
568
+ mockGrid.datasource.currentRow = row;
569
+
570
+ manager.directionalRight();
571
+
572
+ expect(row.groupOpened).toBe(true); // unchanged
573
+ });
574
+
575
+ it('should do nothing if currentRow.groupOpened is true', () => {
576
+ const row = { group: true, groupOpened: true };
577
+ mockGrid.datasource.currentRow = row;
578
+
579
+ manager.directionalRight();
580
+
581
+ expect(row.groupOpened).toBe(true); // still true
582
+ });
583
+
584
+ it('should set groupOpened to true if group and groupOpened are already true', () => {
585
+ const row = { group: true, groupOpened: false };
586
+ mockGrid.datasource.currentRow = row;
587
+
588
+ manager.directionalRight();
589
+
590
+ expect(row.groupOpened).toBe(true); // remains true
591
+ });
592
+ });
593
+ });