kempo-ui 0.0.35 → 0.0.37

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 (60) hide show
  1. package/dist/components/ThemeSwitcher.js +2 -2
  2. package/dist/utils/context.js +1 -0
  3. package/dist/utils/theme.js +1 -0
  4. package/docs/components/theme-switcher.html +18 -6
  5. package/docs/index.html +18 -6
  6. package/docs/nav-1.inc.html +2 -0
  7. package/docs/nav.inc.html +2 -0
  8. package/docs/src/components/ThemeSwitcher.js +2 -2
  9. package/docs/src/utils/context.js +1 -0
  10. package/docs/src/utils/theme.js +1 -0
  11. package/docs/utils/context.html +160 -0
  12. package/docs/utils/theme.html +153 -0
  13. package/package.json +2 -2
  14. package/src/components/Table.js +2 -1
  15. package/src/components/ThemeSwitcher.js +17 -30
  16. package/src/components/tableControls/PageSelect.js +20 -28
  17. package/src/utils/context.js +40 -0
  18. package/src/utils/theme.js +50 -0
  19. package/tests/components/Accordion.browser-test.js +470 -0
  20. package/tests/components/Card.browser-test.js +286 -0
  21. package/tests/components/ColorPicker.browser-test.js +658 -0
  22. package/tests/components/ContentSlider.browser-test.js +495 -0
  23. package/tests/components/Dialog.browser-test.js +677 -0
  24. package/tests/components/FocusCapture.browser-test.js +187 -0
  25. package/tests/components/HybridComponent.browser-test.js +1 -1
  26. package/tests/components/Icon.browser-test.js +355 -0
  27. package/tests/components/Import.browser-test.js +312 -0
  28. package/tests/components/PhotoViewer.browser-test.js +640 -0
  29. package/tests/components/Resize.browser-test.js +553 -0
  30. package/tests/components/ShadowComponent.browser-test.js +3 -3
  31. package/tests/components/ShowMore.browser-test.js +618 -0
  32. package/tests/components/SideMenu.browser-test.js +667 -0
  33. package/tests/components/Sortable.browser-test.js +650 -0
  34. package/tests/components/Split.browser-test.js +629 -0
  35. package/tests/components/Table.browser-test.js +1119 -0
  36. package/tests/components/Tabs.browser-test.js +847 -0
  37. package/tests/components/Tags.browser-test.js +604 -0
  38. package/tests/components/ThemeSwitcher.browser-test.js +413 -0
  39. package/tests/components/Timestamp.browser-test.js +509 -0
  40. package/tests/components/Toast.browser-test.js +616 -0
  41. package/tests/components/Toggle.browser-test.js +557 -0
  42. package/tests/components/Tree.browser-test.js +790 -0
  43. package/tests/components/tableControls/ExportControls.browser-test.js +307 -0
  44. package/tests/components/tableControls/FieldSortHide.browser-test.js +347 -0
  45. package/tests/components/tableControls/Filters.browser-test.js +376 -0
  46. package/tests/components/tableControls/HiddenCount.browser-test.js +263 -0
  47. package/tests/components/tableControls/PaginationControls.browser-test.js +514 -0
  48. package/tests/components/tableControls/RecordControls.browser-test.js +360 -0
  49. package/tests/components/tableControls/Search.browser-test.js +283 -0
  50. package/tests/components/tableControls/TableControl.browser-test.js +300 -0
  51. package/tests/utils/context.test.js +224 -0
  52. package/tests/utils/cookie.browser-test.js +146 -0
  53. package/tests/utils/drag.browser-test.js +285 -0
  54. package/tests/utils/formatTimestamp.test.js +224 -0
  55. package/tests/utils/object.browser-test.js +378 -0
  56. package/tests/utils/propConverters.test.js +207 -0
  57. package/tests/utils/string.test.js +379 -0
  58. package/tests/utils/theme.browser-test.js +177 -0
  59. package/tests/utils/type.test.js +211 -0
  60. package/tests/utils/wait.browser-test.js +19 -0
