@keenthemes/ktui 1.1.0 → 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 (222) hide show
  1. package/README.md +0 -27
  2. package/dist/ktui.js +6790 -14063
  3. package/dist/ktui.min.js +1 -1
  4. package/dist/ktui.min.js.map +1 -1
  5. package/dist/styles.css +1132 -2705
  6. package/lib/cjs/components/datatable/__tests__/pagination-reset.test.js +596 -0
  7. package/lib/cjs/components/datatable/__tests__/pagination-reset.test.js.map +1 -0
  8. package/lib/cjs/components/datatable/__tests__/race-conditions.test.js +548 -0
  9. package/lib/cjs/components/datatable/__tests__/race-conditions.test.js.map +1 -0
  10. package/lib/cjs/components/datatable/__tests__/setup.js +63 -0
  11. package/lib/cjs/components/datatable/__tests__/setup.js.map +1 -0
  12. package/lib/cjs/components/datatable/datatable.js +92 -30
  13. package/lib/cjs/components/datatable/datatable.js.map +1 -1
  14. package/lib/cjs/index.js +1 -10
  15. package/lib/cjs/index.js.map +1 -1
  16. package/lib/esm/components/datatable/__tests__/pagination-reset.test.js +594 -0
  17. package/lib/esm/components/datatable/__tests__/pagination-reset.test.js.map +1 -0
  18. package/lib/esm/components/datatable/__tests__/race-conditions.test.js +546 -0
  19. package/lib/esm/components/datatable/__tests__/race-conditions.test.js.map +1 -0
  20. package/lib/esm/components/datatable/__tests__/setup.js +58 -0
  21. package/lib/esm/components/datatable/__tests__/setup.js.map +1 -0
  22. package/lib/esm/components/datatable/datatable.js +92 -30
  23. package/lib/esm/components/datatable/datatable.js.map +1 -1
  24. package/lib/esm/index.js +0 -7
  25. package/lib/esm/index.js.map +1 -1
  26. package/package.json +7 -9
  27. package/src/components/alert/alert.css +188 -429
  28. package/src/components/datatable/__tests__/pagination-reset.test.ts +657 -0
  29. package/src/components/datatable/__tests__/race-conditions.test.ts +455 -0
  30. package/src/components/datatable/__tests__/setup.ts +67 -0
  31. package/src/components/datatable/datatable.ts +66 -11
  32. package/src/components/input/input.css +0 -1
  33. package/src/components/select/select.css +0 -1
  34. package/src/components/select/variants.css +4 -0
  35. package/src/components/textarea/textarea.css +0 -1
  36. package/src/index.ts +0 -10
  37. package/styles.css +0 -1
  38. package/lib/cjs/components/alert/alert.js +0 -1025
  39. package/lib/cjs/components/alert/alert.js.map +0 -1
  40. package/lib/cjs/components/alert/index.js +0 -20
  41. package/lib/cjs/components/alert/index.js.map +0 -1
  42. package/lib/cjs/components/alert/templates.js +0 -120
  43. package/lib/cjs/components/alert/templates.js.map +0 -1
  44. package/lib/cjs/components/alert/types.js +0 -7
  45. package/lib/cjs/components/alert/types.js.map +0 -1
  46. package/lib/cjs/components/datepicker/config/config.js +0 -42
  47. package/lib/cjs/components/datepicker/config/config.js.map +0 -1
  48. package/lib/cjs/components/datepicker/config/index.js +0 -24
  49. package/lib/cjs/components/datepicker/config/index.js.map +0 -1
  50. package/lib/cjs/components/datepicker/config/interfaces.js +0 -7
  51. package/lib/cjs/components/datepicker/config/interfaces.js.map +0 -1
  52. package/lib/cjs/components/datepicker/config/types.js +0 -7
  53. package/lib/cjs/components/datepicker/config/types.js.map +0 -1
  54. package/lib/cjs/components/datepicker/core/event-manager.js +0 -135
  55. package/lib/cjs/components/datepicker/core/event-manager.js.map +0 -1
  56. package/lib/cjs/components/datepicker/core/focus-manager.js +0 -167
  57. package/lib/cjs/components/datepicker/core/focus-manager.js.map +0 -1
  58. package/lib/cjs/components/datepicker/core/helpers.js +0 -219
  59. package/lib/cjs/components/datepicker/core/helpers.js.map +0 -1
  60. package/lib/cjs/components/datepicker/core/index.js +0 -25
  61. package/lib/cjs/components/datepicker/core/index.js.map +0 -1
  62. package/lib/cjs/components/datepicker/core/unified-state-manager.js +0 -394
  63. package/lib/cjs/components/datepicker/core/unified-state-manager.js.map +0 -1
  64. package/lib/cjs/components/datepicker/datepicker.js +0 -2252
  65. package/lib/cjs/components/datepicker/datepicker.js.map +0 -1
  66. package/lib/cjs/components/datepicker/index.js +0 -24
  67. package/lib/cjs/components/datepicker/index.js.map +0 -1
  68. package/lib/cjs/components/datepicker/ui/index.js +0 -23
  69. package/lib/cjs/components/datepicker/ui/index.js.map +0 -1
  70. package/lib/cjs/components/datepicker/ui/input/dropdown.js +0 -489
  71. package/lib/cjs/components/datepicker/ui/input/dropdown.js.map +0 -1
  72. package/lib/cjs/components/datepicker/ui/input/index.js +0 -23
  73. package/lib/cjs/components/datepicker/ui/input/index.js.map +0 -1
  74. package/lib/cjs/components/datepicker/ui/input/segmented-input.js +0 -640
  75. package/lib/cjs/components/datepicker/ui/input/segmented-input.js.map +0 -1
  76. package/lib/cjs/components/datepicker/ui/renderers/calendar.js +0 -446
  77. package/lib/cjs/components/datepicker/ui/renderers/calendar.js.map +0 -1
  78. package/lib/cjs/components/datepicker/ui/renderers/footer.js +0 -42
  79. package/lib/cjs/components/datepicker/ui/renderers/footer.js.map +0 -1
  80. package/lib/cjs/components/datepicker/ui/renderers/header.js +0 -32
  81. package/lib/cjs/components/datepicker/ui/renderers/header.js.map +0 -1
  82. package/lib/cjs/components/datepicker/ui/renderers/index.js +0 -25
  83. package/lib/cjs/components/datepicker/ui/renderers/index.js.map +0 -1
  84. package/lib/cjs/components/datepicker/ui/renderers/time-picker.js +0 -384
  85. package/lib/cjs/components/datepicker/ui/renderers/time-picker.js.map +0 -1
  86. package/lib/cjs/components/datepicker/ui/templates/index.js +0 -22
  87. package/lib/cjs/components/datepicker/ui/templates/index.js.map +0 -1
  88. package/lib/cjs/components/datepicker/ui/templates/templates.js +0 -253
  89. package/lib/cjs/components/datepicker/ui/templates/templates.js.map +0 -1
  90. package/lib/cjs/components/datepicker/utils/date-formatters.js +0 -88
  91. package/lib/cjs/components/datepicker/utils/date-formatters.js.map +0 -1
  92. package/lib/cjs/components/datepicker/utils/date-utils.js +0 -194
  93. package/lib/cjs/components/datepicker/utils/date-utils.js.map +0 -1
  94. package/lib/cjs/components/datepicker/utils/index.js +0 -24
  95. package/lib/cjs/components/datepicker/utils/index.js.map +0 -1
  96. package/lib/cjs/components/datepicker/utils/time-utils.js +0 -213
  97. package/lib/cjs/components/datepicker/utils/time-utils.js.map +0 -1
  98. package/lib/esm/components/alert/alert.js +0 -1022
  99. package/lib/esm/components/alert/alert.js.map +0 -1
  100. package/lib/esm/components/alert/index.js +0 -4
  101. package/lib/esm/components/alert/index.js.map +0 -1
  102. package/lib/esm/components/alert/templates.js +0 -112
  103. package/lib/esm/components/alert/templates.js.map +0 -1
  104. package/lib/esm/components/alert/types.js +0 -6
  105. package/lib/esm/components/alert/types.js.map +0 -1
  106. package/lib/esm/components/datepicker/config/config.js +0 -39
  107. package/lib/esm/components/datepicker/config/config.js.map +0 -1
  108. package/lib/esm/components/datepicker/config/index.js +0 -8
  109. package/lib/esm/components/datepicker/config/index.js.map +0 -1
  110. package/lib/esm/components/datepicker/config/interfaces.js +0 -6
  111. package/lib/esm/components/datepicker/config/interfaces.js.map +0 -1
  112. package/lib/esm/components/datepicker/config/types.js +0 -6
  113. package/lib/esm/components/datepicker/config/types.js.map +0 -1
  114. package/lib/esm/components/datepicker/core/event-manager.js +0 -133
  115. package/lib/esm/components/datepicker/core/event-manager.js.map +0 -1
  116. package/lib/esm/components/datepicker/core/focus-manager.js +0 -164
  117. package/lib/esm/components/datepicker/core/focus-manager.js.map +0 -1
  118. package/lib/esm/components/datepicker/core/helpers.js +0 -211
  119. package/lib/esm/components/datepicker/core/helpers.js.map +0 -1
  120. package/lib/esm/components/datepicker/core/index.js +0 -9
  121. package/lib/esm/components/datepicker/core/index.js.map +0 -1
  122. package/lib/esm/components/datepicker/core/unified-state-manager.js +0 -391
  123. package/lib/esm/components/datepicker/core/unified-state-manager.js.map +0 -1
  124. package/lib/esm/components/datepicker/datepicker.js +0 -2248
  125. package/lib/esm/components/datepicker/datepicker.js.map +0 -1
  126. package/lib/esm/components/datepicker/index.js +0 -7
  127. package/lib/esm/components/datepicker/index.js.map +0 -1
  128. package/lib/esm/components/datepicker/ui/index.js +0 -7
  129. package/lib/esm/components/datepicker/ui/index.js.map +0 -1
  130. package/lib/esm/components/datepicker/ui/input/dropdown.js +0 -486
  131. package/lib/esm/components/datepicker/ui/input/dropdown.js.map +0 -1
  132. package/lib/esm/components/datepicker/ui/input/index.js +0 -7
  133. package/lib/esm/components/datepicker/ui/input/index.js.map +0 -1
  134. package/lib/esm/components/datepicker/ui/input/segmented-input.js +0 -637
  135. package/lib/esm/components/datepicker/ui/input/segmented-input.js.map +0 -1
  136. package/lib/esm/components/datepicker/ui/renderers/calendar.js +0 -443
  137. package/lib/esm/components/datepicker/ui/renderers/calendar.js.map +0 -1
  138. package/lib/esm/components/datepicker/ui/renderers/footer.js +0 -39
  139. package/lib/esm/components/datepicker/ui/renderers/footer.js.map +0 -1
  140. package/lib/esm/components/datepicker/ui/renderers/header.js +0 -29
  141. package/lib/esm/components/datepicker/ui/renderers/header.js.map +0 -1
  142. package/lib/esm/components/datepicker/ui/renderers/index.js +0 -9
  143. package/lib/esm/components/datepicker/ui/renderers/index.js.map +0 -1
  144. package/lib/esm/components/datepicker/ui/renderers/time-picker.js +0 -381
  145. package/lib/esm/components/datepicker/ui/renderers/time-picker.js.map +0 -1
  146. package/lib/esm/components/datepicker/ui/templates/index.js +0 -6
  147. package/lib/esm/components/datepicker/ui/templates/index.js.map +0 -1
  148. package/lib/esm/components/datepicker/ui/templates/templates.js +0 -242
  149. package/lib/esm/components/datepicker/ui/templates/templates.js.map +0 -1
  150. package/lib/esm/components/datepicker/utils/date-formatters.js +0 -83
  151. package/lib/esm/components/datepicker/utils/date-formatters.js.map +0 -1
  152. package/lib/esm/components/datepicker/utils/date-utils.js +0 -184
  153. package/lib/esm/components/datepicker/utils/date-utils.js.map +0 -1
  154. package/lib/esm/components/datepicker/utils/index.js +0 -8
  155. package/lib/esm/components/datepicker/utils/index.js.map +0 -1
  156. package/lib/esm/components/datepicker/utils/time-utils.js +0 -201
  157. package/lib/esm/components/datepicker/utils/time-utils.js.map +0 -1
  158. package/src/components/alert/alert.ts +0 -990
  159. package/src/components/alert/index.ts +0 -4
  160. package/src/components/alert/templates.ts +0 -110
  161. package/src/components/alert/tests/accessibility/aria-roles.test.ts +0 -19
  162. package/src/components/alert/tests/accessibility/focus-management.test.ts +0 -19
  163. package/src/components/alert/tests/accessibility/keyboard-nav.test.ts +0 -22
  164. package/src/components/alert/tests/actions/confirm-cancel.test.ts +0 -122
  165. package/src/components/alert/tests/actions/input-field.test.ts +0 -180
  166. package/src/components/alert/tests/alert.basic.test.ts +0 -126
  167. package/src/components/alert/tests/alert.config.test.ts +0 -75
  168. package/src/components/alert/tests/alert.templates.test.ts +0 -17
  169. package/src/components/alert/tests/config/attribute-config.test.ts +0 -94
  170. package/src/components/alert/tests/config/json-config.test.ts +0 -119
  171. package/src/components/alert/tests/config/merging.test.ts +0 -89
  172. package/src/components/alert/tests/dismissal/auto-dismiss.test.ts +0 -96
  173. package/src/components/alert/tests/dismissal/escape-key-dismiss.test.ts +0 -105
  174. package/src/components/alert/tests/dismissal/manual-dismiss.test.ts +0 -90
  175. package/src/components/alert/tests/dismissal/outside-click-dismiss.test.ts +0 -91
  176. package/src/components/alert/tests/edge-cases/invalid-config.test.ts +0 -19
  177. package/src/components/alert/tests/edge-cases/multiple-alerts.test.ts +0 -19
  178. package/src/components/alert/tests/rendering/custom-content.test.ts +0 -81
  179. package/src/components/alert/tests/rendering/info-alert.test.ts +0 -84
  180. package/src/components/alert/tests/rendering/success-alert.test.ts +0 -100
  181. package/src/components/alert/tests/templates/default-templates.test.ts +0 -16
  182. package/src/components/alert/tests/templates/user-templates.test.ts +0 -16
  183. package/src/components/alert/types.ts +0 -145
  184. package/src/components/datepicker/__tests__/datepicker-events.test.ts +0 -356
  185. package/src/components/datepicker/__tests__/datepicker-init.test.ts +0 -343
  186. package/src/components/datepicker/__tests__/datepicker-integration.test.ts +0 -435
  187. package/src/components/datepicker/__tests__/datepicker-timezone.test.ts +0 -220
  188. package/src/components/datepicker/__tests__/segmented-input-focus.test.ts +0 -380
  189. package/src/components/datepicker/__tests__/selective-state-updates.test.ts +0 -400
  190. package/src/components/datepicker/__tests__/state-manager.test.ts +0 -421
  191. package/src/components/datepicker/__tests__/time-preservation.test.ts +0 -387
  192. package/src/components/datepicker/config/config.ts +0 -40
  193. package/src/components/datepicker/config/index.ts +0 -8
  194. package/src/components/datepicker/config/interfaces.ts +0 -82
  195. package/src/components/datepicker/config/types.ts +0 -188
  196. package/src/components/datepicker/core/event-manager.ts +0 -159
  197. package/src/components/datepicker/core/focus-manager.ts +0 -201
  198. package/src/components/datepicker/core/helpers.ts +0 -231
  199. package/src/components/datepicker/core/index.ts +0 -9
  200. package/src/components/datepicker/core/unified-state-manager.ts +0 -459
  201. package/src/components/datepicker/datepicker.css +0 -435
  202. package/src/components/datepicker/datepicker.ts +0 -2548
  203. package/src/components/datepicker/index.ts +0 -8
  204. package/src/components/datepicker/ui/index.ts +0 -7
  205. package/src/components/datepicker/ui/input/dropdown.ts +0 -552
  206. package/src/components/datepicker/ui/input/index.ts +0 -7
  207. package/src/components/datepicker/ui/input/segmented-input.ts +0 -638
  208. package/src/components/datepicker/ui/renderers/__tests__/calendar-optimizations.test.ts +0 -611
  209. package/src/components/datepicker/ui/renderers/calendar.ts +0 -530
  210. package/src/components/datepicker/ui/renderers/footer.ts +0 -43
  211. package/src/components/datepicker/ui/renderers/header.ts +0 -33
  212. package/src/components/datepicker/ui/renderers/index.ts +0 -9
  213. package/src/components/datepicker/ui/renderers/time-picker.ts +0 -438
  214. package/src/components/datepicker/ui/templates/index.ts +0 -6
  215. package/src/components/datepicker/ui/templates/templates.ts +0 -306
  216. package/src/components/datepicker/utils/__tests__/date-formatters.test.ts +0 -160
  217. package/src/components/datepicker/utils/__tests__/date-utils-keys.test.ts +0 -86
  218. package/src/components/datepicker/utils/__tests__/date-utils-timezone.test.ts +0 -215
  219. package/src/components/datepicker/utils/date-formatters.ts +0 -85
  220. package/src/components/datepicker/utils/date-utils.ts +0 -172
  221. package/src/components/datepicker/utils/index.ts +0 -8
  222. package/src/components/datepicker/utils/time-utils.ts +0 -221
@@ -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
+