@keenthemes/ktui 1.2.6 → 1.2.7

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 (190) hide show
  1. package/README.md +14 -5
  2. package/dist/ktui.js +3775 -2298
  3. package/dist/ktui.min.js +1 -1
  4. package/dist/ktui.min.js.map +1 -1
  5. package/dist/styles.css +25 -5
  6. package/lib/cjs/components/datatable/datatable-checkbox.d.ts +37 -1
  7. package/lib/cjs/components/datatable/datatable-checkbox.d.ts.map +1 -1
  8. package/lib/cjs/components/datatable/datatable-checkbox.js +143 -156
  9. package/lib/cjs/components/datatable/datatable-checkbox.js.map +1 -1
  10. package/lib/cjs/components/datatable/datatable-column-utils.d.ts +30 -0
  11. package/lib/cjs/components/datatable/datatable-column-utils.d.ts.map +1 -0
  12. package/lib/cjs/components/datatable/datatable-column-utils.js +42 -0
  13. package/lib/cjs/components/datatable/datatable-column-utils.js.map +1 -0
  14. package/lib/cjs/components/datatable/datatable-contracts.d.ts +2 -4
  15. package/lib/cjs/components/datatable/datatable-contracts.d.ts.map +1 -1
  16. package/lib/cjs/components/datatable/datatable-defaults.d.ts +20 -0
  17. package/lib/cjs/components/datatable/datatable-defaults.d.ts.map +1 -0
  18. package/lib/cjs/components/datatable/datatable-defaults.js +193 -0
  19. package/lib/cjs/components/datatable/datatable-defaults.js.map +1 -0
  20. package/lib/cjs/components/datatable/datatable-layout-plugin.d.ts.map +1 -1
  21. package/lib/cjs/components/datatable/datatable-layout-plugin.js +11 -1
  22. package/lib/cjs/components/datatable/datatable-layout-plugin.js.map +1 -1
  23. package/lib/cjs/components/datatable/datatable-local-provider.d.ts.map +1 -1
  24. package/lib/cjs/components/datatable/datatable-local-provider.js +80 -24
  25. package/lib/cjs/components/datatable/datatable-local-provider.js.map +1 -1
  26. package/lib/cjs/components/datatable/datatable-pagination-renderer.d.ts.map +1 -1
  27. package/lib/cjs/components/datatable/datatable-pagination-renderer.js +3 -2
  28. package/lib/cjs/components/datatable/datatable-pagination-renderer.js.map +1 -1
  29. package/lib/cjs/components/datatable/datatable-registry.d.ts +18 -0
  30. package/lib/cjs/components/datatable/datatable-registry.d.ts.map +1 -0
  31. package/lib/cjs/components/datatable/datatable-registry.js +66 -0
  32. package/lib/cjs/components/datatable/datatable-registry.js.map +1 -0
  33. package/lib/cjs/components/datatable/datatable-remote-provider.d.ts.map +1 -1
  34. package/lib/cjs/components/datatable/datatable-remote-provider.js +1 -2
  35. package/lib/cjs/components/datatable/datatable-remote-provider.js.map +1 -1
  36. package/lib/cjs/components/datatable/datatable-search-handler.d.ts +10 -0
  37. package/lib/cjs/components/datatable/datatable-search-handler.d.ts.map +1 -0
  38. package/lib/cjs/components/datatable/datatable-search-handler.js +65 -0
  39. package/lib/cjs/components/datatable/datatable-search-handler.js.map +1 -0
  40. package/lib/cjs/components/datatable/datatable-sort.d.ts +31 -4
  41. package/lib/cjs/components/datatable/datatable-sort.d.ts.map +1 -1
  42. package/lib/cjs/components/datatable/datatable-sort.js +86 -58
  43. package/lib/cjs/components/datatable/datatable-sort.js.map +1 -1
  44. package/lib/cjs/components/datatable/datatable-spinner.d.ts +30 -0
  45. package/lib/cjs/components/datatable/datatable-spinner.d.ts.map +1 -0
  46. package/lib/cjs/components/datatable/datatable-spinner.js +54 -0
  47. package/lib/cjs/components/datatable/datatable-spinner.js.map +1 -0
  48. package/lib/cjs/components/datatable/datatable-state-persistence.d.ts +19 -0
  49. package/lib/cjs/components/datatable/datatable-state-persistence.d.ts.map +1 -0
  50. package/lib/cjs/components/datatable/datatable-state-persistence.js +59 -0
  51. package/lib/cjs/components/datatable/datatable-state-persistence.js.map +1 -0
  52. package/lib/cjs/components/datatable/datatable-table-renderer.d.ts +2 -0
  53. package/lib/cjs/components/datatable/datatable-table-renderer.d.ts.map +1 -1
  54. package/lib/cjs/components/datatable/datatable-table-renderer.js +75 -16
  55. package/lib/cjs/components/datatable/datatable-table-renderer.js.map +1 -1
  56. package/lib/cjs/components/datatable/datatable-utils.d.ts +10 -0
  57. package/lib/cjs/components/datatable/datatable-utils.d.ts.map +1 -0
  58. package/lib/cjs/components/datatable/datatable-utils.js +15 -0
  59. package/lib/cjs/components/datatable/datatable-utils.js.map +1 -0
  60. package/lib/cjs/components/datatable/datatable.d.ts +26 -34
  61. package/lib/cjs/components/datatable/datatable.d.ts.map +1 -1
  62. package/lib/cjs/components/datatable/datatable.js +155 -492
  63. package/lib/cjs/components/datatable/datatable.js.map +1 -1
  64. package/lib/cjs/components/datatable/index.d.ts +1 -1
  65. package/lib/cjs/components/datatable/index.d.ts.map +1 -1
  66. package/lib/cjs/components/datatable/types.d.ts +100 -11
  67. package/lib/cjs/components/datatable/types.d.ts.map +1 -1
  68. package/lib/cjs/index.d.ts +1 -1
  69. package/lib/cjs/index.d.ts.map +1 -1
  70. package/lib/cjs/index.js +6 -0
  71. package/lib/cjs/index.js.map +1 -1
  72. package/lib/esm/components/datatable/datatable-checkbox.d.ts +37 -1
  73. package/lib/esm/components/datatable/datatable-checkbox.d.ts.map +1 -1
  74. package/lib/esm/components/datatable/datatable-checkbox.js +142 -155
  75. package/lib/esm/components/datatable/datatable-checkbox.js.map +1 -1
  76. package/lib/esm/components/datatable/datatable-column-utils.d.ts +30 -0
  77. package/lib/esm/components/datatable/datatable-column-utils.d.ts.map +1 -0
  78. package/lib/esm/components/datatable/datatable-column-utils.js +38 -0
  79. package/lib/esm/components/datatable/datatable-column-utils.js.map +1 -0
  80. package/lib/esm/components/datatable/datatable-contracts.d.ts +2 -4
  81. package/lib/esm/components/datatable/datatable-contracts.d.ts.map +1 -1
  82. package/lib/esm/components/datatable/datatable-defaults.d.ts +20 -0
  83. package/lib/esm/components/datatable/datatable-defaults.d.ts.map +1 -0
  84. package/lib/esm/components/datatable/datatable-defaults.js +190 -0
  85. package/lib/esm/components/datatable/datatable-defaults.js.map +1 -0
  86. package/lib/esm/components/datatable/datatable-layout-plugin.d.ts.map +1 -1
  87. package/lib/esm/components/datatable/datatable-layout-plugin.js +11 -1
  88. package/lib/esm/components/datatable/datatable-layout-plugin.js.map +1 -1
  89. package/lib/esm/components/datatable/datatable-local-provider.d.ts.map +1 -1
  90. package/lib/esm/components/datatable/datatable-local-provider.js +80 -24
  91. package/lib/esm/components/datatable/datatable-local-provider.js.map +1 -1
  92. package/lib/esm/components/datatable/datatable-pagination-renderer.d.ts.map +1 -1
  93. package/lib/esm/components/datatable/datatable-pagination-renderer.js +3 -2
  94. package/lib/esm/components/datatable/datatable-pagination-renderer.js.map +1 -1
  95. package/lib/esm/components/datatable/datatable-registry.d.ts +18 -0
  96. package/lib/esm/components/datatable/datatable-registry.d.ts.map +1 -0
  97. package/lib/esm/components/datatable/datatable-registry.js +63 -0
  98. package/lib/esm/components/datatable/datatable-registry.js.map +1 -0
  99. package/lib/esm/components/datatable/datatable-remote-provider.d.ts.map +1 -1
  100. package/lib/esm/components/datatable/datatable-remote-provider.js +1 -2
  101. package/lib/esm/components/datatable/datatable-remote-provider.js.map +1 -1
  102. package/lib/esm/components/datatable/datatable-search-handler.d.ts +10 -0
  103. package/lib/esm/components/datatable/datatable-search-handler.d.ts.map +1 -0
  104. package/lib/esm/components/datatable/datatable-search-handler.js +62 -0
  105. package/lib/esm/components/datatable/datatable-search-handler.js.map +1 -0
  106. package/lib/esm/components/datatable/datatable-sort.d.ts +31 -4
  107. package/lib/esm/components/datatable/datatable-sort.d.ts.map +1 -1
  108. package/lib/esm/components/datatable/datatable-sort.js +85 -57
  109. package/lib/esm/components/datatable/datatable-sort.js.map +1 -1
  110. package/lib/esm/components/datatable/datatable-spinner.d.ts +30 -0
  111. package/lib/esm/components/datatable/datatable-spinner.d.ts.map +1 -0
  112. package/lib/esm/components/datatable/datatable-spinner.js +51 -0
  113. package/lib/esm/components/datatable/datatable-spinner.js.map +1 -0
  114. package/lib/esm/components/datatable/datatable-state-persistence.d.ts +19 -0
  115. package/lib/esm/components/datatable/datatable-state-persistence.d.ts.map +1 -0
  116. package/lib/esm/components/datatable/datatable-state-persistence.js +55 -0
  117. package/lib/esm/components/datatable/datatable-state-persistence.js.map +1 -0
  118. package/lib/esm/components/datatable/datatable-table-renderer.d.ts +2 -0
  119. package/lib/esm/components/datatable/datatable-table-renderer.d.ts.map +1 -1
  120. package/lib/esm/components/datatable/datatable-table-renderer.js +75 -16
  121. package/lib/esm/components/datatable/datatable-table-renderer.js.map +1 -1
  122. package/lib/esm/components/datatable/datatable-utils.d.ts +10 -0
  123. package/lib/esm/components/datatable/datatable-utils.d.ts.map +1 -0
  124. package/lib/esm/components/datatable/datatable-utils.js +12 -0
  125. package/lib/esm/components/datatable/datatable-utils.js.map +1 -0
  126. package/lib/esm/components/datatable/datatable.d.ts +26 -34
  127. package/lib/esm/components/datatable/datatable.d.ts.map +1 -1
  128. package/lib/esm/components/datatable/datatable.js +157 -494
  129. package/lib/esm/components/datatable/datatable.js.map +1 -1
  130. package/lib/esm/components/datatable/index.d.ts +1 -1
  131. package/lib/esm/components/datatable/index.d.ts.map +1 -1
  132. package/lib/esm/components/datatable/types.d.ts +100 -11
  133. package/lib/esm/components/datatable/types.d.ts.map +1 -1
  134. package/lib/esm/index.d.ts +1 -1
  135. package/lib/esm/index.d.ts.map +1 -1
  136. package/lib/esm/index.js +6 -0
  137. package/lib/esm/index.js.map +1 -1
  138. package/package.json +5 -1
  139. package/skills/ktui/SKILL.md +711 -0
  140. package/skills/ktui-datatable/SKILL.md +302 -0
  141. package/skills/ktui-install/SKILL.md +150 -0
  142. package/skills/ktui-select/SKILL.md +271 -0
  143. package/src/components/__tests__/component.test.ts +347 -0
  144. package/src/components/collapse/collapse.css +2 -2
  145. package/src/components/datatable/__tests__/architecture-boundaries.test.ts +56 -8
  146. package/src/components/datatable/__tests__/currency-sort.test.ts +25 -28
  147. package/src/components/datatable/__tests__/datatable-checkbox.test.ts +527 -0
  148. package/src/components/datatable/__tests__/datatable-column-utils.test.ts +117 -0
  149. package/src/components/datatable/__tests__/datatable-defaults.test.ts +57 -0
  150. package/src/components/datatable/__tests__/datatable-finalize-extended.test.ts +361 -0
  151. package/src/components/datatable/__tests__/datatable-fixed-layout.test.ts +427 -0
  152. package/src/components/datatable/__tests__/datatable-improvements.test.ts +484 -0
  153. package/src/components/datatable/__tests__/datatable-pagination-extended.test.ts +508 -0
  154. package/src/components/datatable/__tests__/datatable-public-api.test.ts +269 -0
  155. package/src/components/datatable/__tests__/datatable-registry.test.ts +172 -0
  156. package/src/components/datatable/__tests__/datatable-remote-provider.test.ts +468 -0
  157. package/src/components/datatable/__tests__/datatable-search-handler.test.ts +124 -0
  158. package/src/components/datatable/__tests__/datatable-sort-extended.test.ts +417 -0
  159. package/src/components/datatable/__tests__/datatable-spinner.test.ts +95 -0
  160. package/src/components/datatable/__tests__/datatable-table-renderer-extended.test.ts +425 -0
  161. package/src/components/datatable/__tests__/datatable-types.test.ts +117 -0
  162. package/src/components/datatable/__tests__/datatable-utils.test.ts +52 -0
  163. package/src/components/datatable/__tests__/multi-row-headers.test.ts +7 -7
  164. package/src/components/datatable/__tests__/pagination-reset.test.ts +129 -6
  165. package/src/components/datatable/__tests__/race-conditions.test.ts +11 -11
  166. package/src/components/datatable/__tests__/setup.ts +12 -4
  167. package/src/components/datatable/datatable-checkbox.ts +144 -145
  168. package/src/components/datatable/datatable-column-utils.ts +63 -0
  169. package/src/components/datatable/datatable-contracts.ts +2 -3
  170. package/src/components/datatable/datatable-defaults.ts +204 -0
  171. package/src/components/datatable/datatable-layout-plugin.ts +11 -1
  172. package/src/components/datatable/datatable-local-provider.ts +91 -28
  173. package/src/components/datatable/datatable-pagination-renderer.ts +3 -2
  174. package/src/components/datatable/datatable-registry.ts +89 -0
  175. package/src/components/datatable/datatable-remote-provider.ts +1 -3
  176. package/src/components/datatable/datatable-search-handler.ts +97 -0
  177. package/src/components/datatable/datatable-sort.ts +111 -66
  178. package/src/components/datatable/datatable-spinner.ts +103 -0
  179. package/src/components/datatable/datatable-state-persistence.ts +67 -0
  180. package/src/components/datatable/datatable-table-renderer.ts +81 -18
  181. package/src/components/datatable/datatable-utils.ts +12 -0
  182. package/src/components/datatable/datatable.ts +191 -580
  183. package/src/components/datatable/index.ts +3 -0
  184. package/src/components/datatable/types.ts +124 -23
  185. package/src/helpers/__tests__/dom.test.ts +776 -0
  186. package/src/helpers/__tests__/utils.test.ts +332 -0
  187. package/src/index.ts +10 -0
  188. package/skills/ktui-components/SKILL.md +0 -41
  189. package/skills/ktui-theming/SKILL.md +0 -50
  190. package/src/components/datatable/datatable-event-adapter.ts +0 -21