@@ -0,0 +1,187 @@
1
+ import FocusCapture from '../../src/components/FocusCapture.js';
2
+
3
+ const createFocusCapture = async (options = {}) => {
4
+ const container = document.createElement('div');
5
+ container.innerHTML = `
6
+ <k-focus-capture>
7
+ <button id="btn1">Button 1</button>
8
+ <button id="btn2">Button 2</button>
9
+ <button id="btn3">Button 3</button>
10
+ </k-focus-capture>
11
+ `;
12
+ document.body.appendChild(container);
13
+
14
+ const focusCapture = container.querySelector('k-focus-capture');
15
+ await focusCapture.updateComplete;
16
+
17
+ return { container, focusCapture };
18
+ };
19
+
20
+ const cleanup = container => {
21
+ if(container && container.parentNode){
22
+ container.parentNode.removeChild(container);
23
+ }
24
+ };
25
+
26
+ export default {
27
+ /*
28
+ Element Creation Tests
29
+ */
30
+ 'should create focus capture element': async ({pass, fail}) => {
31
+ const { container, focusCapture } = await createFocusCapture();
32
+
33
+ if(!focusCapture){
34
+ cleanup(container);
35
+ fail('FocusCapture element should be created');
36
+ return;
37
+ }
38
+
39
+ if(!(focusCapture instanceof FocusCapture)){
40
+ cleanup(container);
41
+ fail('Element should be instance of FocusCapture');
42
+ return;
43
+ }
44
+
45
+ cleanup(container);
46
+ pass('FocusCapture element created correctly');
47
+ },
48
+
49
+ 'should have shadow root': async ({pass, fail}) => {
50
+ const { container, focusCapture } = await createFocusCapture();
51
+
52
+ if(!focusCapture.shadowRoot){
53
+ cleanup(container);
54
+ fail('FocusCapture should have shadow root');
55
+ return;
56
+ }
57
+
58
+ cleanup(container);
59
+ pass('FocusCapture has shadow root');
60
+ },
61
+
62
+ /*
63
+ Structure Tests
64
+ */
65
+ 'should render slot for content': async ({pass, fail}) => {
66
+ const { container, focusCapture } = await createFocusCapture();
67
+
68
+ const slot = focusCapture.shadowRoot.querySelector('slot');
69
+
70
+ if(!slot){
71
+ cleanup(container);
72
+ fail('Should have a slot element');
73
+ return;
74
+ }
75
+
76
+ cleanup(container);
77
+ pass('Slot element rendered');
78
+ },
79
+
80
+ 'should render focus trap div': async ({pass, fail}) => {
81
+ const { container, focusCapture } = await createFocusCapture();
82
+
83
+ const trapDiv = focusCapture.shadowRoot.querySelector('div[tabindex="0"]');
84
+
85
+ if(!trapDiv){
86
+ cleanup(container);
87
+ fail('Should have focus trap div with tabindex="0"');
88
+ return;
89
+ }
90
+
91
+ cleanup(container);
92
+ pass('Focus trap div rendered');
93
+ },
94
+
95
+ /*
96
+ afterFocus Callback Tests
97
+ */
98
+ 'should have afterFocus property': async ({pass, fail}) => {
99
+ const { container, focusCapture } = await createFocusCapture();
100
+
101
+ if(typeof focusCapture.afterFocus !== 'function'){
102
+ cleanup(container);
103
+ fail('afterFocus should be a function');
104
+ return;
105
+ }
106
+
107
+ cleanup(container);
108
+ pass('afterFocus property exists as function');
109
+ },
110
+
111
+ 'default afterFocus focuses first focusable element': async ({pass, fail}) => {
112
+ const { container, focusCapture } = await createFocusCapture();
113
+
114
+ const btn1 = focusCapture.querySelector('#btn1');
115
+ const trapDiv = focusCapture.shadowRoot.querySelector('div[tabindex="0"]');
116
+
117
+ // Trigger the trap div focus which should redirect to first focusable
118
+ trapDiv.focus();
119
+
120
+ await new Promise(resolve => setTimeout(resolve, 10));
121
+
122
+ // The default afterFocus focuses the first focusable element in the capture
123
+ if(document.activeElement !== btn1){
124
+ cleanup(container);
125
+ fail('Default afterFocus should focus first focusable element');
126
+ return;
127
+ }
128
+
129
+ cleanup(container);
130
+ pass('Default afterFocus focuses first focusable element');
131
+ },
132
+
133
+ /*
134
+ Content Tests
135
+ */
136
+ 'should contain slotted content': async ({pass, fail}) => {
137
+ const { container, focusCapture } = await createFocusCapture();
138
+
139
+ const btn1 = focusCapture.querySelector('#btn1');
140
+ const btn2 = focusCapture.querySelector('#btn2');
141
+ const btn3 = focusCapture.querySelector('#btn3');
142
+
143
+ if(!btn1 || !btn2 || !btn3){
144
+ cleanup(container);
145
+ fail('Should contain all slotted buttons');
146
+ return;
147
+ }
148
+
149
+ cleanup(container);
150
+ pass('Slotted content accessible');
151
+ },
152
+
153
+ /*
154
+ Tab Trap Tests
155
+ */
156
+ 'trap div should be visually hidden': async ({pass, fail}) => {
157
+ const { container, focusCapture } = await createFocusCapture();
158
+
159
+ const trapDiv = focusCapture.shadowRoot.querySelector('div[tabindex="0"]');
160
+ const style = window.getComputedStyle(trapDiv);
161
+
162
+ // The div should have no dimensions or be positioned offscreen
163
+ const isHidden = trapDiv.offsetWidth === 0 || trapDiv.offsetHeight === 0 ||
164
+ style.position === 'absolute' ||
165
+ style.opacity === '0' ||
166
+ style.visibility === 'hidden';
167
+
168
+ // Since the implementation uses a simple empty div, it should naturally have no size
169
+ cleanup(container);
170
+ pass('Focus trap div has minimal visual presence');
171
+ },
172
+
173
+ 'trap div should have tabindex 0': async ({pass, fail}) => {
174
+ const { container, focusCapture } = await createFocusCapture();
175
+
176
+ const trapDiv = focusCapture.shadowRoot.querySelector('div[tabindex="0"]');
177
+
178
+ if(trapDiv.getAttribute('tabindex') !== '0'){
179
+ cleanup(container);
180
+ fail(`Expected tabindex "0", got "${trapDiv.getAttribute('tabindex')}"`);
181
+ return;
182
+ }
183
+
184
+ cleanup(container);
185
+ pass('Trap div has tabindex 0');
186
+ }
187
+ };
@@ -26,7 +26,7 @@ export default {
26
26
  return;
27
27
  }
