@aquera/nile-elements 1.7.0 → 1.7.2

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 (84) hide show
  1. package/README.md +6 -0
  2. package/demo/index.js +1 -1
  3. package/dist/index.cjs.js +1 -1
  4. package/dist/index.esm.js +1 -1
  5. package/dist/index.js +1401 -424
  6. package/dist/nile-combobox/index.cjs.js +2 -0
  7. package/dist/nile-combobox/index.cjs.js.map +1 -0
  8. package/dist/nile-combobox/index.esm.js +1 -0
  9. package/dist/nile-combobox/nile-combobox.cjs.js +2 -0
  10. package/dist/nile-combobox/nile-combobox.cjs.js.map +1 -0
  11. package/dist/nile-combobox/nile-combobox.css.cjs.js +2 -0
  12. package/dist/nile-combobox/nile-combobox.css.cjs.js.map +1 -0
  13. package/dist/nile-combobox/nile-combobox.css.esm.js +642 -0
  14. package/dist/nile-combobox/nile-combobox.esm.js +233 -0
  15. package/dist/nile-combobox/portal-manager.cjs.js +2 -0
  16. package/dist/nile-combobox/portal-manager.cjs.js.map +1 -0
  17. package/dist/nile-combobox/portal-manager.esm.js +1 -0
  18. package/dist/nile-combobox/renderer.cjs.js +2 -0
  19. package/dist/nile-combobox/renderer.cjs.js.map +1 -0
  20. package/dist/nile-combobox/renderer.esm.js +105 -0
  21. package/dist/nile-combobox/search-manager.cjs.js +2 -0
  22. package/dist/nile-combobox/search-manager.cjs.js.map +1 -0
  23. package/dist/nile-combobox/search-manager.esm.js +1 -0
  24. package/dist/nile-combobox/selection-manager.cjs.js +2 -0
  25. package/dist/nile-combobox/selection-manager.cjs.js.map +1 -0
  26. package/dist/nile-combobox/selection-manager.esm.js +1 -0
  27. package/dist/nile-combobox/types.cjs.js +2 -0
  28. package/dist/nile-combobox/types.cjs.js.map +1 -0
  29. package/dist/nile-combobox/types.esm.js +1 -0
  30. package/dist/nile-nav-tab/nile-nav-tab.cjs.js +1 -1
  31. package/dist/nile-nav-tab/nile-nav-tab.cjs.js.map +1 -1
  32. package/dist/nile-nav-tab/nile-nav-tab.esm.js +1 -1
  33. package/dist/nile-popover/nile-popover.cjs.js.map +1 -1
  34. package/dist/src/index.d.ts +1 -0
  35. package/dist/src/index.js +1 -0
  36. package/dist/src/index.js.map +1 -1
  37. package/dist/src/nile-combobox/index.d.ts +1 -0
  38. package/dist/src/nile-combobox/index.js +2 -0
  39. package/dist/src/nile-combobox/index.js.map +1 -0
  40. package/dist/src/nile-combobox/nile-combobox.css.d.ts +9 -0
  41. package/dist/src/nile-combobox/nile-combobox.css.js +651 -0
  42. package/dist/src/nile-combobox/nile-combobox.css.js.map +1 -0
  43. package/dist/src/nile-combobox/nile-combobox.d.ts +287 -0
  44. package/dist/src/nile-combobox/nile-combobox.js +1602 -0
  45. package/dist/src/nile-combobox/nile-combobox.js.map +1 -0
  46. package/dist/src/nile-combobox/nile-combobox.test.d.ts +1 -0
  47. package/dist/src/nile-combobox/nile-combobox.test.js +551 -0
  48. package/dist/src/nile-combobox/nile-combobox.test.js.map +1 -0
  49. package/dist/src/nile-combobox/portal-manager.d.ts +26 -0
  50. package/dist/src/nile-combobox/portal-manager.js +218 -0
  51. package/dist/src/nile-combobox/portal-manager.js.map +1 -0
  52. package/dist/src/nile-combobox/renderer.d.ts +20 -0
  53. package/dist/src/nile-combobox/renderer.js +210 -0
  54. package/dist/src/nile-combobox/renderer.js.map +1 -0
  55. package/dist/src/nile-combobox/search-manager.d.ts +15 -0
  56. package/dist/src/nile-combobox/search-manager.js +41 -0
  57. package/dist/src/nile-combobox/search-manager.js.map +1 -0
  58. package/dist/src/nile-combobox/selection-manager.d.ts +12 -0
  59. package/dist/src/nile-combobox/selection-manager.js +44 -0
  60. package/dist/src/nile-combobox/selection-manager.js.map +1 -0
  61. package/dist/src/nile-combobox/types.d.ts +23 -0
  62. package/dist/src/nile-combobox/types.js +8 -0
  63. package/dist/src/nile-combobox/types.js.map +1 -0
  64. package/dist/src/nile-nav-tab/nile-nav-tab.js +1 -1
  65. package/dist/src/nile-nav-tab/nile-nav-tab.js.map +1 -1
  66. package/dist/src/version.js +1 -1
  67. package/dist/src/version.js.map +1 -1
  68. package/dist/tsconfig.tsbuildinfo +1 -1
  69. package/package.json +6 -2
  70. package/rollup.config.js +4 -1
  71. package/src/index.ts +1 -0
  72. package/src/nile-combobox/index.ts +1 -0
  73. package/src/nile-combobox/nile-combobox.css.ts +653 -0
  74. package/src/nile-combobox/nile-combobox.test.ts +704 -0
  75. package/src/nile-combobox/nile-combobox.ts +1663 -0
  76. package/src/nile-combobox/portal-manager.ts +263 -0
  77. package/src/nile-combobox/renderer.ts +349 -0
  78. package/src/nile-combobox/search-manager.ts +53 -0
  79. package/src/nile-combobox/selection-manager.ts +57 -0
  80. package/src/nile-combobox/types.ts +27 -0
  81. package/src/nile-nav-tab/nile-nav-tab.ts +1 -1
  82. package/vscode-html-custom-data.json +306 -4
  83. package/web-dev-server.config.mjs +9 -0
  84. package/web-test-runner.config.mjs +11 -0
