@keenthemes/ktui 1.0.29 → 1.1.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 (90) hide show
  1. package/dist/ktui.js +3942 -8634
  2. package/dist/ktui.min.js +1 -1
  3. package/dist/ktui.min.js.map +1 -1
  4. package/dist/styles.css +0 -196
  5. package/lib/cjs/components/datatable/__tests__/pagination-reset.test.js +596 -0
  6. package/lib/cjs/components/datatable/__tests__/pagination-reset.test.js.map +1 -0
  7. package/lib/cjs/components/datatable/__tests__/race-conditions.test.js +548 -0
  8. package/lib/cjs/components/datatable/__tests__/race-conditions.test.js.map +1 -0
  9. package/lib/cjs/components/datatable/__tests__/setup.js +63 -0
  10. package/lib/cjs/components/datatable/__tests__/setup.js.map +1 -0
  11. package/lib/cjs/components/datatable/datatable.js +92 -30
  12. package/lib/cjs/components/datatable/datatable.js.map +1 -1
  13. package/lib/cjs/index.js +1 -5
  14. package/lib/cjs/index.js.map +1 -1
  15. package/lib/esm/components/datatable/__tests__/pagination-reset.test.js +594 -0
  16. package/lib/esm/components/datatable/__tests__/pagination-reset.test.js.map +1 -0
  17. package/lib/esm/components/datatable/__tests__/race-conditions.test.js +546 -0
  18. package/lib/esm/components/datatable/__tests__/race-conditions.test.js.map +1 -0
  19. package/lib/esm/components/datatable/__tests__/setup.js +58 -0
  20. package/lib/esm/components/datatable/__tests__/setup.js.map +1 -0
  21. package/lib/esm/components/datatable/datatable.js +92 -30
  22. package/lib/esm/components/datatable/datatable.js.map +1 -1
  23. package/lib/esm/index.js +0 -3
  24. package/lib/esm/index.js.map +1 -1
  25. package/package.json +9 -2
  26. package/src/components/datatable/__tests__/pagination-reset.test.ts +657 -0
  27. package/src/components/datatable/__tests__/race-conditions.test.ts +455 -0
  28. package/src/components/datatable/__tests__/setup.ts +67 -0
  29. package/src/components/datatable/datatable.ts +66 -11
  30. package/src/components/input/input.css +0 -1
  31. package/src/components/select/select.css +0 -1
  32. package/src/components/textarea/textarea.css +0 -1
  33. package/src/index.ts +0 -4
  34. package/styles.css +0 -1
  35. package/lib/cjs/components/datepicker/calendar.js +0 -1061
  36. package/lib/cjs/components/datepicker/calendar.js.map +0 -1
  37. package/lib/cjs/components/datepicker/config.js +0 -332
  38. package/lib/cjs/components/datepicker/config.js.map +0 -1
  39. package/lib/cjs/components/datepicker/datepicker.js +0 -949
  40. package/lib/cjs/components/datepicker/datepicker.js.map +0 -1
  41. package/lib/cjs/components/datepicker/dropdown.js +0 -635
  42. package/lib/cjs/components/datepicker/dropdown.js.map +0 -1
  43. package/lib/cjs/components/datepicker/events.js +0 -129
  44. package/lib/cjs/components/datepicker/events.js.map +0 -1
  45. package/lib/cjs/components/datepicker/index.js +0 -13
  46. package/lib/cjs/components/datepicker/index.js.map +0 -1
  47. package/lib/cjs/components/datepicker/keyboard.js +0 -536
  48. package/lib/cjs/components/datepicker/keyboard.js.map +0 -1
  49. package/lib/cjs/components/datepicker/locales.js +0 -78
  50. package/lib/cjs/components/datepicker/locales.js.map +0 -1
  51. package/lib/cjs/components/datepicker/templates.js +0 -403
  52. package/lib/cjs/components/datepicker/templates.js.map +0 -1
  53. package/lib/cjs/components/datepicker/types.js +0 -23
  54. package/lib/cjs/components/datepicker/types.js.map +0 -1
  55. package/lib/cjs/components/datepicker/utils.js +0 -524
  56. package/lib/cjs/components/datepicker/utils.js.map +0 -1
  57. package/lib/esm/components/datepicker/calendar.js +0 -1058
  58. package/lib/esm/components/datepicker/calendar.js.map +0 -1
  59. package/lib/esm/components/datepicker/config.js +0 -329
  60. package/lib/esm/components/datepicker/config.js.map +0 -1
  61. package/lib/esm/components/datepicker/datepicker.js +0 -946
  62. package/lib/esm/components/datepicker/datepicker.js.map +0 -1
  63. package/lib/esm/components/datepicker/dropdown.js +0 -632
  64. package/lib/esm/components/datepicker/dropdown.js.map +0 -1
  65. package/lib/esm/components/datepicker/events.js +0 -126
  66. package/lib/esm/components/datepicker/events.js.map +0 -1
  67. package/lib/esm/components/datepicker/index.js +0 -9
  68. package/lib/esm/components/datepicker/index.js.map +0 -1
  69. package/lib/esm/components/datepicker/keyboard.js +0 -533
  70. package/lib/esm/components/datepicker/keyboard.js.map +0 -1
  71. package/lib/esm/components/datepicker/locales.js +0 -74
  72. package/lib/esm/components/datepicker/locales.js.map +0 -1
  73. package/lib/esm/components/datepicker/templates.js +0 -390
  74. package/lib/esm/components/datepicker/templates.js.map +0 -1
  75. package/lib/esm/components/datepicker/types.js +0 -20
  76. package/lib/esm/components/datepicker/types.js.map +0 -1
  77. package/lib/esm/components/datepicker/utils.js +0 -508
  78. package/lib/esm/components/datepicker/utils.js.map +0 -1
  79. package/src/components/datepicker/calendar.ts +0 -1397
  80. package/src/components/datepicker/config.ts +0 -368
  81. package/src/components/datepicker/datepicker.css +0 -7
  82. package/src/components/datepicker/datepicker.ts +0 -1287
  83. package/src/components/datepicker/dropdown.ts +0 -757
  84. package/src/components/datepicker/events.ts +0 -149
  85. package/src/components/datepicker/index.ts +0 -10
  86. package/src/components/datepicker/keyboard.ts +0 -646
  87. package/src/components/datepicker/locales.ts +0 -80
  88. package/src/components/datepicker/templates.ts +0 -792
  89. package/src/components/datepicker/types.ts +0 -154
  90. package/src/components/datepicker/utils.ts +0 -631