@@ -0,0 +1,117 @@
1
+ import { describe, it, expect, beforeEach } from 'vitest';
2
+ import { resolveColumns, getLogicalColumnCount } from '../datatable-column-utils';
3
+
4
+ describe('datatable-column-utils', () => {
5
+ let thead: HTMLTableSectionElement;
6
+
7
+ beforeEach(() => {
8
+ document.body.innerHTML = '';
9
+ thead = document.createElement('thead');
10
+ });
11
+
12
+ describe('resolveColumns', () => {
13
+ it('should resolve columns with data-kt-datatable-column attributes', () => {
14
+ const tr = document.createElement('tr');
15
+ ['id', 'name', 'status'].forEach(name => {
16
+ const th = document.createElement('th');
17
+ th.setAttribute('data-kt-datatable-column', name);
18
+ tr.appendChild(th);
19
+ });
20
+ thead.appendChild(tr);
21
+
22
+ const result = resolveColumns(thead);
23
+ expect(result.allThs).toHaveLength(3);
24
+ expect(result.typedThs).toHaveLength(3);
25
+ expect(result.columnsByIndex).toHaveLength(3);
26
+ expect(result.columnsByIndex[0]?.getAttribute('data-kt-datatable-column')).toBe('id');
27
+ });
28
+
29
+ it('should handle empty thead', () => {
30
+ const result = resolveColumns(thead);
31
+ expect(result.allThs).toHaveLength(0);
32
+ expect(result.typedThs).toHaveLength(0);
33
+ expect(result.columnsByIndex).toHaveLength(0);
34
+ });
35
+
36
+ it('should prefer typedThs when only some have attributes', () => {
37
+ const tr = document.createElement('tr');
38
+ const th1 = document.createElement('th');
39
+ th1.setAttribute('data-kt-datatable-column', 'id');
40
+ tr.appendChild(th1);
41
+ tr.appendChild(document.createElement('th')); // no attribute (e.g. group header in multi-row)
42
+ thead.appendChild(tr);
43
+
44
+ const result = resolveColumns(thead);
45
+ // Prefer typed ths — untyped ths are group headers in multi-row layouts
46
+ expect(result.allThs).toHaveLength(2);
47
+ expect(result.typedThs).toHaveLength(1);
48
+ expect(result.columnsByIndex).toHaveLength(1);
49
+ });
50
+
51
+ it('should fall back to allThs when no ths have attributes', () => {
52
+ const tr = document.createElement('tr');
53
+ tr.appendChild(document.createElement('th'));
54
+ tr.appendChild(document.createElement('th'));
55
+ thead.appendChild(tr);
56
+
57
+ const result = resolveColumns(thead);
58
+ // When no typed ths exist, fall back to all ths by index
59
+ expect(result.allThs).toHaveLength(2);
60
+ expect(result.typedThs).toHaveLength(0);
61
+ expect(result.columnsByIndex).toHaveLength(2);
62
+ });
63
+
64
+ it('should handle null thead', () => {
65
+ const result = resolveColumns(null);
66
+ expect(result.allThs).toHaveLength(0);
67
+ expect(result.typedThs).toHaveLength(0);
68
+ expect(result.columnsByIndex).toHaveLength(0);
69
+ });
70
+ });
71
+
72
+ describe('getLogicalColumnCount', () => {
73
+ it('should count columns with data-kt-datatable-column', () => {
74
+ const tr = document.createElement('tr');
75
+ ['id', 'name'].forEach(name => {
76
+ const th = document.createElement('th');
77
+ th.setAttribute('data-kt-datatable-column', name);
78
+ tr.appendChild(th);
79
+ });
80
+ thead.appendChild(tr);
81
+
82
+ expect(getLogicalColumnCount(thead, null)).toBe(2);
83
+ });
84
+
85
+ it('should return 0 for empty thead', () => {
86
+ expect(getLogicalColumnCount(thead, null)).toBe(0);
87
+ });
88
+
89
+ it('should prefer originalData keys when available', () => {
90
+ const originalData = [{ id: '1', name: 'test', status: 'active' }];
91
+ expect(getLogicalColumnCount(thead, null, originalData)).toBe(3);
92
+ });
93
+
94
+ it('should fall back to tbody first row td count', () => {
95
+ const tbody = document.createElement('tbody');
96
+ const tr = document.createElement('tr');
97
+ tr.appendChild(document.createElement('td'));
98
+ tr.appendChild(document.createElement('td'));
99
+ tr.appendChild(document.createElement('td'));
100
+ tbody.appendChild(tr);
101
+
102
+ expect(getLogicalColumnCount(thead, tbody)).toBe(3);
103
+ });
104
+
105
+ it('should fall back to thead columns when no data or tbody', () => {
106
+ const tr = document.createElement('tr');
107
+ ['id', 'name', 'status', 'email'].forEach(name => {
108
+ const th = document.createElement('th');
109
+ th.setAttribute('data-kt-datatable-column', name);
110
+ tr.appendChild(th);
111
+ });
112
+ thead.appendChild(tr);
113
+
114
+ expect(getLogicalColumnCount(thead, null)).toBe(4);
115
+ });
116
+ });
117
+ });
@@ -0,0 +1,57 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { DATATABLE_DEFAULTS, DEFAULT_PAGE_SIZES, DEFAULT_SEARCH_DELAY } from '../datatable-defaults';
3
+
4
+ describe('DATATABLE_DEFAULTS', () => {
5
+ it('should have all required properties', () => {
6
+ expect(DATATABLE_DEFAULTS).toHaveProperty('pageSize');
7
+ expect(DATATABLE_DEFAULTS).toHaveProperty('pageSizes');
8
+ expect(DATATABLE_DEFAULTS).toHaveProperty('stateSave');
9
+ expect(DATATABLE_DEFAULTS).toHaveProperty('attributes');
10
+ expect(DATATABLE_DEFAULTS).toHaveProperty('sort');
11
+ expect(DATATABLE_DEFAULTS).toHaveProperty('search');
12
+ expect(DATATABLE_DEFAULTS).toHaveProperty('pagination');
13
+ expect(DATATABLE_DEFAULTS).toHaveProperty('loading');
14
+ expect(DATATABLE_DEFAULTS).toHaveProperty('checkbox');
15
+ });
16
+
17
+ it('should have correct default pageSize', () => {
18
+ expect(DATATABLE_DEFAULTS.pageSize).toBe(10);
19
+ });
20
+
21
+ it('should have correct default pageSizes array', () => {
22
+ expect(DATATABLE_DEFAULTS.pageSizes).toEqual(DEFAULT_PAGE_SIZES);
23
+ });
24
+
25
+ it('should have stateSave enabled by default', () => {
26
+ expect(DATATABLE_DEFAULTS.stateSave).toBe(true);
27
+ });
28
+
29
+ it('should have table selector in attributes', () => {
30
+ expect(DATATABLE_DEFAULTS.attributes?.table).toContain('data-kt-datatable-table');
31
+ });
32
+
33
+ it('should have sort classes defined', () => {
34
+ expect(DATATABLE_DEFAULTS.sort?.classes?.base).toBeDefined();
35
+ expect(DATATABLE_DEFAULTS.sort?.classes?.asc).toBeDefined();
36
+ expect(DATATABLE_DEFAULTS.sort?.classes?.desc).toBeDefined();
37
+ });
38
+
39
+ it('should have search delay defined', () => {
40
+ expect(DATATABLE_DEFAULTS.search?.delay).toBe(DEFAULT_SEARCH_DELAY);
41
+ });
42
+
43
+ it('should have pagination templates', () => {
44
+ expect(DATATABLE_DEFAULTS.pagination?.number?.class).toBeDefined();
45
+ expect(DATATABLE_DEFAULTS.pagination?.previous?.class).toBeDefined();
46
+ expect(DATATABLE_DEFAULTS.pagination?.next?.class).toBeDefined();
47
+ });
48
+
49
+ it('should have _state as empty object', () => {
50
+ expect(DATATABLE_DEFAULTS._state).toEqual({});
51
+ });
52
+
53
+ it('should have loading template', () => {
54
+ expect(DATATABLE_DEFAULTS.loading?.template).toBeDefined();
55
+ expect(DATATABLE_DEFAULTS.loading?.content).toBe('Loading...');
56
+ });
57
+ });
@@ -0,0 +1,361 @@
1
+ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
2
+
3
+ vi.mock('../../../index', () => ({
4
+ default: { init: vi.fn() },
5
+ }));
6
+
7
+ vi.mock('../../helpers/data', () => {
8
+ const map = new Map();
9
+ return {
10
+ default: {
11
+ set: vi.fn((el: HTMLElement, key: string, val: unknown) => {
12
+ if (!map.has(el)) map.set(el, new Map());
13
+ map.get(el).set(key, val);
14
+ }),
15
+ get: vi.fn((el: HTMLElement, key: string) => {
16
+ return map.has(el) ? map.get(el).get(key) ?? null : null;
17
+ }),
18
+ has: vi.fn((el: HTMLElement, key: string) => {
19
+ return map.has(el) && map.get(el).has(key);
20
+ }),
21
+ remove: vi.fn((el: HTMLElement, key: string) => {
22
+ if (map.has(el)) map.get(el).delete(key);
23
+ }),
24
+ clear: vi.fn((el: HTMLElement) => {
25
+ map.delete(el);
26
+ }),
27
+ },
28
+ };
29
+ });
30
+
31
+ import { KTDataTable } from '../datatable';
32
+ import { KTDataTableConfigInterface } from '../types';
33
+
34
+ function createLocalTableHtml(overrides: Record<string, string> = {}): string {
35
+ const attrs = Object.entries(overrides)
36
+ .map(([k, v]) => `${k}="${v}"`)
37
+ .join(' ');
38
+ return `
39
+ <div data-kt-datatable="true" ${attrs}>
40
+ <table data-kt-datatable-table="true">
41
+ <thead>
42
+ <tr>
43
+ <th data-kt-datatable-column="name">Name</th>
44
+ <th data-kt-datatable-column="age">Age</th>
45
+ </tr>
46
+ </thead>
47
+ <tbody>
48
+ <tr><td>Alice</td><td>30</td></tr>
49
+ <tr><td>Bob</td><td>25</td></tr>
50
+ </tbody>
51
+ </table>
52
+ </div>
53
+ `;
54
+ }
55
+
56
+ function createLocalDatatable(
57
+ overrides: Record<string, string> = {},
58
+ configOverrides: Partial<KTDataTableConfigInterface> = {},
59
+ ) {
60
+ const container = document.createElement('div');
61
+ container.innerHTML = createLocalTableHtml(overrides);
62
+ document.body.appendChild(container);
63
+ const table = container.querySelector('[data-kt-datatable]') as HTMLElement;
64
+ return { container, table, dt: new KTDataTable(table, configOverrides) };
65
+ }
66
+
67
+ describe('KTDataTable _finalize and config edge cases', () => {
68
+ beforeEach(() => {
69
+ document.body.innerHTML = '';
70
+ });
71
+
72
+ describe('_finalize behavior', () => {
73
+ it('adds datatable-initialized class to element', async () => {
74
+ const { table, dt } = createLocalDatatable();
75
+ // _updateData is async; wait for the microtask queue to flush
76
+ await vi.waitFor(() => {
77
+ expect(table.classList.contains('datatable-initialized')).toBe(true);
78
+ });
79
+ dt.dispose();
80
+ });
81
+
82
+ it('_emit with no listeners does not throw', () => {
83
+ const { table, dt } = createLocalDatatable();
84
+ expect(() => (dt as any)._emit('nonexistent-event')).not.toThrow();
85
+ dt.dispose();
86
+ });
87
+
88
+ it('_emit with registered handlers calls them', () => {
89
+ const { table, dt } = createLocalDatatable();
90
+ const handler = vi.fn();
91
+ dt.on('custom-event', handler);
92
+ (dt as any)._emit('custom-event', { data: 123 });
93
+ expect(handler).toHaveBeenCalledWith({ data: 123 });
94
+ dt.dispose();
95
+ });
96
+ });
97
+
98
+ describe('_initDefaultConfig with data attributes', () => {
99
+ it('reads pageSizes from data attribute', () => {
100
+ const container = document.createElement('div');
101
+ container.innerHTML = `
102
+ <div data-kt-datatable="true" data-kt-datatable-page-sizes="[3,6,9]">
103
+ <table data-kt-datatable-table="true">
104
+ <thead><tr><th data-kt-datatable-column="name">Name</th></tr></thead>
105
+ <tbody><tr><td>Alice</td></tr></tbody>
106
+ </table>
107
+ </div>
108
+ `;
109
+ document.body.appendChild(container);
110
+ const el = container.querySelector('[data-kt-datatable]') as HTMLElement;
111
+ const dt = new KTDataTable(el);
112
+ const state = dt.getState();
113
+ expect(state.pageSize).toBeDefined();
114
+ dt.dispose();
115
+ });
116
+
117
+ it('reads search delay from data attribute', () => {
118
+ const container = document.createElement('div');
119
+ container.innerHTML = `
120
+ <div data-kt-datatable="true" data-kt-datatable-search-delay="1000">
121
+ <table data-kt-datatable-table="true">
122
+ <thead><tr><th data-kt-datatable-column="name">Name</th></tr></thead>
123
+ <tbody><tr><td>Alice</td></tr></tbody>
124
+ </table>
125
+ </div>
126
+ `;
127
+ document.body.appendChild(container);
128
+ const el = container.querySelector('[data-kt-datatable]') as HTMLElement;
129
+ const dt = new KTDataTable(el);
130
+ expect(dt).toBeDefined();
131
+ dt.dispose();
132
+ });
133
+ });
134
+
135
+ describe('constructor with apiEndpoint', () => {
136
+ it('creates remote provider when apiEndpoint is set', () => {
137
+ const container = document.createElement('div');
138
+ container.innerHTML = `
139
+ <div data-kt-datatable="true">
140
+ <table data-kt-datatable-table="true">
141
+ <thead><tr><th data-kt-datatable-column="name">Name</th></tr></thead>
142
+ <tbody></tbody>
143
+ </table>
144
+ </div>
145
+ `;
146
+ document.body.appendChild(container);
147
+ const el = container.querySelector('[data-kt-datatable]') as HTMLElement;
148
+ const dt = new KTDataTable(el, {
149
+ apiEndpoint: 'https://example.com/api/data',
150
+ });
151
+ const remoteProvider = (dt as any)._remoteProvider;
152
+ expect(remoteProvider).toBeDefined();
153
+ dt.dispose();
154
+ });
155
+ });
156
+
157
+ describe('constructor without apiEndpoint', () => {
158
+ it('creates local provider when no apiEndpoint', () => {
159
+ const { table, dt } = createLocalDatatable();
160
+ const localProvider = (dt as any)._localProvider;
161
+ expect(localProvider).toBeDefined();
162
+ dt.dispose();
163
+ });
164
+ });
165
+
166
+ describe('dispose cleanup', () => {
167
+ it('cleans up checkbox handler', () => {
168
+ const { table, dt } = createLocalDatatable();
169
+ const checkbox = (dt as any)._checkbox;
170
+ const spy = vi.spyOn(checkbox, 'dispose');
171
+ dt.dispose();
172
+ expect(spy).toHaveBeenCalled();
173
+ });
174
+
175
+ it('cleans up sort handler', () => {
176
+ const { table, dt } = createLocalDatatable();
177
+ const sortHandler = (dt as any)._sortHandler;
178
+ const spy = vi.spyOn(sortHandler, 'dispose');
179
+ dt.dispose();
180
+ expect(spy).toHaveBeenCalled();
181
+ });
182
+
183
+ it('cleans up search handler', () => {
184
+ const { table, dt } = createLocalDatatable();
185
+ const searchHandler = (dt as any)._searchHandler;
186
+ const spy = vi.spyOn(searchHandler, 'detach');
187
+ dt.dispose();
188
+ expect(spy).toHaveBeenCalled();
189
+ });
190
+
191
+ it('removes element from datatable registry', () => {
192
+ const { table, dt } = createLocalDatatable();
193
+ dt.dispose();
194
+ expect(KTDataTable.getInstance(table)).toBeUndefined();
195
+ });
196
+
197
+ it('removes datatable-initialized class', () => {
198
+ const { table, dt } = createLocalDatatable();
199
+ dt.dispose();
200
+ expect(table.classList.contains('datatable-initialized')).toBe(false);
201
+ });
202
+ });
203
+
204
+ describe('dispose with layout plugin', () => {
205
+ it('disposes layout plugin when present', () => {
206
+ const disposeSpy = vi.fn();
207
+ const { table, dt } = createLocalDatatable({}, {
208
+ layoutPlugin: {
209
+ dispose: disposeSpy,
210
+ },
211
+ });
212
+ dt.dispose();
213
+ expect(disposeSpy).toHaveBeenCalled();
214
+ });
215
+ });
216
+
217
+ describe('stateSave', () => {
218
+ it('does not save state when stateSave is false', () => {
219
+ const { table, dt } = createLocalDatatable({}, { stateSave: false });
220
+ const persistence = (dt as any)._statePersistence;
221
+ const spy = vi.spyOn(persistence, 'save');
222
+ // Trigger a redraw that would normally save state
223
+ dt.reload();
224
+ // Since stateSave is false, save should not be called in _draw
225
+ // (but _updateData calls _finalize which doesn't save)
226
+ expect(spy).not.toHaveBeenCalled();
227
+ dt.dispose();
228
+ });
229
+ });
230
+
231
+ describe('stateNamespace', () => {
232
+ it('uses stateNamespace from config when provided', () => {
233
+ const { table, dt } = createLocalDatatable({}, {
234
+ stateNamespace: 'custom-namespace',
235
+ });
236
+ expect(dt).toBeDefined();
237
+ dt.dispose();
238
+ });
239
+ });
240
+
241
+ describe('_initElements edge cases', () => {
242
+ it('throws when table element not found', () => {
243
+ const container = document.createElement('div');
244
+ container.innerHTML = `
245
+ <div data-kt-datatable="true">
246
+ <div>No table here</div>
247
+ </div>
248
+ `;
249
+ document.body.appendChild(container);
250
+ const el = container.querySelector('[data-kt-datatable]') as HTMLElement;
251
+ expect(() => new KTDataTable(el)).toThrow();
252
+ });
253
+ });
254
+
255
+ describe('MutationObserver destroy event', () => {
256
+ it('fires destroy event when element is removed from DOM', async () => {
257
+ vi.useFakeTimers();
258
+ const { container, table, dt } = createLocalDatatable();
259
+ const handler = vi.fn();
260
+ dt.on('destroy', handler);
261
+
262
+ // Remove the element from DOM — MutationObserver should detect this
263
+ container.remove();
264
+
265
+ // Advance timers to allow MutationObserver callback to fire
266
+ vi.advanceTimersByTime(100);
267
+
268
+ // Note: jsdom may not fully support MutationObserver,
269
+ // so we just verify dispose works without error
270
+ expect(() => dt.dispose()).not.toThrow();
271
+ vi.useRealTimers();
272
+ });
273
+ });
274
+
275
+ describe('config normalization', () => {
276
+ it('normalizes invalid pageSize to first pageSizes entry', () => {
277
+ const { table, dt } = createLocalDatatable({}, { pageSize: -5 });
278
+ const state = dt.getState();
279
+ expect(state.pageSize).toBeGreaterThan(0);
280
+ dt.dispose();
281
+ });
282
+
283
+ it('normalizes pageSize to integer', () => {
284
+ const { table, dt } = createLocalDatatable({}, { pageSize: 7.5 });
285
+ const state = dt.getState();
286
+ expect(Number.isInteger(state.pageSize)).toBe(true);
287
+ dt.dispose();
288
+ });
289
+
290
+ it('filters out invalid pageSizes entries', () => {
291
+ const { table, dt } = createLocalDatatable({}, {
292
+ pageSizes: [5, -1, 0, 20, NaN],
293
+ });
294
+ const config = (dt as any)._config;
295
+ expect(config.pageSizes).toContain(5);
296
+ expect(config.pageSizes).toContain(20);
297
+ expect(config.pageSizes).not.toContain(-1);
298
+ expect(config.pageSizes).not.toContain(0);
299
+ expect(config.pageSizes).not.toContain(NaN);
300
+ dt.dispose();
301
+ });
302
+
303
+ it('uses default pageSizes when all entries are invalid', () => {
304
+ const { table, dt } = createLocalDatatable({}, {
305
+ pageSizes: [-1, 0],
306
+ });
307
+ const config = (dt as any)._config;
308
+ expect(config.pageSizes.length).toBeGreaterThan(0);
309
+ dt.dispose();
310
+ });
311
+ });
312
+
313
+ describe('goPage edge cases', () => {
314
+ it('ignores page less than 1', () => {
315
+ const { table, dt } = createLocalDatatable();
316
+ const spy = vi.spyOn(dt as any, '_paginateData');
317
+ dt.goPage(0);
318
+ expect(spy).not.toHaveBeenCalled();
319
+ dt.dispose();
320
+ });
321
+
322
+ it('ignores non-integer page', () => {
323
+ const { table, dt } = createLocalDatatable();
324
+ const spy = vi.spyOn(dt as any, '_paginateData');
325
+ dt.goPage(1.5);
326
+ expect(spy).not.toHaveBeenCalled();
327
+ dt.dispose();
328
+ });
329
+ });
330
+
331
+ describe('setPageSize edge cases', () => {
332
+ it('ignores non-integer pageSize', () => {
333
+ const { table, dt } = createLocalDatatable();
334
+ const spy = vi.spyOn(dt as any, '_reloadPageSize');
335
+ dt.setPageSize(5.5);
336
+ expect(spy).not.toHaveBeenCalled();
337
+ dt.dispose();
338
+ });
339
+ });
340
+
341
+ describe('sort', () => {
342
+ it('sorts data by field and updates state', async () => {
343
+ const { table, dt } = createLocalDatatable();
344
+ dt.sort('name');
345
+ const state = dt.getState();
346
+ expect(state.sortField).toBe('name');
347
+ expect(state.sortOrder).toBeTruthy();
348
+ dt.dispose();
349
+ });
350
+
351
+ it('toggles sort order on repeated calls', async () => {
352
+ const { table, dt } = createLocalDatatable();
353
+ dt.sort('name');
354
+ const firstOrder = dt.getState().sortOrder;
355
+ dt.sort('name');
356
+ const secondOrder = dt.getState().sortOrder;
357
+ expect(firstOrder).not.toBe(secondOrder);
358
+ dt.dispose();
359
+ });
360
+ });
361
+ });