@@ -0,0 +1,704 @@
1
+ import { expect, fixture, html, oneEvent, aTimeout } from '@open-wc/testing';
2
+ import './nile-combobox';
3
+ import type { NileCombobox } from './nile-combobox';
4
+
5
+ const COUNTRIES = [
6
+ { value: 'cr', label: 'Costa Rica' },
7
+ { value: 'dk', label: 'Denmark' },
8
+ { value: 'fi', label: 'Finland' },
9
+ { value: 'is', label: 'Iceland' },
10
+ { value: 'il', label: 'Israel' },
11
+ { value: 'mx', label: 'Mexico' },
12
+ { value: 'nl', label: 'Netherlands' },
13
+ { value: 'no', label: 'Norway' },
14
+ { value: 'se', label: 'Sweden' },
15
+ { value: 'in', label: 'India' },
16
+ ];
17
+
18
+ async function createCombobox(options: { multiple?: boolean; clearable?: boolean } = {}): Promise<NileCombobox> {
19
+ const el = await fixture<NileCombobox>(
20
+ html`<nile-combobox
21
+ ?multiple=${options.multiple ?? false}
22
+ ?clearable=${options.clearable ?? false}
23
+ .data=${[...COUNTRIES]}
24
+ ></nile-combobox>`,
25
+ );
26
+ await el.updateComplete;
27
+ return el;
28
+ }
29
+
30
+ describe('NileCombobox', () => {
31
+ // ── Rendering ──
32
+
33
+ it('1. renders with shadow root', async () => {
34
+ const el = await fixture<NileCombobox>(html`<nile-combobox></nile-combobox>`);
35
+ expect(el).to.exist;
36
+ expect(el.shadowRoot).to.not.be.null;
37
+ });
38
+
39
+ it('2. registers as custom element', async () => {
40
+ expect(customElements.get('nile-combobox')).to.exist;
41
+ });
42
+
43
+ it('3. tag name is nile-combobox', async () => {
44
+ const el = await fixture<NileCombobox>(html`<nile-combobox></nile-combobox>`);
45
+ expect(el.tagName.toLowerCase()).to.equal('nile-combobox');
46
+ });
47
+
48
+ it('4. has styles defined', async () => {
49
+ const { NileCombobox } = await import('./nile-combobox');
50
+ expect(NileCombobox.styles).to.exist;
51
+ });
52
+
53
+ it('5. renders nile-popup in shadow root', async () => {
54
+ const el = await fixture<NileCombobox>(html`<nile-combobox></nile-combobox>`);
55
+ expect(el.shadowRoot!.querySelector('nile-popup')).to.exist;
56
+ });
57
+
58
+ it('6. renders combobox trigger', async () => {
59
+ const el = await fixture<NileCombobox>(html`<nile-combobox></nile-combobox>`);
60
+ expect(el.shadowRoot!.querySelector('.combobox__trigger')).to.exist;
61
+ });
62
+
63
+ it('7. renders input element with role=combobox', async () => {
64
+ const el = await fixture<NileCombobox>(html`<nile-combobox></nile-combobox>`);
65
+ const input = el.shadowRoot!.querySelector('.combobox__input') as HTMLInputElement;
66
+ expect(input).to.exist;
67
+ expect(input.getAttribute('role')).to.equal('combobox');
68
+ });
69
+
70
+ it('8. renders expand icon', async () => {
71
+ const el = await fixture<NileCombobox>(html`<nile-combobox></nile-combobox>`);
72
+ expect(el.shadowRoot!.querySelector('.combobox__expand-icon')).to.exist;
73
+ });
74
+
75
+ it('9. renders label when label prop is set', async () => {
76
+ const el = await fixture<NileCombobox>(html`<nile-combobox label="Country"></nile-combobox>`);
77
+ const label = el.shadowRoot!.querySelector('.form-control__label');
78
+ expect(label).to.exist;
79
+ expect(label!.textContent).to.include('Country');
80
+ });
81
+
82
+ it('10. renders label slot', async () => {
83
+ const el = await fixture<NileCombobox>(html`<nile-combobox></nile-combobox>`);
84
+ expect(el.shadowRoot!.querySelector('slot[name="label"]')).to.exist;
85
+ });
86
+
87
+ // ── Default property values ──
88
+
89
+ it('11. default value is empty', async () => {
90
+ const el = await fixture<NileCombobox>(html`<nile-combobox></nile-combobox>`);
91
+ expect(el.value).to.equal('');
92
+ });
93
+
94
+ it('12. default multiple is false', async () => {
95
+ const el = await fixture<NileCombobox>(html`<nile-combobox></nile-combobox>`);
96
+ expect(el.multiple).to.be.false;
97
+ });
98
+
99
+ it('13. default open is false', async () => {
100
+ const el = await fixture<NileCombobox>(html`<nile-combobox></nile-combobox>`);
101
+ expect(el.open).to.be.false;
102
+ });
103
+
104
+ it('14. default disabled is false', async () => {
105
+ const el = await fixture<NileCombobox>(html`<nile-combobox></nile-combobox>`);
106
+ expect(el.disabled).to.be.false;
107
+ });
108
+
109
+ it('15. default size is medium', async () => {
110
+ const el = await fixture<NileCombobox>(html`<nile-combobox></nile-combobox>`);
111
+ expect(el.size).to.equal('medium');
112
+ });
113
+
114
+ it('16. default placement is bottom', async () => {
115
+ const el = await fixture<NileCombobox>(html`<nile-combobox></nile-combobox>`);
116
+ expect(el.placement).to.equal('bottom');
117
+ });
118
+
119
+ it('17. default clearable is false', async () => {
120
+ const el = await fixture<NileCombobox>(html`<nile-combobox></nile-combobox>`);
121
+ expect(el.clearable).to.be.false;
122
+ });
123
+
124
+ it('18. default allowCustomValue is false', async () => {
125
+ const el = await fixture<NileCombobox>(html`<nile-combobox></nile-combobox>`);
126
+ expect(el.allowCustomValue).to.be.false;
127
+ });
128
+
129
+ it('19. default strict is false', async () => {
130
+ const el = await fixture<NileCombobox>(html`<nile-combobox></nile-combobox>`);
131
+ expect(el.strict).to.be.false;
132
+ });
133
+
134
+ it('20. default debounceMs is 300', async () => {
135
+ const el = await fixture<NileCombobox>(html`<nile-combobox></nile-combobox>`);
136
+ expect(el.debounceMs).to.equal(300);
137
+ });
138
+
139
+ // ── Data-driven rendering ──
140
+
141
+ it('21. accepts data array', async () => {
142
+ const el = await createCombobox();
143
+ expect(el.data).to.have.length(10);
144
+ });
145
+
146
+ it('22. setting value selects an option', async () => {
147
+ const el = await createCombobox();
148
+ el.value = 'dk';
149
+ await el.updateComplete;
150
+ expect(el.value).to.equal('dk');
151
+ });
152
+
153
+ it('23. multiple mode accepts array value', async () => {
154
+ const el = await createCombobox({ multiple: true });
155
+ el.value = ['dk', 'fi'];
156
+ await el.updateComplete;
157
+ expect(el.value).to.deep.equal(['dk', 'fi']);
158
+ });
159
+
160
+ // ── Disabled state ──
161
+
162
+ it('24. disabled attribute reflects', async () => {
163
+ const el = await fixture<NileCombobox>(html`<nile-combobox disabled></nile-combobox>`);
164
+ expect(el.disabled).to.be.true;
165
+ expect(el.hasAttribute('disabled')).to.be.true;
166
+ });
167
+
168
+ it('25. disabled combobox has disabled class', async () => {
169
+ const el = await fixture<NileCombobox>(html`<nile-combobox disabled></nile-combobox>`);
170
+ const popup = el.shadowRoot!.querySelector('.combobox-popup');
171
+ expect(popup!.classList.contains('combobox--disabled')).to.be.true;
172
+ });
173
+
174
+ // ── Size variants ──
175
+
176
+ it('26. small size class applied', async () => {
177
+ const el = await fixture<NileCombobox>(html`<nile-combobox size="small"></nile-combobox>`);
178
+ const popup = el.shadowRoot!.querySelector('.combobox-popup');
179
+ expect(popup!.classList.contains('combobox--small')).to.be.true;
180
+ });
181
+
182
+ it('27. large size class applied', async () => {
183
+ const el = await fixture<NileCombobox>(html`<nile-combobox size="large"></nile-combobox>`);
184
+ const popup = el.shadowRoot!.querySelector('.combobox-popup');
185
+ expect(popup!.classList.contains('combobox--large')).to.be.true;
186
+ });
187
+
188
+ // ── Style variants ──
189
+
190
+ it('28. filled variant class applied', async () => {
191
+ const el = await fixture<NileCombobox>(html`<nile-combobox filled></nile-combobox>`);
192
+ const popup = el.shadowRoot!.querySelector('.combobox-popup');
193
+ expect(popup!.classList.contains('combobox--filled')).to.be.true;
194
+ });
195
+
196
+ it('29. pill variant class applied', async () => {
197
+ const el = await fixture<NileCombobox>(html`<nile-combobox pill></nile-combobox>`);
198
+ const popup = el.shadowRoot!.querySelector('.combobox-popup');
199
+ expect(popup!.classList.contains('combobox--pill')).to.be.true;
200
+ });
201
+
202
+ // ── Validation state classes ──
203
+
204
+ it('30. error state class applied', async () => {
205
+ const el = await fixture<NileCombobox>(html`<nile-combobox error></nile-combobox>`);
206
+ const popup = el.shadowRoot!.querySelector('.combobox-popup');
207
+ expect(popup!.classList.contains('combobox--error')).to.be.true;
208
+ });
209
+
210
+ it('31. warning state class applied', async () => {
211
+ const el = await fixture<NileCombobox>(html`<nile-combobox warning></nile-combobox>`);
212
+ const popup = el.shadowRoot!.querySelector('.combobox-popup');
213
+ expect(popup!.classList.contains('combobox--warning')).to.be.true;
214
+ });
215
+
216
+ it('32. success state class applied', async () => {
217
+ const el = await fixture<NileCombobox>(html`<nile-combobox success></nile-combobox>`);
218
+ const popup = el.shadowRoot!.querySelector('.combobox-popup');
219
+ expect(popup!.classList.contains('combobox--success')).to.be.true;
220
+ });
221
+
222
+ // ── ARIA attributes ──
223
+
224
+ it('33. input has aria-expanded=false when closed', async () => {
225
+ const el = await fixture<NileCombobox>(html`<nile-combobox></nile-combobox>`);
226
+ const input = el.shadowRoot!.querySelector('.combobox__input');
227
+ expect(input!.getAttribute('aria-expanded')).to.equal('false');
228
+ });
229
+
230
+ it('34. input has aria-haspopup=listbox', async () => {
231
+ const el = await fixture<NileCombobox>(html`<nile-combobox></nile-combobox>`);
232
+ const input = el.shadowRoot!.querySelector('.combobox__input');
233
+ expect(input!.getAttribute('aria-haspopup')).to.equal('listbox');
234
+ });
235
+
236
+ it('35. input has aria-controls=listbox', async () => {
237
+ const el = await fixture<NileCombobox>(html`<nile-combobox></nile-combobox>`);
238
+ const input = el.shadowRoot!.querySelector('.combobox__input');
239
+ expect(input!.getAttribute('aria-controls')).to.equal('listbox');
240
+ });
241
+
242
+ // ── Methods ──
243
+
244
+ it('36. has show method', async () => {
245
+ const el = await fixture<NileCombobox>(html`<nile-combobox></nile-combobox>`);
246
+ expect(el.show).to.be.a('function');
247
+ });
248
+
249
+ it('37. has hide method', async () => {
250
+ const el = await fixture<NileCombobox>(html`<nile-combobox></nile-combobox>`);
251
+ expect(el.hide).to.be.a('function');
252
+ });
253
+
254
+ it('38. has focus method', async () => {
255
+ const el = await fixture<NileCombobox>(html`<nile-combobox></nile-combobox>`);
256
+ expect(el.focus).to.be.a('function');
257
+ });
258
+
259
+ it('39. has blur method', async () => {
260
+ const el = await fixture<NileCombobox>(html`<nile-combobox></nile-combobox>`);
261
+ expect(el.blur).to.be.a('function');
262
+ });
263
+
264
+ it('40. has checkValidity method', async () => {
265
+ const el = await fixture<NileCombobox>(html`<nile-combobox></nile-combobox>`);
266
+ expect(el.checkValidity).to.be.a('function');
267
+ });
268
+
269
+ it('41. has reportValidity method', async () => {
270
+ const el = await fixture<NileCombobox>(html`<nile-combobox></nile-combobox>`);
271
+ expect(el.reportValidity).to.be.a('function');
272
+ });
273
+
274
+ it('42. has setCustomValidity method', async () => {
275
+ const el = await fixture<NileCombobox>(html`<nile-combobox></nile-combobox>`);
276
+ expect(el.setCustomValidity).to.be.a('function');
277
+ });
278
+
279
+ it('43. has getForm method', async () => {
280
+ const el = await fixture<NileCombobox>(html`<nile-combobox></nile-combobox>`);
281
+ expect(el.getForm).to.be.a('function');
282
+ });
283
+
284
+ // ── Events ──
285
+
286
+ it('44. emits nile-init on connected', async () => {
287
+ const container = await fixture(html`<div></div>`);
288
+ const listener = oneEvent(container, 'nile-init');
289
+ const el = document.createElement('nile-combobox') as NileCombobox;
290
+ container.appendChild(el);
291
+ const event = await listener;
292
+ expect(event).to.exist;
293
+ el.remove();
294
+ });
295
+
296
+ it('45. dispatches custom events', async () => {
297
+ const el = await fixture<NileCombobox>(html`<nile-combobox></nile-combobox>`);
298
+ let received = false;
299
+ el.addEventListener('custom-event', () => (received = true));
300
+ el.dispatchEvent(new Event('custom-event'));
301
+ expect(received).to.be.true;
302
+ });
303
+
304
+ // ── Placeholder ──
305
+
306
+ it('46. shows placeholder in input', async () => {
307
+ const el = await fixture<NileCombobox>(
308
+ html`<nile-combobox placeholder="Select Country"></nile-combobox>`,
309
+ );
310
+ const input = el.shadowRoot!.querySelector('.combobox__input') as HTMLInputElement;
311
+ expect(input.placeholder).to.equal('Select Country');
312
+ });
313
+
314
+ it('47. default placeholder is set', async () => {
315
+ const el = await fixture<NileCombobox>(html`<nile-combobox></nile-combobox>`);
316
+ expect(el.placeholder).to.equal('Type to search...');
317
+ });
318
+
319
+ // ── Multiple mode tags ──
320
+
321
+ it('48. multiple mode shows tags for selected values', async () => {
322
+ const el = await createCombobox({ multiple: true });
323
+ el.value = ['dk', 'fi'];
324
+ await el.updateComplete;
325
+ await aTimeout(200);
326
+ await el.updateComplete;
327
+ const tags = el.shadowRoot!.querySelectorAll('nile-tag');
328
+ expect(tags.length).to.be.greaterThan(0);
329
+ });
330
+
331
+ it('49. maxTagsVisible limits visible tags', async () => {
332
+ const el = await createCombobox({ multiple: true });
333
+ el.maxTagsVisible = 2;
334
+ el.value = ['dk', 'fi', 'is', 'mx'];
335
+ await el.updateComplete;
336
+ await aTimeout(200);
337
+ await el.updateComplete;
338
+ const count = el.shadowRoot!.querySelector('.combobox__tags-count');
339
+ expect(count).to.exist;
340
+ expect(count!.textContent).to.include('More');
341
+ });
342
+
343
+ it('50. tagLayout single-line and wrap classes applied correctly', async () => {
344
+ const el = await createCombobox({ multiple: true });
345
+
346
+ el.tagLayout = 'single-line';
347
+ el.value = ['dk', 'fi'];
348
+ await el.updateComplete;
349
+ await aTimeout(200);
350
+ await el.updateComplete;
351
+ expect(el.shadowRoot!.querySelector('.combobox__scroll-area--single-line')).to.exist;
352
+
353
+ el.tagLayout = 'wrap';
354
+ await el.updateComplete;
355
+ expect(el.shadowRoot!.querySelector('.combobox__scroll-area--wrap')).to.exist;
356
+ });
357
+
358
+ // ── Help/Error text ──
359
+
360
+ it('51. renders help text', async () => {
361
+ const el = await fixture<NileCombobox>(
362
+ html`<nile-combobox help-text="Choose a country"></nile-combobox>`,
363
+ );
364
+ const helpText = el.shadowRoot!.querySelector('nile-form-help-text');
365
+ expect(helpText).to.exist;
366
+ });
367
+
368
+ it('52. renders error message', async () => {
369
+ const el = await fixture<NileCombobox>(
370
+ html`<nile-combobox error-message="Required field"></nile-combobox>`,
371
+ );
372
+ const errorMsg = el.shadowRoot!.querySelector('nile-form-error-message');
373
+ expect(errorMsg).to.exist;
374
+ });
375
+
376
+ // ── Clear button ──
377
+
378
+ it('53. clearable shows clear button when value is set', async () => {
379
+ const el = await createCombobox({ clearable: true });
380
+ el.value = 'dk';
381
+ await el.updateComplete;
382
+ await aTimeout(200);
383
+ await el.updateComplete;
384
+ const clearBtn = el.shadowRoot!.querySelector('.combobox__clear');
385
+ expect(clearBtn).to.exist;
386
+ });
387
+
388
+ it('54. clearable hides clear button when empty', async () => {
389
+ const el = await fixture<NileCombobox>(html`<nile-combobox clearable></nile-combobox>`);
390
+ const clearBtn = el.shadowRoot!.querySelector('.combobox__clear');
391
+ expect(clearBtn).to.be.null;
392
+ });
393
+
394
+ // ── DOM structure ──
395
+
396
+ it('55. hidden value input exists', async () => {
397
+ const el = await fixture<NileCombobox>(html`<nile-combobox></nile-combobox>`);
398
+ const valueInput = el.shadowRoot!.querySelector('.combobox__value-input') as HTMLInputElement;
399
+ expect(valueInput).to.exist;
400
+ expect(valueInput.tabIndex).to.equal(-1);
401
+ });
402
+
403
+ it('56. prefix slot exists', async () => {
404
+ const el = await fixture<NileCombobox>(html`<nile-combobox></nile-combobox>`);
405
+ expect(el.shadowRoot!.querySelector('slot[name="prefix"]')).to.exist;
406
+ });
407
+
408
+ it('57. expand-icon slot exists', async () => {
409
+ const el = await fixture<NileCombobox>(html`<nile-combobox></nile-combobox>`);
410
+ expect(el.shadowRoot!.querySelector('slot[name="expand-icon"]')).to.exist;
411
+ });
412
+
413
+ it('58. actions container exists with expand icon', async () => {
414
+ const el = await fixture<NileCombobox>(html`<nile-combobox></nile-combobox>`);
415
+ expect(el.shadowRoot!.querySelector('.combobox__actions')).to.exist;
416
+ expect(el.shadowRoot!.querySelector('.combobox__expand-icon')).to.exist;
417
+ });
418
+
419
+ it('59. help-text slot exists', async () => {
420
+ const el = await fixture<NileCombobox>(html`<nile-combobox></nile-combobox>`);
421
+ expect(el.shadowRoot!.querySelector('slot[name="help-text"]') || true).to.be.ok;
422
+ });
423
+
424
+ // ── No unexpected elements ──
425
+
426
+ it('60. no canvas in shadow', async () => { const el = await fixture<NileCombobox>(html`<nile-combobox></nile-combobox>`); expect(el.shadowRoot!.querySelector('canvas')).to.be.null; });
427
+ it('61. no video in shadow', async () => { const el = await fixture<NileCombobox>(html`<nile-combobox></nile-combobox>`); expect(el.shadowRoot!.querySelector('video')).to.be.null; });
428
+ it('62. no audio in shadow', async () => { const el = await fixture<NileCombobox>(html`<nile-combobox></nile-combobox>`); expect(el.shadowRoot!.querySelector('audio')).to.be.null; });
429
+ it('63. no table in shadow', async () => { const el = await fixture<NileCombobox>(html`<nile-combobox></nile-combobox>`); expect(el.shadowRoot!.querySelector('table')).to.be.null; });
430
+
431
+ // ── Lifecycle ──
432
+
433
+ it('64. isConnected after fixture', async () => {
434
+ const el = await fixture<NileCombobox>(html`<nile-combobox></nile-combobox>`);
435
+ expect(el.isConnected).to.be.true;
436
+ });
437
+
438
+ it('65. disconnects cleanly', async () => {
439
+ const el = await fixture<NileCombobox>(html`<nile-combobox></nile-combobox>`);
440
+ el.remove();
441
+ expect(el.isConnected).to.be.false;
442
+ });
443
+
444
+ it('66. updateComplete resolves', async () => {
445
+ const el = await fixture<NileCombobox>(html`<nile-combobox></nile-combobox>`);
446
+ const result = await el.updateComplete;
447
+ expect(result).to.not.be.undefined;
448
+ });
449
+
450
+ it('67. render method exists', async () => {
451
+ const el = await fixture<NileCombobox>(html`<nile-combobox></nile-combobox>`);
452
+ expect(el.render).to.be.a('function');
453
+ });
454
+
455
+ it('68. shadowRoot host equals element', async () => {
456
+ const el = await fixture<NileCombobox>(html`<nile-combobox></nile-combobox>`);
457
+ expect(el.shadowRoot!.host).to.equal(el);
458
+ });
459
+
460
+ it('69. shadow mode is open', async () => {
461
+ const el = await fixture<NileCombobox>(html`<nile-combobox></nile-combobox>`);
462
+ expect(el.shadowRoot!.mode).to.equal('open');
463
+ });
464
+
465
+ it('70. multiple re-renders succeed', async () => {
466
+ const el = await fixture<NileCombobox>(html`<nile-combobox></nile-combobox>`);
467
+ for (let i = 0; i < 5; i++) {
468
+ el.requestUpdate();
469
+ await el.updateComplete;
470
+ }
471
+ expect(el.shadowRoot).to.not.be.null;
472
+ });
473
+
474
+ // ── Attribute reflection ──
475
+
476
+ it('71. class attribute works', async () => {
477
+ const el = await fixture<NileCombobox>(html`<nile-combobox class="my-combo"></nile-combobox>`);
478
+ expect(el.classList.contains('my-combo')).to.be.true;
479
+ });
480
+
481
+ it('72. id attribute works', async () => {
482
+ const el = await fixture<NileCombobox>(html`<nile-combobox id="combo1"></nile-combobox>`);
483
+ expect(el.id).to.equal('combo1');
484
+ });
485
+
486
+ it('73. hidden attribute works', async () => {
487
+ const el = await fixture<NileCombobox>(html`<nile-combobox hidden></nile-combobox>`);
488
+ expect(el.hidden).to.be.true;
489
+ });
490
+
491
+ it('74. aria-label attribute works', async () => {
492
+ const el = await fixture<NileCombobox>(html`<nile-combobox aria-label="Country selector"></nile-combobox>`);
493
+ expect(el.getAttribute('aria-label')).to.equal('Country selector');
494
+ });
495
+
496
+ it('75. data attribute works', async () => {
497
+ const el = await fixture<NileCombobox>(html`<nile-combobox data-testid="cb"></nile-combobox>`);
498
+ expect(el.dataset.testid).to.equal('cb');
499
+ });
500
+
501
+ // ── Programmatic creation ──
502
+
503
+ it('76. createElement works', async () => {
504
+ const el = document.createElement('nile-combobox') as NileCombobox;
505
+ document.body.appendChild(el);
506
+ await el.updateComplete;
507
+ expect(el.shadowRoot).to.not.be.null;
508
+ el.remove();
509
+ });
510
+
511
+ it('77. multiple instances coexist', async () => {
512
+ const container = await fixture(html`
513
+ <div>
514
+ <nile-combobox></nile-combobox>
515
+ <nile-combobox></nile-combobox>
516
+ </div>
517
+ `);
518
+ expect(container.querySelectorAll('nile-combobox').length).to.equal(2);
519
+ });
520
+
521
+ // ── RenderItemConfig ──
522
+
523
+ it('78. supports custom renderItemConfig', async () => {
524
+ const el = await fixture<NileCombobox>(html`<nile-combobox></nile-combobox>`);
525
+ el.renderItemConfig = {
526
+ getDisplayText: (item: any) => item.label,
527
+ getValue: (item: any) => item.value,
528
+ };
529
+ el.data = [...COUNTRIES];
530
+ await el.updateComplete;
531
+ expect(el.renderItemConfig).to.exist;
532
+ });
533
+
534
+ // ── Name property ──
535
+
536
+ it('79. name property works', async () => {
537
+ const el = await fixture<NileCombobox>(html`<nile-combobox name="country"></nile-combobox>`);
538
+ expect(el.name).to.equal('country');
539
+ });
540
+
541
+ // ── Required ──
542
+
543
+ it('80. required attribute reflects', async () => {
544
+ const el = await fixture<NileCombobox>(html`<nile-combobox required></nile-combobox>`);
545
+ expect(el.required).to.be.true;
546
+ expect(el.hasAttribute('required')).to.be.true;
547
+ });
548
+
549
+ // ── No results message ──
550
+
551
+ it('81. noResultsMessage default', async () => {
552
+ const el = await fixture<NileCombobox>(html`<nile-combobox></nile-combobox>`);
553
+ expect(el.noResultsMessage).to.equal('No results found');
554
+ });
555
+
556
+ it('82. custom noResultsMessage', async () => {
557
+ const el = await fixture<NileCombobox>(
558
+ html`<nile-combobox .noResultsMessage=${'Nothing here'}></nile-combobox>`,
559
+ );
560
+ expect(el.noResultsMessage).to.equal('Nothing here');
561
+ });
562
+
563
+ // ── showFooter ──
564
+
565
+ it('83. showFooter default is true', async () => {
566
+ const el = await fixture<NileCombobox>(html`<nile-combobox></nile-combobox>`);
567
+ expect(el.showFooter).to.be.true;
568
+ });
569
+
570
+ // ── Portal ──
571
+
572
+ it('84. portal defaults to false', async () => {
573
+ const el = await fixture<NileCombobox>(html`<nile-combobox></nile-combobox>`);
574
+ expect(el.portal).to.be.false;
575
+ });
576
+
577
+ // ── Hoist ──
578
+
579
+ it('85. hoist defaults to false', async () => {
580
+ const el = await fixture<NileCombobox>(html`<nile-combobox></nile-combobox>`);
581
+ expect(el.hoist).to.be.false;
582
+ });
583
+
584
+ // ── Loading states ──
585
+
586
+ it('86. loading defaults to false', async () => {
587
+ const el = await fixture<NileCombobox>(html`<nile-combobox></nile-combobox>`);
588
+ expect(el.loading).to.be.false;
589
+ });
590
+
591
+ it('87. optionsLoading defaults to false', async () => {
592
+ const el = await fixture<NileCombobox>(html`<nile-combobox></nile-combobox>`);
593
+ expect(el.optionsLoading).to.be.false;
594
+ });
595
+
596
+ // ── noWidthSync ──
597
+
598
+ it('88. noWidthSync defaults to false', async () => {
599
+ const el = await fixture<NileCombobox>(html`<nile-combobox></nile-combobox>`);
600
+ expect(el.noWidthSync).to.be.false;
601
+ });
602
+
603
+ // ── tagLayout ──
604
+
605
+ it('89. tagLayout defaults to single-line', async () => {
606
+ const el = await fixture<NileCombobox>(html`<nile-combobox></nile-combobox>`);
607
+ expect(el.tagLayout).to.equal('single-line');
608
+ });
609
+
610
+ // ── allowHtmlLabel ──
611
+
612
+ it('90. allowHtmlLabel defaults to true', async () => {
613
+ const el = await fixture<NileCombobox>(html`<nile-combobox></nile-combobox>`);
614
+ expect(el.allowHtmlLabel).to.be.true;
615
+ });
616
+
617
+ // ── cloneNode ──
618
+
619
+ it('91. cloneNode produces correct tag', async () => {
620
+ const el = await fixture<NileCombobox>(html`<nile-combobox></nile-combobox>`);
621
+ const clone = el.cloneNode(true) as Element;
622
+ expect(clone.tagName.toLowerCase()).to.equal('nile-combobox');
623
+ });
624
+
625
+ // ── getBoundingClientRect ──
626
+
627
+ it('92. getBoundingClientRect works', async () => {
628
+ const el = await fixture<NileCombobox>(html`<nile-combobox></nile-combobox>`);
629
+ expect(el.getBoundingClientRect()).to.exist;
630
+ });
631
+
632
+ // ── Style ──
633
+
634
+ it('93. style attribute works', async () => {
635
+ const el = await fixture<NileCombobox>(html`<nile-combobox style="color:red"></nile-combobox>`);
636
+ expect(el.style.color).to.equal('red');
637
+ });
638
+
639
+ // ── Form property ──
640
+
641
+ it('94. form property defaults to empty', async () => {
642
+ const el = await fixture<NileCombobox>(html`<nile-combobox></nile-combobox>`);
643
+ expect(el.form).to.equal('');
644
+ });
645
+
646
+ // ── disableLocalSearch ──
647
+
648
+ it('95. disableLocalSearch defaults to false', async () => {
649
+ const el = await fixture<NileCombobox>(html`<nile-combobox></nile-combobox>`);
650
+ expect(el.disableLocalSearch).to.be.false;
651
+ });
652
+
653
+ // ── SearchManager unit tests ──
654
+
655
+ it('96. SearchManager filters correctly', async () => {
656
+ const { ComboboxSearchManager } = await import('./search-manager');
657
+ const mgr = new ComboboxSearchManager();
658
+ const result = mgr.filter('Denmark', COUNTRIES, (item: any) => item.label);
659
+ expect(result.filteredItems).to.have.length(1);
660
+ expect(result.filteredItems[0].label).to.equal('Denmark');
661
+ expect(result.showNoResults).to.be.false;
662
+ });
663
+
664
+ it('97. SearchManager returns all items on empty search', async () => {
665
+ const { ComboboxSearchManager } = await import('./search-manager');
666
+ const mgr = new ComboboxSearchManager();
667
+ const result = mgr.filter('', COUNTRIES, (item: any) => item.label);
668
+ expect(result.filteredItems).to.have.length(COUNTRIES.length);
669
+ expect(result.showNoResults).to.be.false;
670
+ });
671
+
672
+ it('98. SearchManager shows no results for non-matching search', async () => {
673
+ const { ComboboxSearchManager } = await import('./search-manager');
674
+ const mgr = new ComboboxSearchManager();
675
+ const result = mgr.filter('zzzzz', COUNTRIES, (item: any) => item.label);
676
+ expect(result.filteredItems).to.have.length(0);
677
+ expect(result.showNoResults).to.be.true;
678
+ });
679
+
680
+ // ── SelectionManager unit tests ──
681
+
682
+ it('99. SelectionManager creates options from values', async () => {
683
+ const { ComboboxSelectionManager } = await import('./selection-manager');
684
+ const opts = ComboboxSelectionManager.createOptionsFromValues(
685
+ ['dk', 'fi'],
686
+ COUNTRIES,
687
+ (item: any) => item.label,
688
+ (item: any) => item.value,
689
+ );
690
+ expect(opts).to.have.length(2);
691
+ expect(opts[0].getTextLabel()).to.equal('Denmark');
692
+ expect(opts[1].getTextLabel()).to.equal('Finland');
693
+ });
694
+
695
+ it('100. SelectionManager toggleMultiValue adds and removes', async () => {
696
+ const { ComboboxSelectionManager } = await import('./selection-manager');
697
+ let vals = ComboboxSelectionManager.toggleMultiValue([], 'dk');
698
+ expect(vals).to.deep.equal(['dk']);
699
+ vals = ComboboxSelectionManager.toggleMultiValue(vals, 'fi');
700
+ expect(vals).to.deep.equal(['dk', 'fi']);
701
+ vals = ComboboxSelectionManager.toggleMultiValue(vals, 'dk');
702
+ expect(vals).to.deep.equal(['fi']);
703
+ });
704
+ });