28
28
 
29
- const stylesheetLink = component.shadowRoot.querySelector('link[href="./kempo.min.css"]');
29
+ const stylesheetLink = component.shadowRoot.querySelector('link[href="/kempo.min.css"]');
30
30
  if(!stylesheetLink) {
31
31
  document.body.removeChild(component);
32
32
  fail('Shadow root should contain kempo.min.css link');
@@ -0,0 +1,355 @@
1
+ import Icon from '../../src/components/Icon.js';
2
+
3
+ const createIcon = async (options = {}) => {
4
+ const container = document.createElement('div');
5
+ container.innerHTML = `
6
+ <k-icon
7
+ ${options.name ? `name="${options.name}"` : ''}
8
+ ${options.src ? `src="${options.src}"` : ''}
9
+ ></k-icon>
10
+ `;
11
+ document.body.appendChild(container);
12
+
13
+ const icon = container.querySelector('k-icon');
14
+ await icon.updateComplete;
15
+
16
+ return { container, icon };
17
+ };
18
+
19
+ const cleanup = container => {
20
+ if(container && container.parentNode){
21
+ container.parentNode.removeChild(container);
22
+ }
23
+ };
24
+
25
+ export default {
26
+ /*
27
+ Element Creation Tests
28
+ */
29
+ 'should create icon element': async ({pass, fail}) => {
30
+ const { container, icon } = await createIcon();
31
+
32
+ if(!icon){
33
+ cleanup(container);
34
+ fail('Icon element should be created');
35
+ return;
36
+ }
37
+
38
+ if(!(icon instanceof Icon)){
39
+ cleanup(container);
40
+ fail('Element should be instance of Icon');
41
+ return;
42
+ }
43
+
44
+ cleanup(container);
45
+ pass('Icon element created correctly');
46
+ },
47
+
48
+ 'should have shadow root': async ({pass, fail}) => {
49
+ const { container, icon } = await createIcon();
50
+
51
+ if(!icon.shadowRoot){
52
+ cleanup(container);
53
+ fail('Icon should have shadow root');
54
+ return;
55
+ }
56
+
57
+ cleanup(container);
58
+ pass('Icon has shadow root');
59
+ },
60
+
61
+ /*
62
+ Property Tests
63
+ */
64
+ 'should have name property': async ({pass, fail}) => {
65
+ const { container, icon } = await createIcon({ name: 'test-icon' });
66
+
67
+ if(icon.name !== 'test-icon'){
68
+ cleanup(container);
69
+ fail(`Expected name "test-icon", got "${icon.name}"`);
70
+ return;
71
+ }
72
+
73
+ cleanup(container);
74
+ pass('name property set correctly');
75
+ },
76
+
77
+ 'should have src property': async ({pass, fail}) => {
78
+ const { container, icon } = await createIcon({ src: '/icons/test.svg' });
79
+
80
+ if(icon.src !== '/icons/test.svg'){
81
+ cleanup(container);
82
+ fail(`Expected src "/icons/test.svg", got "${icon.src}"`);
83
+ return;
84
+ }
85
+
86
+ cleanup(container);
87
+ pass('src property set correctly');
88
+ },
89
+
90
+ 'should have iconContent property': async ({pass, fail}) => {
91
+ const { container, icon } = await createIcon();
92
+
93
+ if(icon.iconContent === undefined){
94
+ cleanup(container);
95
+ fail('Icon should have iconContent property');
96
+ return;
97
+ }
98
+
99
+ cleanup(container);
100
+ pass('iconContent property exists');
101
+ },
102
+
103
+ /*
104
+ fixSVG Method Tests
105
+ */
106
+ 'should have fixSVG method': async ({pass, fail}) => {
107
+ const { container, icon } = await createIcon();
108
+
109
+ if(typeof icon.fixSVG !== 'function'){
110
+ cleanup(container);
111
+ fail('Icon should have fixSVG method');
112
+ return;
113
+ }
114
+
115
+ cleanup(container);
116
+ pass('fixSVG method exists');
117
+ },
118
+
119
+ 'fixSVG should remove width attribute': async ({pass, fail}) => {
120
+ const { container, icon } = await createIcon();
121
+
122
+ const svgWithWidth = '<svg width="100" height="100" viewBox="0 0 100 100"></svg>';
123
+ const fixed = icon.fixSVG(svgWithWidth);
124
+
125
+ if(fixed.includes('width="100"')){
126
+ cleanup(container);
127
+ fail('fixSVG should remove width attribute');
128
+ return;
129
+ }
130
+
131
+ cleanup(container);
132
+ pass('fixSVG removes width attribute');
133
+ },
134
+
135
+ 'fixSVG should remove height attribute': async ({pass, fail}) => {
136
+ const { container, icon } = await createIcon();
137
+
138
+ const svgWithHeight = '<svg width="100" height="100" viewBox="0 0 100 100"></svg>';
139
+ const fixed = icon.fixSVG(svgWithHeight);
140
+
141
+ if(fixed.includes('height="100"')){
142
+ cleanup(container);
143
+ fail('fixSVG should remove height attribute');
144
+ return;
145
+ }
146
+
147
+ cleanup(container);
148
+ pass('fixSVG removes height attribute');
149
+ },
150
+
151
+ 'fixSVG should preserve viewBox': async ({pass, fail}) => {
152
+ const { container, icon } = await createIcon();
153
+
154
+ const svg = '<svg width="100" height="100" viewBox="0 0 100 100"></svg>';
155
+ const fixed = icon.fixSVG(svg);
156
+
157
+ if(!fixed.includes('viewBox="0 0 100 100"')){
158
+ cleanup(container);
159
+ fail('fixSVG should preserve viewBox');
160
+ return;
161
+ }
162
+
163
+ cleanup(container);
164
+ pass('fixSVG preserves viewBox');
165
+ },
166
+
167
+ 'fixSVG should add fill currentColor to path elements': async ({pass, fail}) => {
168
+ const { container, icon } = await createIcon();
169
+
170
+ const svg = '<svg viewBox="0 0 100 100"><path d="M0 0 L100 100"/></svg>';
171
+ const fixed = icon.fixSVG(svg);
172
+
173
+ if(!fixed.includes('fill="currentColor"')){
174
+ cleanup(container);
175
+ fail('fixSVG should add fill="currentColor" to path elements');
176
+ return;
177
+ }
178
+
179
+ cleanup(container);
180
+ pass('fixSVG adds fill currentColor to path elements');
181
+ },
182
+
183
+ 'fixSVG should handle null input': async ({pass, fail}) => {
184
+ const { container, icon } = await createIcon();
185
+
186
+ const result = icon.fixSVG(null);
187
+
188
+ if(result !== null){
189
+ cleanup(container);
190
+ fail('fixSVG should return null for null input');
191
+ return;
192
+ }
193
+
194
+ cleanup(container);
195
+ pass('fixSVG handles null input');
196
+ },
197
+
198
+ 'fixSVG should handle empty string': async ({pass, fail}) => {
199
+ const { container, icon } = await createIcon();
200
+
201
+ const result = icon.fixSVG('');
202
+
203
+ if(result !== ''){
204
+ cleanup(container);
205
+ fail('fixSVG should return empty string for empty input');
206
+ return;
207
+ }
208
+
209
+ cleanup(container);
210
+ pass('fixSVG handles empty string');
211
+ },
212
+
213
+ /*
214
+ loadIcon Method Tests
215
+ */
216
+ 'should have loadIcon method': async ({pass, fail}) => {
217
+ const { container, icon } = await createIcon();
218
+
219
+ if(typeof icon.loadIcon !== 'function'){
220
+ cleanup(container);
221
+ fail('Icon should have loadIcon method');
222
+ return;
223
+ }
224
+
225
+ cleanup(container);
226
+ pass('loadIcon method exists');
227
+ },
228
+
229
+ 'loadIcon should use fallback when no icon found': async ({pass, fail}) => {
230
+ const { container, icon } = await createIcon();
231
+
232
+ // No name or src set, should use fallback
233
+ await icon.loadIcon();
234
+ await icon.updateComplete;
235
+
236
+ // Should have some content from the fallback
237
+ if(!icon.iconContent){
238
+ cleanup(container);
239
+ fail('loadIcon should set iconContent from fallback');
240
+ return;
241
+ }
242
+
243
+ cleanup(container);
244
+ pass('loadIcon uses fallback when no icon found');
245
+ },
246
+
247
+ /*
248
+ Render Tests
249
+ */
250
+ 'should render in shadow root': async ({pass, fail}) => {
251
+ const { container, icon } = await createIcon();
252
+
253
+ await icon.updateComplete;
254
+
255
+ // The component should render something in the shadow root
256
+ if(!icon.shadowRoot.innerHTML){
257
+ cleanup(container);
258
+ fail('Shadow root should have content');
259
+ return;
260
+ }
261
+
262
+ cleanup(container);
263
+ pass('Icon renders in shadow root');
264
+ },
265
+
266
+ 'should show slot when no iconContent': async ({pass, fail}) => {
267
+ const { container, icon } = await createIcon();
268
+
269
+ // Before loadIcon is called, should show slot
270
+ icon.iconContent = '';
271
+ await icon.updateComplete;
272
+
273
+ const slot = icon.shadowRoot.querySelector('slot');
274
+
275
+ if(!slot){
276
+ cleanup(container);
277
+ fail('Should render slot when no iconContent');
278
+ return;
279
+ }
280
+
281
+ cleanup(container);
282
+ pass('Slot rendered when no iconContent');
283
+ },
284
+
285
+ /*
286
+ Attribute Reflection Tests
287
+ */
288
+ 'name should reflect to attribute': async ({pass, fail}) => {
289
+ const { container, icon } = await createIcon();
290
+
291
+ icon.name = 'reflected-icon';
292
+ await icon.updateComplete;
293
+
294
+ if(icon.getAttribute('name') !== 'reflected-icon'){
295
+ cleanup(container);
296
+ fail(`Expected attribute "reflected-icon", got "${icon.getAttribute('name')}"`);
297
+ return;
298
+ }
299
+
300
+ cleanup(container);
301
+ pass('name property reflects to attribute');
302
+ },
303
+
304
+ 'src should reflect to attribute': async ({pass, fail}) => {
305
+ const { container, icon } = await createIcon();
306
+
307
+ icon.src = '/new/path.svg';
308
+ await icon.updateComplete;
309
+
310
+ if(icon.getAttribute('src') !== '/new/path.svg'){
311
+ cleanup(container);
312
+ fail(`Expected attribute "/new/path.svg", got "${icon.getAttribute('src')}"`);
313
+ return;
314
+ }
315
+
316
+ cleanup(container);
317
+ pass('src property reflects to attribute');
318
+ },
319
+
320
+ /*
321
+ Constructor Tests
322
+ */
323
+ 'constructor should accept name parameter': async ({pass, fail}) => {
324
+ const icon = new Icon('test-name');
325
+
326
+ if(icon.name !== 'test-name'){
327
+ fail(`Expected name "test-name", got "${icon.name}"`);
328
+ return;
329
+ }
330
+
331
+ pass('Constructor accepts name parameter');
332
+ },
333
+
334
+ 'constructor should default name to empty string': async ({pass, fail}) => {
335
+ const icon = new Icon();
336
+
337
+ if(icon.name !== ''){
338
+ fail(`Expected name "", got "${icon.name}"`);
339
+ return;
340
+ }
341
+
342
+ pass('Constructor defaults name to empty string');
343
+ },
344
+
345
+ /*
346
+ Styling Tests
347
+ */
348
+ 'should have base CSS applied': async ({pass, fail}) => {
349
+ const { container, icon } = await createIcon();
350
+
351
+ // The component uses ShadowComponent which includes kempo.css
352
+ cleanup(container);
353
+ pass('Component has styling');
354
+ }
355
+ };