@sneat/datagrid 0.1.2 → 0.1.4

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.
@@ -1,635 +0,0 @@
1
- import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
2
- import { IonicModule } from '@ionic/angular';
3
- import { SimpleChange } from '@angular/core';
4
- import { ErrorLogger } from '@sneat/core';
5
- import { vi } from 'vitest';
6
-
7
- import { DataGridComponent } from './data-grid.component';
8
- import { IGridColumn } from '@sneat/grid';
9
-
10
- describe('DataGridComponent', () => {
11
- let component: DataGridComponent;
12
- let fixture: ComponentFixture<DataGridComponent>;
13
- let mockErrorLogger: {
14
- logError: ReturnType<typeof vi.fn>;
15
- logErrorHandler: ReturnType<typeof vi.fn>;
16
- };
17
-
18
- beforeEach(waitForAsync(async () => {
19
- // Create mock error logger
20
- mockErrorLogger = {
21
- logError: vi.fn(),
22
- logErrorHandler: vi.fn().mockReturnValue((err: unknown) => {
23
- console.error('Error handler:', err);
24
- }),
25
- };
26
-
27
- await TestBed.configureTestingModule({
28
- imports: [DataGridComponent, IonicModule.forRoot()],
29
- providers: [{ provide: ErrorLogger, useValue: mockErrorLogger }],
30
- }).compileComponents();
31
-
32
- fixture = TestBed.createComponent(DataGridComponent);
33
- component = fixture.componentInstance;
34
- fixture.detectChanges();
35
- }));
36
-
37
- it('should create', () => {
38
- expect(component).toBeTruthy();
39
- });
40
-
41
- describe('Input Properties', () => {
42
- it('should have default layout as fitColumns', () => {
43
- expect(component.layout).toBe('fitColumns');
44
- });
45
-
46
- it('should initialize data as empty array', () => {
47
- expect(component.data).toEqual([]);
48
- });
49
-
50
- it('should initialize columns as empty array', () => {
51
- expect(component.columns).toEqual([]);
52
- });
53
-
54
- it('should accept custom layout input', () => {
55
- component.layout = 'fitData';
56
- expect(component.layout).toBe('fitData');
57
- });
58
-
59
- it('should accept selectable input', () => {
60
- component.selectable = true;
61
- expect(component.selectable).toBe(true);
62
- });
63
-
64
- it('should accept numeric selectable input', () => {
65
- component.selectable = 5;
66
- expect(component.selectable).toBe(5);
67
- });
68
-
69
- it('should accept highlight selectable input', () => {
70
- component.selectable = 'highlight';
71
- expect(component.selectable).toBe('highlight');
72
- });
73
- });
74
-
75
- describe('ngOnChanges', () => {
76
- it('should handle data changes when both data and columns are present', () => {
77
- const mockColumns: IGridColumn[] = [
78
- { field: 'id', title: 'ID', dbType: 'int' },
79
- { field: 'name', title: 'Name', dbType: 'string' },
80
- ];
81
- const mockData = [
82
- { id: 1, name: 'Test 1' },
83
- { id: 2, name: 'Test 2' },
84
- ];
85
-
86
- component.columns = mockColumns;
87
- component.data = mockData;
88
-
89
- vi.spyOn<any, any>(component, 'drawTable');
90
-
91
- component.ngOnChanges({
92
- data: new SimpleChange(null, mockData, false),
93
- });
94
-
95
- expect((component as any).drawTable).toHaveBeenCalled();
96
- });
97
-
98
- it('should handle columns changes', () => {
99
- const mockColumns: IGridColumn[] = [
100
- { field: 'id', title: 'ID', dbType: 'int' },
101
- ];
102
-
103
- component.data = [{ id: 1 }];
104
- component.columns = mockColumns;
105
-
106
- vi.spyOn<any, any>(component, 'drawTable');
107
-
108
- component.ngOnChanges({
109
- columns: new SimpleChange(null, mockColumns, false),
110
- });
111
-
112
- expect((component as any).drawTable).toHaveBeenCalled();
113
- });
114
-
115
- it('should handle rowClick changes', () => {
116
- const mockRowClick = (_event: Event, _row: unknown) => undefined;
117
-
118
- component.rowClick = mockRowClick;
119
- component.data = [{ id: 1 }];
120
- component.columns = [{ field: 'id', title: 'ID', dbType: 'int' }];
121
-
122
- vi.spyOn<any, any>(component, 'drawTable');
123
-
124
- component.ngOnChanges({
125
- rowClick: new SimpleChange(null, mockRowClick, false),
126
- });
127
-
128
- expect((component as any).drawTable).toHaveBeenCalled();
129
- });
130
-
131
- it('should not call drawTable if data is missing', () => {
132
- component.columns = [{ field: 'id', title: 'ID', dbType: 'int' }];
133
- component.data = undefined;
134
-
135
- vi.spyOn<any, any>(component, 'drawTable');
136
-
137
- component.ngOnChanges({
138
- data: new SimpleChange(null, undefined, false),
139
- });
140
-
141
- expect((component as any).drawTable).not.toHaveBeenCalled();
142
- });
143
-
144
- it('should not call drawTable if columns are missing', () => {
145
- component.data = [{ id: 1 }];
146
- component.columns = undefined;
147
-
148
- vi.spyOn<any, any>(component, 'drawTable');
149
-
150
- component.ngOnChanges({
151
- data: new SimpleChange(null, [{ id: 1 }], false),
152
- });
153
-
154
- expect((component as any).drawTable).not.toHaveBeenCalled();
155
- });
156
-
157
- it('should catch and log errors during ngOnChanges', () => {
158
- component.data = [{ id: 1 }];
159
- component.columns = [{ field: 'id', title: 'ID', dbType: 'int' }];
160
-
161
- vi.spyOn<any, any>(component, 'drawTable').mockImplementation(() => {
162
- throw new Error('Test error');
163
- });
164
-
165
- component.ngOnChanges({
166
- data: new SimpleChange(null, [{ id: 1 }], false),
167
- });
168
-
169
- expect(mockErrorLogger.logError).toHaveBeenCalled();
170
- expect(mockErrorLogger.logError).toHaveBeenCalledWith(
171
- expect.any(Error),
172
- 'Failed to process ngOnChanges in DataGridComponent',
173
- );
174
- });
175
- });
176
-
177
- describe('ngAfterViewInit', () => {
178
- it('should not throw error when tabulator is undefined', () => {
179
- expect(() => component.ngAfterViewInit()).not.toThrow();
180
- });
181
-
182
- it('should catch errors when redraw fails', () => {
183
- // Create a mock tabulator with a redraw method that throws
184
- (component as any).tabulator = {
185
- redraw: vi.fn().mockImplementation(() => {
186
- throw new Error('Redraw error');
187
- }),
188
- };
189
-
190
- component.ngAfterViewInit();
191
-
192
- expect(mockErrorLogger.logError).toHaveBeenCalledWith(
193
- expect.any(Error),
194
- 'Failed to redraw tabulator',
195
- { show: false, report: false },
196
- );
197
- });
198
- });
199
-
200
- describe('Column Mapping', () => {
201
- it('should map basic column properties', () => {
202
- const columns: IGridColumn[] = [
203
- {
204
- field: 'testField',
205
- title: 'Test Title',
206
- dbType: 'string',
207
- },
208
- ];
209
-
210
- component.columns = columns;
211
- component.data = [{ testField: 'value' }];
212
-
213
- vi.spyOn<any, any>(component, 'createTabulatorGrid');
214
-
215
- (component as any).drawTable();
216
-
217
- expect((component as any).tabulatorOptions.columns).toBeDefined();
218
- expect((component as any).tabulatorOptions.columns[0].field).toBe(
219
- 'testField',
220
- );
221
- expect((component as any).tabulatorOptions.columns[0].title).toBe(
222
- 'Test Title',
223
- );
224
- });
225
-
226
- it('should map column with tooltip', () => {
227
- const tooltipFn = (_cell: unknown) => 'Tooltip text';
228
- const columns: IGridColumn[] = [
229
- {
230
- field: 'testField',
231
- title: 'Test',
232
- dbType: 'string',
233
- tooltip: tooltipFn,
234
- },
235
- ];
236
-
237
- component.columns = columns;
238
- component.data = [{ testField: 'value' }];
239
-
240
- vi.spyOn<any, any>(component, 'createTabulatorGrid');
241
-
242
- (component as any).drawTable();
243
-
244
- expect((component as any).tabulatorOptions.columns[0].tooltip).toBe(
245
- tooltipFn,
246
- );
247
- });
248
-
249
- it('should map column with formatter', () => {
250
- const columns: IGridColumn[] = [
251
- {
252
- field: 'testField',
253
- title: 'Test',
254
- dbType: 'string',
255
- formatter: 'money',
256
- },
257
- ];
258
-
259
- component.columns = columns;
260
- component.data = [{ testField: 100 }];
261
-
262
- vi.spyOn<any, any>(component, 'createTabulatorGrid');
263
-
264
- (component as any).drawTable();
265
-
266
- expect((component as any).tabulatorOptions.columns[0].formatter).toBe(
267
- 'money',
268
- );
269
- });
270
-
271
- it('should map column horizontal alignment', () => {
272
- const columns: IGridColumn[] = [
273
- {
274
- field: 'amount',
275
- title: 'Amount',
276
- dbType: 'number',
277
- hozAlign: 'right',
278
- },
279
- ];
280
-
281
- component.columns = columns;
282
- component.data = [{ amount: 100 }];
283
-
284
- vi.spyOn<any, any>(component, 'createTabulatorGrid');
285
-
286
- (component as any).drawTable();
287
-
288
- expect((component as any).tabulatorOptions.columns[0].hozAlign).toBe(
289
- 'right',
290
- );
291
- });
292
-
293
- it('should map column widthGrow', () => {
294
- const columns: IGridColumn[] = [
295
- {
296
- field: 'description',
297
- title: 'Description',
298
- dbType: 'string',
299
- widthGrow: 2,
300
- },
301
- ];
302
-
303
- component.columns = columns;
304
- component.data = [{ description: 'Test' }];
305
-
306
- vi.spyOn<any, any>(component, 'createTabulatorGrid');
307
-
308
- (component as any).drawTable();
309
-
310
- expect((component as any).tabulatorOptions.columns[0].widthGrow).toBe(2);
311
- });
312
-
313
- it('should map column widthShrink', () => {
314
- const columns: IGridColumn[] = [
315
- {
316
- field: 'id',
317
- title: 'ID',
318
- dbType: 'int',
319
- widthShrink: 1,
320
- },
321
- ];
322
-
323
- component.columns = columns;
324
- component.data = [{ id: 1 }];
325
-
326
- vi.spyOn<any, any>(component, 'createTabulatorGrid');
327
-
328
- (component as any).drawTable();
329
-
330
- expect((component as any).tabulatorOptions.columns[0].widthShrink).toBe(
331
- 1,
332
- );
333
- });
334
-
335
- it('should map column width', () => {
336
- const columns: IGridColumn[] = [
337
- {
338
- field: 'status',
339
- title: 'Status',
340
- dbType: 'string',
341
- width: 100,
342
- },
343
- ];
344
-
345
- component.columns = columns;
346
- component.data = [{ status: 'active' }];
347
-
348
- vi.spyOn<any, any>(component, 'createTabulatorGrid');
349
-
350
- (component as any).drawTable();
351
-
352
- expect((component as any).tabulatorOptions.columns[0].width).toBe(100);
353
- });
354
-
355
- it('should map column width as string', () => {
356
- const columns: IGridColumn[] = [
357
- {
358
- field: 'status',
359
- title: 'Status',
360
- dbType: 'string',
361
- width: '150px',
362
- },
363
- ];
364
-
365
- component.columns = columns;
366
- component.data = [{ status: 'active' }];
367
-
368
- vi.spyOn<any, any>(component, 'createTabulatorGrid');
369
-
370
- (component as any).drawTable();
371
-
372
- expect((component as any).tabulatorOptions.columns[0].width).toBe(
373
- '150px',
374
- );
375
- });
376
-
377
- it('should handle column with Id suffix as link', () => {
378
- const columns: IGridColumn[] = [
379
- {
380
- field: 'userId',
381
- title: 'User',
382
- dbType: 'int',
383
- colName: 'UserId',
384
- },
385
- ];
386
-
387
- component.columns = columns;
388
- component.data = [{ userId: 123 }];
389
-
390
- vi.spyOn<any, any>(component, 'createTabulatorGrid');
391
-
392
- (component as any).drawTable();
393
-
394
- expect((component as any).tabulatorOptions.columns[0].formatter).toBe(
395
- 'link',
396
- );
397
- expect(
398
- (component as any).tabulatorOptions.columns[0].formatterParams.url,
399
- ).toBe('test-url');
400
- expect(
401
- (component as any).tabulatorOptions.columns[0].cellClick,
402
- ).toBeDefined();
403
-
404
- // Test cellClick callback
405
- const mockEvent = { preventDefault: vi.fn(), stopPropagation: vi.fn() };
406
- const cellClickHandler = (component as any).tabulatorOptions.columns[0]
407
- .cellClick;
408
-
409
- cellClickHandler(mockEvent, { value: 123 });
410
-
411
- expect(mockEvent.preventDefault).toHaveBeenCalled();
412
- expect(mockEvent.stopPropagation).toHaveBeenCalled();
413
- });
414
-
415
- it('should not set link formatter for Id column', () => {
416
- const columns: IGridColumn[] = [
417
- {
418
- field: 'id',
419
- title: 'ID',
420
- dbType: 'int',
421
- colName: 'Id',
422
- },
423
- ];
424
-
425
- component.columns = columns;
426
- component.data = [{ id: 1 }];
427
-
428
- vi.spyOn<any, any>(component, 'createTabulatorGrid');
429
-
430
- (component as any).drawTable();
431
-
432
- expect(
433
- (component as any).tabulatorOptions.columns[0].formatter,
434
- ).toBeUndefined();
435
- });
436
- });
437
-
438
- describe('Tabulator Options', () => {
439
- it('should set height when provided', () => {
440
- component.height = '400px';
441
- component.columns = [{ field: 'id', title: 'ID', dbType: 'int' }];
442
- component.data = [{ id: 1 }];
443
-
444
- vi.spyOn<any, any>(component, 'createTabulatorGrid');
445
-
446
- (component as any).drawTable();
447
-
448
- expect((component as any).tabulatorOptions.height).toBe('400px');
449
- });
450
-
451
- it('should set maxHeight when provided', () => {
452
- component.maxHeight = 500;
453
- component.columns = [{ field: 'id', title: 'ID', dbType: 'int' }];
454
- component.data = [{ id: 1 }];
455
-
456
- vi.spyOn<any, any>(component, 'createTabulatorGrid');
457
-
458
- (component as any).drawTable();
459
-
460
- expect((component as any).tabulatorOptions.maxHeight).toBe(500);
461
- });
462
-
463
- it('should set groupBy when provided', () => {
464
- component.groupBy = 'category';
465
- component.columns = [
466
- { field: 'id', title: 'ID', dbType: 'int' },
467
- { field: 'category', title: 'Category', dbType: 'string' },
468
- ];
469
- component.data = [
470
- { id: 1, category: 'A' },
471
- { id: 2, category: 'B' },
472
- ];
473
-
474
- vi.spyOn<any, any>(component, 'createTabulatorGrid');
475
-
476
- (component as any).drawTable();
477
-
478
- expect((component as any).tabulatorOptions.groupBy).toBe('category');
479
- expect((component as any).tabulatorOptions.groupHeader).toBeDefined();
480
- });
481
-
482
- it('should generate group header with singular record', () => {
483
- component.groupBy = 'status';
484
- component.columns = [{ field: 'id', title: 'ID', dbType: 'int' }];
485
- component.data = [{ id: 1, status: 'active' }];
486
-
487
- vi.spyOn<any, any>(component, 'createTabulatorGrid');
488
-
489
- (component as any).drawTable();
490
-
491
- const groupHeader = (component as any).tabulatorOptions.groupHeader;
492
- const result = groupHeader('active', 1);
493
-
494
- expect(result).toContain('status: active');
495
- expect(result).toContain('(1 record)');
496
- });
497
-
498
- it('should generate group header with plural records', () => {
499
- component.groupBy = 'status';
500
- component.columns = [{ field: 'id', title: 'ID', dbType: 'int' }];
501
- component.data = [
502
- { id: 1, status: 'active' },
503
- { id: 2, status: 'active' },
504
- ];
505
-
506
- vi.spyOn<any, any>(component, 'createTabulatorGrid');
507
-
508
- (component as any).drawTable();
509
-
510
- const groupHeader = (component as any).tabulatorOptions.groupHeader;
511
- const result = groupHeader('active', 2);
512
-
513
- expect(result).toContain('status: active');
514
- expect(result).toContain('(2 records)');
515
- });
516
-
517
- it('should set layout from input', () => {
518
- component.layout = 'fitData';
519
- component.columns = [{ field: 'id', title: 'ID', dbType: 'int' }];
520
- component.data = [{ id: 1 }];
521
-
522
- vi.spyOn<any, any>(component, 'createTabulatorGrid');
523
-
524
- (component as any).drawTable();
525
-
526
- expect((component as any).tabulatorOptions.layout).toBe('fitData');
527
- });
528
-
529
- it('should default to fitColumns layout', () => {
530
- component.columns = [{ field: 'id', title: 'ID', dbType: 'int' }];
531
- component.data = [{ id: 1 }];
532
-
533
- vi.spyOn<any, any>(component, 'createTabulatorGrid');
534
-
535
- (component as any).drawTable();
536
-
537
- expect((component as any).tabulatorOptions.layout).toBe('fitColumns');
538
- });
539
-
540
- it('should set selectable rows', () => {
541
- component.selectable = true;
542
- component.columns = [{ field: 'id', title: 'ID', dbType: 'int' }];
543
- component.data = [{ id: 1 }];
544
-
545
- vi.spyOn<any, any>(component, 'createTabulatorGrid');
546
-
547
- (component as any).drawTable();
548
-
549
- expect((component as any).tabulatorOptions.selectableRows).toBe(true);
550
- });
551
-
552
- it('should set rowContextMenu when provided', () => {
553
- const mockContextMenu = [
554
- {
555
- label: 'Edit',
556
- action: (_e: Event, _row: unknown) => undefined,
557
- },
558
- ];
559
-
560
- component.rowContextMenu = mockContextMenu as any;
561
- component.columns = [{ field: 'id', title: 'ID', dbType: 'int' }];
562
- component.data = [{ id: 1 }];
563
-
564
- vi.spyOn<any, any>(component, 'createTabulatorGrid');
565
-
566
- (component as any).drawTable();
567
-
568
- expect((component as any).tabulatorOptions.rowContextMenu).toBe(
569
- mockContextMenu,
570
- );
571
- });
572
- });
573
-
574
- describe('drawTable edge cases', () => {
575
- it('should log warning when columns are undefined', () => {
576
- const warnSpy = vi.spyOn(console, 'warn');
577
- component.columns = undefined;
578
- component.data = [{ id: 1 }];
579
-
580
- (component as any).drawTable();
581
-
582
- expect(warnSpy).toHaveBeenCalled();
583
- });
584
-
585
- it('should log warning when data is undefined', () => {
586
- const warnSpy = vi.spyOn(console, 'warn');
587
- component.columns = [{ field: 'id', title: 'ID', dbType: 'int' }];
588
- component.data = undefined;
589
-
590
- (component as any).drawTable();
591
-
592
- expect(warnSpy).toHaveBeenCalled();
593
- });
594
-
595
- it('should log error when tabulatorDiv is undefined', () => {
596
- component.columns = [{ field: 'id', title: 'ID', dbType: 'int' }];
597
- component.data = [{ id: 1 }];
598
- (component as any).tabulatorDiv = undefined;
599
-
600
- (component as any).drawTable();
601
-
602
- expect(mockErrorLogger.logError).toHaveBeenCalledWith(expect.any(Error));
603
- });
604
- });
605
-
606
- describe('Output Events', () => {
607
- it('should emit rowSelected event', async () => {
608
- const promise = new Promise<void>((resolve) => {
609
- component.rowSelected.subscribe((result) => {
610
- expect(result).toBeDefined();
611
- expect(result.row).toBeDefined();
612
- resolve();
613
- });
614
- });
615
-
616
- component.rowSelected.emit({ row: { id: 1 } });
617
- await promise;
618
- });
619
-
620
- it('should emit rowSelected with event', async () => {
621
- const mockEvent = new Event('click');
622
-
623
- const promise = new Promise<void>((resolve) => {
624
- component.rowSelected.subscribe((result) => {
625
- expect(result.event).toBe(mockEvent);
626
- expect(result.row).toEqual({ id: 1 });
627
- resolve();
628
- });
629
- });
630
-
631
- component.rowSelected.emit({ row: { id: 1 }, event: mockEvent });
632
- await promise;
633
- });
634
- });
635
- });