@@ -0,0 +1,657 @@
1
+ /**
2
+ * pagination-reset.test.ts
3
+ * Tests for datatable pagination reset behavior on search and filter operations
4
+ *
5
+ * Spec: openspec/changes/fix-datatable-pagination-reset
6
+ * Requirement: Pagination Reset on Search and Filter
7
+ */
8
+
9
+ import { describe, it, expect, beforeEach, vi } from 'vitest';
10
+ import { KTDataTable } from '../datatable';
11
+ import { KTDataTableColumnFilterInterface } from '../types';
12
+
13
+ describe('KTDataTable - Pagination Reset', () => {
14
+ let container: HTMLElement;
15
+ let tableElement: HTMLTableElement;
16
+ let datatable: KTDataTable<any>;
17
+
18
+ /**
19
+ * Helper: Create a mock datatable with sample data
20
+ */
21
+ const createMockDataTable = (recordCount: number = 25) => {
22
+ container = document.createElement('div');
23
+ container.id = 'test-datatable-container';
24
+
25
+ // Create table structure
26
+ tableElement = document.createElement('table');
27
+ tableElement.setAttribute('data-kt-datatable-table', 'true');
28
+ tableElement.id = 'test-table';
29
+
30
+ const thead = document.createElement('thead');
31
+ const headerRow = document.createElement('tr');
32
+
33
+ const th1 = document.createElement('th');
34
+ th1.setAttribute('data-kt-datatable-column', 'id');
35
+ th1.textContent = 'ID';
36
+
37
+ const th2 = document.createElement('th');
38
+ th2.setAttribute('data-kt-datatable-column', 'name');
39
+ th2.textContent = 'Name';
40
+
41
+ const th3 = document.createElement('th');
42
+ th3.setAttribute('data-kt-datatable-column', 'status');
43
+ th3.textContent = 'Status';
44
+
45
+ headerRow.appendChild(th1);
46
+ headerRow.appendChild(th2);
47
+ headerRow.appendChild(th3);
48
+ thead.appendChild(headerRow);
49
+ tableElement.appendChild(thead);
50
+
51
+ // Create tbody with sample data
52
+ const tbody = document.createElement('tbody');
53
+ for (let i = 1; i <= recordCount; i++) {
54
+ const row = document.createElement('tr');
55
+
56
+ const td1 = document.createElement('td');
57
+ td1.textContent = String(i);
58
+
59
+ const td2 = document.createElement('td');
60
+ td2.textContent = `User ${i}`;
61
+
62
+ const td3 = document.createElement('td');
63
+ td3.textContent = i % 2 === 0 ? 'active' : 'inactive';
64
+
65
+ row.appendChild(td1);
66
+ row.appendChild(td2);
67
+ row.appendChild(td3);
68
+ tbody.appendChild(row);
69
+ }
70
+ tableElement.appendChild(tbody);
71
+
72
+ // Create pagination info element
73
+ const infoElement = document.createElement('div');
74
+ infoElement.setAttribute('data-kt-datatable-info', 'true');
75
+
76
+ // Create page size selector
77
+ const sizeElement = document.createElement('select');
78
+ sizeElement.setAttribute('data-kt-datatable-size', 'true');
79
+
80
+ // Create pagination container
81
+ const paginationElement = document.createElement('div');
82
+ paginationElement.setAttribute('data-kt-datatable-pagination', 'true');
83
+
84
+ container.appendChild(tableElement);
85
+ container.appendChild(infoElement);
86
+ container.appendChild(sizeElement);
87
+ container.appendChild(paginationElement);
88
+
89
+ document.body.appendChild(container);
90
+
91
+ return {
92
+ container,
93
+ tableElement,
94
+ infoElement,
95
+ sizeElement,
96
+ paginationElement,
97
+ };
98
+ };
99
+
100
+ beforeEach(() => {
101
+ // Clear any existing elements
102
+ document.body.innerHTML = '';
103
+ vi.clearAllMocks();
104
+ });
105
+
106
+ describe('Scenario: Pagination resets when search is performed', () => {
107
+ it('should reset to page 1 when search is called from page 2+', () => {
108
+ // Setup: Create datatable with 25 records (page size 10 = 3 pages)
109
+ const { container } = createMockDataTable(25);
110
+ datatable = new KTDataTable(container, {
111
+ pageSize: 10,
112
+ stateSave: false,
113
+ });
114
+
115
+ // Navigate to page 2
116
+ datatable.goPage(2);
117
+ expect(datatable.getState().page).toBe(2);
118
+
119
+ // Perform search
120
+ datatable.search('User 5');
121
+
122
+ // Assert: Pagination should reset to page 1
123
+ expect(datatable.getState().page).toBe(1);
124
+ });
125
+
126
+ it('should reset to page 1 from page 3 when searching', () => {
127
+ const { container } = createMockDataTable(35);
128
+ datatable = new KTDataTable(container, {
129
+ pageSize: 10,
130
+ stateSave: false,
131
+ });
132
+
133
+ // Navigate to page 3
134
+ datatable.goPage(3);
135
+ expect(datatable.getState().page).toBe(3);
136
+
137
+ // Perform search
138
+ datatable.search('test query');
139
+
140
+ // Assert
141
+ expect(datatable.getState().page).toBe(1);
142
+ });
143
+
144
+ it('should reset page even when search yields no results', () => {
145
+ const { container } = createMockDataTable(25);
146
+ datatable = new KTDataTable(container, {
147
+ pageSize: 10,
148
+ stateSave: false,
149
+ });
150
+
151
+ datatable.goPage(2);
152
+
153
+ // Search for something that doesn't exist
154
+ datatable.search('NonExistentUser999');
155
+
156
+ expect(datatable.getState().page).toBe(1);
157
+ });
158
+
159
+ it('should reset page on empty search query', () => {
160
+ const { container } = createMockDataTable(25);
161
+ datatable = new KTDataTable(container, {
162
+ pageSize: 10,
163
+ stateSave: false,
164
+ });
165
+
166
+ datatable.goPage(2);
167
+ datatable.search('');
168
+
169
+ expect(datatable.getState().page).toBe(1);
170
+ });
171
+
172
+ it('should reset page when searching with object query', () => {
173
+ const { container } = createMockDataTable(25);
174
+ datatable = new KTDataTable(container, {
175
+ pageSize: 10,
176
+ stateSave: false,
177
+ // Provide custom search callback that handles objects
178
+ search: {
179
+ delay: 500,
180
+ callback: (data: any[], search: string | object) => {
181
+ if (!search) return data;
182
+ // For object search, just return all data (simplified for test)
183
+ if (typeof search === 'object') return data;
184
+ // String search
185
+ return data.filter((item: any) =>
186
+ Object.values(item).some((value: any) =>
187
+ String(value).toLowerCase().includes((search as string).toLowerCase()),
188
+ ),
189
+ );
190
+ },
191
+ },
192
+ });
193
+
194
+ datatable.goPage(2);
195
+
196
+ // Search with object (for complex queries)
197
+ datatable.search({ name: 'User', status: 'active' });
198
+
199
+ expect(datatable.getState().page).toBe(1);
200
+ });
201
+ });
202
+
203
+ describe('Scenario: Pagination resets when filter is applied', () => {
204
+ it('should reset to page 1 when setFilter is called from page 2+', () => {
205
+ const { container } = createMockDataTable(25);
206
+ datatable = new KTDataTable(container, {
207
+ pageSize: 10,
208
+ stateSave: false,
209
+ });
210
+
211
+ // Navigate to page 2
212
+ datatable.goPage(2);
213
+ expect(datatable.getState().page).toBe(2);
214
+
215
+ // Apply filter
216
+ const filter: KTDataTableColumnFilterInterface = {
217
+ column: 'status',
218
+ type: 'text',
219
+ value: 'active',
220
+ };
221
+ datatable.setFilter(filter);
222
+
223
+ // Assert: Pagination should reset to page 1
224
+ expect(datatable.getState().page).toBe(1);
225
+ });
226
+
227
+ it('should reset to page 1 from any page when filtering', () => {
228
+ const { container } = createMockDataTable(50);
229
+ datatable = new KTDataTable(container, {
230
+ pageSize: 10,
231
+ stateSave: false,
232
+ });
233
+
234
+ // Navigate to page 5
235
+ datatable.goPage(5);
236
+ expect(datatable.getState().page).toBe(5);
237
+
238
+ // Apply filter
239
+ datatable.setFilter({
240
+ column: 'name',
241
+ type: 'text',
242
+ value: 'User 1',
243
+ });
244
+
245
+ expect(datatable.getState().page).toBe(1);
246
+ });
247
+
248
+ it('should reset page for numeric filter', () => {
249
+ const { container } = createMockDataTable(30);
250
+ datatable = new KTDataTable(container, {
251
+ pageSize: 10,
252
+ stateSave: false,
253
+ });
254
+
255
+ datatable.goPage(3);
256
+
257
+ datatable.setFilter({
258
+ column: 'id',
259
+ type: 'numeric',
260
+ value: '5',
261
+ });
262
+
263
+ expect(datatable.getState().page).toBe(1);
264
+ });
265
+ });
266
+
267
+ describe('Scenario: Multiple filters can be chained before reload', () => {
268
+ it('should reset page once when chaining multiple setFilter calls', () => {
269
+ const { container } = createMockDataTable(30);
270
+ datatable = new KTDataTable(container, {
271
+ pageSize: 10,
272
+ stateSave: false,
273
+ });
274
+
275
+ datatable.goPage(3);
276
+ expect(datatable.getState().page).toBe(3);
277
+
278
+ // Chain multiple filters
279
+ datatable
280
+ .setFilter({ column: 'status', type: 'text', value: 'active' })
281
+ .setFilter({ column: 'name', type: 'text', value: 'User' });
282
+
283
+ // Page should be reset to 1 after first filter
284
+ expect(datatable.getState().page).toBe(1);
285
+
286
+ // Verify both filters are applied
287
+ const filters = datatable.getState().filters;
288
+ expect(filters).toHaveLength(2);
289
+ expect(filters[0].column).toBe('status');
290
+ expect(filters[1].column).toBe('name');
291
+ });
292
+
293
+ it('should maintain page 1 through multiple filter operations', () => {
294
+ const { container } = createMockDataTable(40);
295
+ datatable = new KTDataTable(container, {
296
+ pageSize: 10,
297
+ stateSave: false,
298
+ });
299
+
300
+ datatable.goPage(4);
301
+
302
+ // Apply first filter
303
+ datatable.setFilter({ column: 'status', type: 'text', value: 'active' });
304
+ expect(datatable.getState().page).toBe(1);
305
+
306
+ // Apply second filter (page should stay at 1)
307
+ datatable.setFilter({ column: 'name', type: 'text', value: 'John' });
308
+ expect(datatable.getState().page).toBe(1);
309
+
310
+ // Apply third filter (page should stay at 1)
311
+ datatable.setFilter({ column: 'id', type: 'numeric', value: '10' });
312
+ expect(datatable.getState().page).toBe(1);
313
+ });
314
+
315
+ it('should allow filter replacement on same column', () => {
316
+ const { container } = createMockDataTable(30);
317
+ datatable = new KTDataTable(container, {
318
+ pageSize: 10,
319
+ stateSave: false,
320
+ });
321
+
322
+ datatable.goPage(2);
323
+
324
+ // Apply filter on 'status'
325
+ datatable.setFilter({ column: 'status', type: 'text', value: 'active' });
326
+ expect(datatable.getState().page).toBe(1);
327
+
328
+ // Replace filter on same column
329
+ datatable.setFilter({ column: 'status', type: 'text', value: 'inactive' });
330
+ expect(datatable.getState().page).toBe(1);
331
+
332
+ // Should only have one filter for 'status' column
333
+ const filters = datatable.getState().filters;
334
+ const statusFilters = filters.filter((f) => f.column === 'status');
335
+ expect(statusFilters).toHaveLength(1);
336
+ expect(statusFilters[0].value).toBe('inactive');
337
+ });
338
+ });
339
+
340
+ describe('Scenario: Pagination reset aligns with page size behavior', () => {
341
+ it('should use same state update pattern as setPageSize', () => {
342
+ const { container } = createMockDataTable(30);
343
+ datatable = new KTDataTable(container, {
344
+ pageSize: 10,
345
+ stateSave: false,
346
+ });
347
+
348
+ // Navigate to page 2
349
+ datatable.goPage(2);
350
+
351
+ // Change page size (existing behavior)
352
+ datatable.setPageSize(20);
353
+ expect(datatable.getState().page).toBe(1);
354
+
355
+ // Navigate to page 2 again
356
+ datatable.goPage(2);
357
+
358
+ // Apply search (new behavior)
359
+ datatable.search('test');
360
+ expect(datatable.getState().page).toBe(1);
361
+
362
+ // Navigate to page 2 again
363
+ datatable.goPage(2);
364
+
365
+ // Apply filter (new behavior)
366
+ datatable.setFilter({ column: 'status', type: 'text', value: 'active' });
367
+ expect(datatable.getState().page).toBe(1);
368
+ });
369
+
370
+ it('should reset pagination for all data-modifying operations', () => {
371
+ const { container } = createMockDataTable(30);
372
+ datatable = new KTDataTable(container, {
373
+ pageSize: 10,
374
+ stateSave: false,
375
+ });
376
+
377
+ // Test 1: Page size change
378
+ datatable.goPage(3);
379
+ datatable.setPageSize(15);
380
+ expect(datatable.getState().page).toBe(1);
381
+
382
+ // Test 2: Search
383
+ datatable.goPage(3);
384
+ datatable.search('query');
385
+ expect(datatable.getState().page).toBe(1);
386
+
387
+ // Test 3: Filter
388
+ datatable.goPage(3);
389
+ datatable.setFilter({ column: 'name', type: 'text', value: 'test' });
390
+ expect(datatable.getState().page).toBe(1);
391
+ });
392
+ });
393
+
394
+ describe('Scenario: Empty search results display correctly', () => {
395
+ it('should show page 1 when search yields no results from page 2+', () => {
396
+ const { container } = createMockDataTable(25);
397
+ datatable = new KTDataTable(container, {
398
+ pageSize: 10,
399
+ stateSave: false,
400
+ });
401
+
402
+ datatable.goPage(2);
403
+
404
+ // Search for non-existent data
405
+ datatable.search('XYZ_NONEXISTENT_999');
406
+
407
+ expect(datatable.getState().page).toBe(1);
408
+ });
409
+
410
+ it('should maintain page 1 state after empty search', () => {
411
+ const { container } = createMockDataTable(25);
412
+ datatable = new KTDataTable(container, {
413
+ pageSize: 10,
414
+ stateSave: false,
415
+ });
416
+
417
+ datatable.goPage(3);
418
+ datatable.search('NonExistent');
419
+
420
+ // Page should be 1
421
+ expect(datatable.getState().page).toBe(1);
422
+
423
+ // Clear search
424
+ datatable.search('');
425
+
426
+ // Should still be on page 1
427
+ expect(datatable.getState().page).toBe(1);
428
+ });
429
+ });
430
+
431
+ describe('Scenario: State persistence respects pagination reset', () => {
432
+ it('should save page 1 to state when search resets pagination', async () => {
433
+ const { container } = createMockDataTable(25);
434
+
435
+ // Enable state saving with unique namespace
436
+ datatable = new KTDataTable(container, {
437
+ pageSize: 10,
438
+ stateSave: true,
439
+ stateNamespace: 'test-datatable-search-reset',
440
+ });
441
+
442
+ // Wait for initial _updateData() to complete
443
+ await new Promise((resolve) => setTimeout(resolve, 100));
444
+
445
+ datatable.goPage(2);
446
+
447
+ // Wait for goPage to complete
448
+ await new Promise((resolve) => setTimeout(resolve, 100));
449
+
450
+ datatable.search('test query');
451
+
452
+ // Wait for async state save operations to complete
453
+ await new Promise((resolve) => setTimeout(resolve, 100));
454
+
455
+ // Check saved state
456
+ const savedState = localStorage.getItem('test-datatable-search-reset');
457
+ expect(savedState).toBeTruthy();
458
+
459
+ if (savedState) {
460
+ const state = JSON.parse(savedState);
461
+ expect(state.page).toBe(1);
462
+ expect(state.search).toBe('test query');
463
+ }
464
+
465
+ // Cleanup
466
+ localStorage.removeItem('test-datatable-search-reset');
467
+ });
468
+
469
+ it('should save page 1 to state when filter resets pagination', () => {
470
+ const { container } = createMockDataTable(25);
471
+
472
+ datatable = new KTDataTable(container, {
473
+ pageSize: 10,
474
+ stateSave: true,
475
+ stateNamespace: 'test-datatable-filter-reset',
476
+ });
477
+
478
+ datatable.goPage(3);
479
+ datatable.setFilter({ column: 'status', type: 'text', value: 'active' });
480
+
481
+ // Trigger reload to save state
482
+ datatable.reload();
483
+
484
+ const savedState = localStorage.getItem('test-datatable-filter-reset');
485
+ expect(savedState).toBeTruthy();
486
+
487
+ if (savedState) {
488
+ const state = JSON.parse(savedState);
489
+ expect(state.page).toBe(1);
490
+ }
491
+
492
+ // Cleanup
493
+ localStorage.removeItem('test-datatable-filter-reset');
494
+ });
495
+
496
+ it('should restore to page 1 with active search on reload', async () => {
497
+ const { container } = createMockDataTable(25);
498
+ const namespace = 'test-datatable-restore';
499
+
500
+ // First instance
501
+ let table1 = new KTDataTable(container, {
502
+ pageSize: 10,
503
+ stateSave: true,
504
+ stateNamespace: namespace,
505
+ });
506
+
507
+ // Wait for initial load
508
+ await new Promise((resolve) => setTimeout(resolve, 100));
509
+
510
+ table1.goPage(2);
511
+
512
+ // Wait for goPage to complete
513
+ await new Promise((resolve) => setTimeout(resolve, 100));
514
+
515
+ table1.search('User 5');
516
+
517
+ // Wait for search and state save to complete
518
+ await new Promise((resolve) => setTimeout(resolve, 100));
519
+
520
+ // Destroy first instance
521
+ table1.dispose();
522
+
523
+ // Wait for cleanup to complete
524
+ await new Promise((resolve) => setTimeout(resolve, 50));
525
+
526
+ document.body.innerHTML = '';
527
+
528
+ // Create new instance (simulating page reload)
529
+ const { container: newContainer } = createMockDataTable(25);
530
+ const table2 = new KTDataTable(newContainer, {
531
+ pageSize: 10,
532
+ stateSave: true,
533
+ stateNamespace: namespace,
534
+ });
535
+
536
+ // Should restore to page 1 with search active
537
+ expect(table2.getState().page).toBe(1);
538
+ expect(table2.getState().search).toBe('User 5');
539
+
540
+ // Cleanup
541
+ localStorage.removeItem(namespace);
542
+ });
543
+ });
544
+
545
+ describe('Edge Cases', () => {
546
+ it('should handle search reset on page 1 (no-op)', () => {
547
+ const { container } = createMockDataTable(25);
548
+ datatable = new KTDataTable(container, {
549
+ pageSize: 10,
550
+ stateSave: false,
551
+ });
552
+
553
+ // Already on page 1
554
+ expect(datatable.getState().page).toBe(1);
555
+
556
+ datatable.search('test');
557
+
558
+ // Should still be on page 1
559
+ expect(datatable.getState().page).toBe(1);
560
+ });
561
+
562
+ it('should handle filter reset on page 1 (no-op)', () => {
563
+ const { container } = createMockDataTable(25);
564
+ datatable = new KTDataTable(container, {
565
+ pageSize: 10,
566
+ stateSave: false,
567
+ });
568
+
569
+ expect(datatable.getState().page).toBe(1);
570
+
571
+ datatable.setFilter({ column: 'status', type: 'text', value: 'active' });
572
+
573
+ expect(datatable.getState().page).toBe(1);
574
+ });
575
+
576
+ it('should handle rapid consecutive search calls', () => {
577
+ const { container } = createMockDataTable(30);
578
+ datatable = new KTDataTable(container, {
579
+ pageSize: 10,
580
+ stateSave: false,
581
+ });
582
+
583
+ datatable.goPage(3);
584
+
585
+ // Rapid searches
586
+ datatable.search('query1');
587
+ datatable.search('query2');
588
+ datatable.search('query3');
589
+
590
+ // Should be on page 1 with last query
591
+ expect(datatable.getState().page).toBe(1);
592
+ expect(datatable.getState().search).toBe('query3');
593
+ });
594
+
595
+ it('should handle search and filter in quick succession', () => {
596
+ const { container } = createMockDataTable(30);
597
+ datatable = new KTDataTable(container, {
598
+ pageSize: 10,
599
+ stateSave: false,
600
+ });
601
+
602
+ datatable.goPage(3);
603
+
604
+ // Search then filter quickly
605
+ datatable.search('User');
606
+ datatable.setFilter({ column: 'status', type: 'text', value: 'active' });
607
+
608
+ expect(datatable.getState().page).toBe(1);
609
+ expect(datatable.getState().search).toBe('User');
610
+ expect(datatable.getState().filters).toHaveLength(1);
611
+ });
612
+ });
613
+
614
+ describe('Backward Compatibility', () => {
615
+ it('should maintain same return type for search method', () => {
616
+ const { container } = createMockDataTable(25);
617
+ datatable = new KTDataTable(container, { pageSize: 10 });
618
+
619
+ // search() returns void
620
+ const result = datatable.search('test');
621
+ expect(result).toBeUndefined();
622
+ });
623
+
624
+ it('should maintain same return type for setFilter method', () => {
625
+ const { container } = createMockDataTable(25);
626
+ datatable = new KTDataTable(container, { pageSize: 10 });
627
+
628
+ // setFilter() returns this (chainable)
629
+ const result = datatable.setFilter({
630
+ column: 'status',
631
+ type: 'text',
632
+ value: 'active',
633
+ });
634
+
635
+ expect(result).toBe(datatable);
636
+ });
637
+
638
+ it('should not break existing event handlers', async () => {
639
+ const { container } = createMockDataTable(25);
640
+ datatable = new KTDataTable(container, { pageSize: 10, stateSave: false });
641
+
642
+ const reloadSpy = vi.fn();
643
+ // Listen for 'reload' event directly (CustomEvent)
644
+ container.addEventListener('reload', reloadSpy);
645
+
646
+ datatable.goPage(2);
647
+ datatable.search('test');
648
+
649
+ // Wait for async reload to complete
650
+ await new Promise((resolve) => setTimeout(resolve, 50));
651
+
652
+ // reload event should still fire
653
+ expect(reloadSpy).toHaveBeenCalled();
654
+ });
655
+ });
656
+ });
657
+