@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.
- package/dist/ktui.js +3942 -8634
- package/dist/ktui.min.js +1 -1
- package/dist/ktui.min.js.map +1 -1
- package/dist/styles.css +0 -196
- package/lib/cjs/components/datatable/__tests__/pagination-reset.test.js +596 -0
- package/lib/cjs/components/datatable/__tests__/pagination-reset.test.js.map +1 -0
- package/lib/cjs/components/datatable/__tests__/race-conditions.test.js +548 -0
- package/lib/cjs/components/datatable/__tests__/race-conditions.test.js.map +1 -0
- package/lib/cjs/components/datatable/__tests__/setup.js +63 -0
- package/lib/cjs/components/datatable/__tests__/setup.js.map +1 -0
- package/lib/cjs/components/datatable/datatable.js +92 -30
- package/lib/cjs/components/datatable/datatable.js.map +1 -1
- package/lib/cjs/index.js +1 -5
- package/lib/cjs/index.js.map +1 -1
- package/lib/esm/components/datatable/__tests__/pagination-reset.test.js +594 -0
- package/lib/esm/components/datatable/__tests__/pagination-reset.test.js.map +1 -0
- package/lib/esm/components/datatable/__tests__/race-conditions.test.js +546 -0
- package/lib/esm/components/datatable/__tests__/race-conditions.test.js.map +1 -0
- package/lib/esm/components/datatable/__tests__/setup.js +58 -0
- package/lib/esm/components/datatable/__tests__/setup.js.map +1 -0
- package/lib/esm/components/datatable/datatable.js +92 -30
- package/lib/esm/components/datatable/datatable.js.map +1 -1
- package/lib/esm/index.js +0 -3
- package/lib/esm/index.js.map +1 -1
- package/package.json +9 -2
- package/src/components/datatable/__tests__/pagination-reset.test.ts +657 -0
- package/src/components/datatable/__tests__/race-conditions.test.ts +455 -0
- package/src/components/datatable/__tests__/setup.ts +67 -0
- package/src/components/datatable/datatable.ts +66 -11
- package/src/components/input/input.css +0 -1
- package/src/components/select/select.css +0 -1
- package/src/components/textarea/textarea.css +0 -1
- package/src/index.ts +0 -4
- package/styles.css +0 -1
- package/lib/cjs/components/datepicker/calendar.js +0 -1061
- package/lib/cjs/components/datepicker/calendar.js.map +0 -1
- package/lib/cjs/components/datepicker/config.js +0 -332
- package/lib/cjs/components/datepicker/config.js.map +0 -1
- package/lib/cjs/components/datepicker/datepicker.js +0 -949
- package/lib/cjs/components/datepicker/datepicker.js.map +0 -1
- package/lib/cjs/components/datepicker/dropdown.js +0 -635
- package/lib/cjs/components/datepicker/dropdown.js.map +0 -1
- package/lib/cjs/components/datepicker/events.js +0 -129
- package/lib/cjs/components/datepicker/events.js.map +0 -1
- package/lib/cjs/components/datepicker/index.js +0 -13
- package/lib/cjs/components/datepicker/index.js.map +0 -1
- package/lib/cjs/components/datepicker/keyboard.js +0 -536
- package/lib/cjs/components/datepicker/keyboard.js.map +0 -1
- package/lib/cjs/components/datepicker/locales.js +0 -78
- package/lib/cjs/components/datepicker/locales.js.map +0 -1
- package/lib/cjs/components/datepicker/templates.js +0 -403
- package/lib/cjs/components/datepicker/templates.js.map +0 -1
- package/lib/cjs/components/datepicker/types.js +0 -23
- package/lib/cjs/components/datepicker/types.js.map +0 -1
- package/lib/cjs/components/datepicker/utils.js +0 -524
- package/lib/cjs/components/datepicker/utils.js.map +0 -1
- package/lib/esm/components/datepicker/calendar.js +0 -1058
- package/lib/esm/components/datepicker/calendar.js.map +0 -1
- package/lib/esm/components/datepicker/config.js +0 -329
- package/lib/esm/components/datepicker/config.js.map +0 -1
- package/lib/esm/components/datepicker/datepicker.js +0 -946
- package/lib/esm/components/datepicker/datepicker.js.map +0 -1
- package/lib/esm/components/datepicker/dropdown.js +0 -632
- package/lib/esm/components/datepicker/dropdown.js.map +0 -1
- package/lib/esm/components/datepicker/events.js +0 -126
- package/lib/esm/components/datepicker/events.js.map +0 -1
- package/lib/esm/components/datepicker/index.js +0 -9
- package/lib/esm/components/datepicker/index.js.map +0 -1
- package/lib/esm/components/datepicker/keyboard.js +0 -533
- package/lib/esm/components/datepicker/keyboard.js.map +0 -1
- package/lib/esm/components/datepicker/locales.js +0 -74
- package/lib/esm/components/datepicker/locales.js.map +0 -1
- package/lib/esm/components/datepicker/templates.js +0 -390
- package/lib/esm/components/datepicker/templates.js.map +0 -1
- package/lib/esm/components/datepicker/types.js +0 -20
- package/lib/esm/components/datepicker/types.js.map +0 -1
- package/lib/esm/components/datepicker/utils.js +0 -508
- package/lib/esm/components/datepicker/utils.js.map +0 -1
- package/src/components/datepicker/calendar.ts +0 -1397
- package/src/components/datepicker/config.ts +0 -368
- package/src/components/datepicker/datepicker.css +0 -7
- package/src/components/datepicker/datepicker.ts +0 -1287
- package/src/components/datepicker/dropdown.ts +0 -757
- package/src/components/datepicker/events.ts +0 -149
- package/src/components/datepicker/index.ts +0 -10
- package/src/components/datepicker/keyboard.ts +0 -646
- package/src/components/datepicker/locales.ts +0 -80
- package/src/components/datepicker/templates.ts +0 -792
- package/src/components/datepicker/types.ts +0 -154
- 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
|
+
|