@adia-ai/web-components 0.8.19 → 0.8.21

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 (128) hide show
  1. package/CHANGELOG.md +26 -0
  2. package/components/card/card.a2ui.json +6 -3
  3. package/components/card/card.css +15 -1
  4. package/components/card/card.yaml +5 -3
  5. package/components/nav-group/nav-group.a2ui.json +5 -0
  6. package/components/nav-group/nav-group.class.js +14 -6
  7. package/components/nav-group/nav-group.css +5 -2
  8. package/components/nav-group/nav-group.d.ts +2 -0
  9. package/components/nav-group/nav-group.yaml +4 -0
  10. package/components/select/select.a2ui.json +3 -2
  11. package/components/select/select.class.js +14 -1
  12. package/components/select/select.css +13 -1
  13. package/components/select/select.yaml +5 -4
  14. package/components/table/table.class.js +7 -3
  15. package/components/table/table.css +7 -5
  16. package/dist/theme-provider.min.js +1 -1
  17. package/dist/web-components.min.css +1 -1
  18. package/dist/web-components.min.js +19 -19
  19. package/dist/web-components.sheet.js +1 -1
  20. package/package.json +2 -1
  21. package/components/adia-mark/adia-mark.test.js +0 -69
  22. package/components/adia-wordmark/adia-wordmark.test.js +0 -69
  23. package/components/agent-artifact/agent-artifact.test.js +0 -61
  24. package/components/alert/alert.test.js +0 -364
  25. package/components/color-picker/color-picker.test.js +0 -96
  26. package/components/combobox/combobox.test.js +0 -256
  27. package/components/command/command.test.js +0 -99
  28. package/components/date-range-picker/date-range-picker.test.js +0 -387
  29. package/components/datetime-picker/datetime-picker.test.js +0 -454
  30. package/components/demo-toggle/demo-toggle.test.js +0 -102
  31. package/components/description-list/description-list.test.js +0 -72
  32. package/components/display-field/display-field.test.js +0 -115
  33. package/components/empty-state/empty-state.test.js +0 -77
  34. package/components/field/field.test.js +0 -260
  35. package/components/fields/fields.test.js +0 -88
  36. package/components/inline-message/inline-message.test.js +0 -203
  37. package/components/input/input.test.js +0 -335
  38. package/components/integration-card/integration-card.test.js +0 -318
  39. package/components/link/link.test.js +0 -143
  40. package/components/list/list.test.js +0 -106
  41. package/components/list-window/list-window.test.js +0 -303
  42. package/components/loading-overlay/loading-overlay.test.js +0 -257
  43. package/components/menu/menu.test.js +0 -176
  44. package/components/nav/nav.test.js +0 -428
  45. package/components/nav-group/nav-group.test.js +0 -67
  46. package/components/option-card/option-card.test.js +0 -32
  47. package/components/pagination/pagination.test.js +0 -195
  48. package/components/select/select.test.js +0 -481
  49. package/components/slider/slider.test.js +0 -202
  50. package/components/spinner/spinner.test.js +0 -272
  51. package/components/stat/stat.test.js +0 -108
  52. package/components/step-progress/step-progress.test.js +0 -129
  53. package/components/swatch/swatch.test.js +0 -563
  54. package/components/table/table.test.js +0 -479
  55. package/components/table-toolbar/table-toolbar.test.js +0 -152
  56. package/components/tag/tag.test.js +0 -137
  57. package/components/tags-input/tags-input.test.js +0 -477
  58. package/components/text/text.test.js +0 -202
  59. package/components/theme-provider/theme-provider.test.js +0 -94
  60. package/components/time-picker/time-picker.test.js +0 -287
  61. package/components/timeline/timeline.test.js +0 -31
  62. package/components/toggle-scheme/toggle-scheme.test.js +0 -110
  63. package/components/tree/tree.test.js +0 -312
  64. package/core/element.test.js +0 -389
  65. package/core/icons.test.js +0 -187
  66. package/core/logical-children.test.js +0 -54
  67. package/core/markdown.test.js +0 -47
  68. package/core/responsive.test.js +0 -121
  69. package/core/template.test.js +0 -611
  70. package/traits/active-state/active-state.test.js +0 -28
  71. package/traits/anchor-positioning/anchor-positioning.test.js +0 -122
  72. package/traits/announcer/announcer.test.js +0 -268
  73. package/traits/arrow-grid-nav/arrow-grid-nav.test.js +0 -375
  74. package/traits/attention-pulse/attention-pulse.test.js +0 -26
  75. package/traits/confetti/confetti.test.js +0 -46
  76. package/traits/confetti-burst/confetti-burst.test.js +0 -46
  77. package/traits/count-up/count-up.test.js +0 -54
  78. package/traits/declarative.test.js +0 -138
  79. package/traits/dirty-state/dirty-state.test.js +0 -45
  80. package/traits/drag-ghost/drag-ghost.test.js +0 -19
  81. package/traits/draggable/draggable.test.js +0 -60
  82. package/traits/draggable-list-item/draggable-list-item.test.js +0 -51
  83. package/traits/drop-target/drop-target.test.js +0 -241
  84. package/traits/droppable/droppable.test.js +0 -54
  85. package/traits/droppable-collection/droppable-collection.test.js +0 -99
  86. package/traits/error-shake/error-shake.test.js +0 -114
  87. package/traits/fade-presence/fade-presence.test.js +0 -20
  88. package/traits/focus-restore/focus-restore.test.js +0 -202
  89. package/traits/focus-trap/focus-trap.test.js +0 -42
  90. package/traits/focusable/focusable.test.js +0 -53
  91. package/traits/glow-focus/glow-focus.test.js +0 -31
  92. package/traits/gradient-shift/gradient-shift.test.js +0 -22
  93. package/traits/haptic-feedback/haptic-feedback.test.js +0 -52
  94. package/traits/hotkey/hotkey.test.js +0 -61
  95. package/traits/hoverable/hoverable.test.js +0 -24
  96. package/traits/inertia-drag/inertia-drag.test.js +0 -33
  97. package/traits/input-mask/input-mask.test.js +0 -151
  98. package/traits/intersection-observer/intersection-observer.test.js +0 -38
  99. package/traits/keyboard-nav/keyboard-nav.test.js +0 -41
  100. package/traits/keyboard-reorderable/keyboard-reorderable.test.js +0 -45
  101. package/traits/layout-animation/layout-animation.test.js +0 -114
  102. package/traits/long-press/long-press.test.js +0 -244
  103. package/traits/magnetic-hover/magnetic-hover.test.js +0 -30
  104. package/traits/noise-texture/noise-texture.test.js +0 -20
  105. package/traits/parallax/parallax.test.js +0 -26
  106. package/traits/portal/portal.test.js +0 -30
  107. package/traits/pressable/pressable.test.js +0 -73
  108. package/traits/resettable/resettable.test.js +0 -100
  109. package/traits/resizable/resizable.test.js +0 -39
  110. package/traits/resize-observer/resize-observer.test.js +0 -38
  111. package/traits/ripple/ripple.test.js +0 -32
  112. package/traits/roving-tabindex/roving-tabindex.test.js +0 -28
  113. package/traits/scale-press/scale-press.test.js +0 -39
  114. package/traits/scroll-lock/scroll-lock.test.js +0 -45
  115. package/traits/scroll-progress/scroll-progress.test.js +0 -182
  116. package/traits/shimmer-loading/shimmer-loading.test.js +0 -43
  117. package/traits/smoke.test.js +0 -111
  118. package/traits/snap-to-grid/snap-to-grid.test.js +0 -40
  119. package/traits/sound-feedback/sound-feedback.test.js +0 -26
  120. package/traits/spring-animate/spring-animate.test.js +0 -28
  121. package/traits/success-checkmark/success-checkmark.test.js +0 -120
  122. package/traits/tilt-hover/tilt-hover.test.js +0 -32
  123. package/traits/tossable/tossable.test.js +0 -31
  124. package/traits/traits-host.test.js +0 -73
  125. package/traits/typeahead/typeahead.test.js +0 -38
  126. package/traits/typewriter/typewriter.test.js +0 -47
  127. package/traits/validation/validation.test.js +0 -93
  128. package/traits/view-transition/view-transition.test.js +0 -268
@@ -1,389 +0,0 @@
1
- import { describe, it, expect, beforeEach } from 'vitest';
2
- import { UIElement, signal, effect } from './element.js';
3
-
4
- // ── Shared helpers ──
5
-
6
- let registrationCounter = 0;
7
- function registerTestElement(BaseImpl) {
8
- const tag = `test-el-${++registrationCounter}`;
9
- customElements.define(tag, BaseImpl);
10
- return tag;
11
- }
12
-
13
- function mount(tag, attrs = {}) {
14
- const el = document.createElement(tag);
15
- for (const [k, v] of Object.entries(attrs)) el.setAttribute(k, v);
16
- document.body.appendChild(el);
17
- return el;
18
- }
19
-
20
- // Signals flush via queueMicrotask — await one tick to let reactive
21
- // effects settle after a mutation.
22
- const tick = () => new Promise((r) => queueMicrotask(r));
23
-
24
- describe('UIElement — construction + lifecycle', () => {
25
- beforeEach(() => { document.body.innerHTML = ''; });
26
-
27
- it('instantiates and calls connected() on mount, disconnected() on remove', () => {
28
- const events = [];
29
- class El extends UIElement {
30
- connected() { events.push('connected'); }
31
- disconnected() { events.push('disconnected'); }
32
- }
33
- const tag = registerTestElement(El);
34
- const el = mount(tag);
35
- expect(events).toEqual(['connected']);
36
- el.remove();
37
- expect(events).toEqual(['connected', 'disconnected']);
38
- });
39
-
40
- it('remount after disconnect re-runs connected()', () => {
41
- const events = [];
42
- class El extends UIElement {
43
- connected() { events.push('connected'); }
44
- disconnected() { events.push('disconnected'); }
45
- }
46
- const tag = registerTestElement(El);
47
- const el = mount(tag);
48
- el.remove();
49
- document.body.appendChild(el);
50
- expect(events).toEqual(['connected', 'disconnected', 'connected']);
51
- });
52
- });
53
-
54
- describe('UIElement — properties', () => {
55
- beforeEach(() => { document.body.innerHTML = ''; });
56
-
57
- it('reads default value from static properties', () => {
58
- class El extends UIElement {
59
- static properties = { label: { type: String, default: 'hi' } };
60
- }
61
- const tag = registerTestElement(El);
62
- const el = mount(tag);
63
- expect(el.label).toBe('hi');
64
- });
65
-
66
- // FEEDBACK-99 (color-app, 2026-06-07): a property assigned on the instance
67
- // BEFORE upgrade — template engines set `.prop=${v}` during stamp, possibly
68
- // before the element is defined (see template.js property-binding path) —
69
- // must survive upgrade, not be clobbered by the signal default. WHATWG
70
- // "upgrade a property" pattern, here in installProps. Fixes input-ui `.value`.
71
- it('reifies a property set on the instance before upgrade (lazy upgrade)', () => {
72
- const tag = `test-el-${++registrationCounter}`;
73
- const el = document.createElement(tag); // undefined tag → un-upgraded
74
- document.body.appendChild(el);
75
- el.value = 'pre-upgrade'; // own data-property, no accessor yet
76
- class El extends UIElement {
77
- static properties = { value: { type: String, default: '' } };
78
- }
79
- customElements.define(tag, El); // upgrade → installProps runs
80
- expect(el.value).toBe('pre-upgrade'); // reified, not clobbered to ''
81
- });
82
-
83
- it('reflects Boolean properties to HTML attributes when reflect: true', () => {
84
- class El extends UIElement {
85
- static properties = { disabled: { type: Boolean, default: false, reflect: true } };
86
- }
87
- const tag = registerTestElement(El);
88
- const el = mount(tag);
89
- expect(el.hasAttribute('disabled')).toBe(false);
90
- el.disabled = true;
91
- expect(el.hasAttribute('disabled')).toBe(true);
92
- el.disabled = false;
93
- expect(el.hasAttribute('disabled')).toBe(false);
94
- });
95
-
96
- it('reflects String properties as attribute values', () => {
97
- class El extends UIElement {
98
- static properties = { variant: { type: String, default: 'neutral', reflect: true } };
99
- }
100
- const tag = registerTestElement(El);
101
- const el = mount(tag);
102
- el.variant = 'accent';
103
- expect(el.getAttribute('variant')).toBe('accent');
104
- });
105
-
106
- it('parses incoming attributes back into typed properties', () => {
107
- class El extends UIElement {
108
- static properties = {
109
- count: { type: Number, default: 0 },
110
- active: { type: Boolean, default: false },
111
- };
112
- }
113
- const tag = registerTestElement(El);
114
- const el = mount(tag, { count: '42', active: '' });
115
- expect(el.count).toBe(42);
116
- expect(el.active).toBe(true);
117
- });
118
-
119
- it('does NOT reflect when reflect: false / omitted', () => {
120
- class El extends UIElement {
121
- static properties = { internal: { type: String, default: 'x' } };
122
- }
123
- const tag = registerTestElement(El);
124
- const el = mount(tag);
125
- el.internal = 'y';
126
- expect(el.hasAttribute('internal')).toBe(false);
127
- });
128
- });
129
-
130
- describe('UIElement — reactive effect', () => {
131
- beforeEach(() => { document.body.innerHTML = ''; });
132
-
133
- it('calls render() on mount and when a reactive property changes', async () => {
134
- let renders = 0;
135
- class El extends UIElement {
136
- static properties = { label: { type: String, default: 'a' } };
137
- render() { renders++; void this.label; }
138
- }
139
- const tag = registerTestElement(El);
140
- const el = mount(tag);
141
- expect(renders).toBeGreaterThan(0);
142
- const before = renders;
143
- el.label = 'b';
144
- await tick();
145
- expect(renders).toBeGreaterThan(before);
146
- });
147
-
148
- it('stops re-rendering after disconnect', async () => {
149
- let renders = 0;
150
- class El extends UIElement {
151
- static properties = { label: { type: String, default: 'a' } };
152
- render() { renders++; void this.label; }
153
- }
154
- const tag = registerTestElement(El);
155
- const el = mount(tag);
156
- el.remove();
157
- const frozen = renders;
158
- el.label = 'c';
159
- await tick();
160
- expect(renders).toBe(frozen);
161
- });
162
- });
163
-
164
- describe('UIElement — subscription-leak guard (0.0.10 regression)', () => {
165
- beforeEach(() => { document.body.innerHTML = ''; });
166
-
167
- it('child connected() reads do NOT subscribe an outer effect', async () => {
168
- // The bug: when a parent effect called appendChild(child) while running,
169
- // child.connectedCallback would synchronously call connected(), and any
170
- // template-literal reads of child's reactive properties would subscribe
171
- // the OUTER effect to the CHILD's signals. A later change to a child
172
- // prop would spuriously re-run the parent effect.
173
- //
174
- // Fix: connected() is now wrapped in untracked(…).
175
- class Child extends UIElement {
176
- static properties = { value: { type: String, default: 'initial', reflect: true } };
177
- connected() {
178
- // Read our own reactive prop exactly the way template-literal
179
- // interpolation would — this read must not subscribe an ancestor
180
- // effect.
181
- void this.value;
182
- }
183
- }
184
- const tag = registerTestElement(Child);
185
-
186
- const trigger = signal(0);
187
- let outerRuns = 0;
188
- let childRef = null;
189
-
190
- const dispose = effect(() => {
191
- outerRuns++;
192
- void trigger.value;
193
- if (!childRef) {
194
- childRef = document.createElement(tag);
195
- document.body.appendChild(childRef);
196
- }
197
- });
198
-
199
- const outerRunsAfterMount = outerRuns;
200
- expect(childRef).not.toBeNull();
201
-
202
- // Now mutate the CHILD's signal — the outer effect MUST NOT re-run.
203
- childRef.value = 'changed';
204
- await tick();
205
- expect(outerRuns).toBe(outerRunsAfterMount);
206
-
207
- // Sanity check: mutating the trigger signal the outer effect DID
208
- // subscribe to re-runs the effect, proving the effect is alive.
209
- trigger.value = 1;
210
- await tick();
211
- expect(outerRuns).toBe(outerRunsAfterMount + 1);
212
-
213
- dispose();
214
- });
215
- });
216
-
217
- describe('UIElement — addTrait', () => {
218
- beforeEach(() => { document.body.innerHTML = ''; });
219
-
220
- it('applies a trait once, idempotent on repeat', () => {
221
- let connects = 0;
222
- const myTrait = () => ({
223
- connect() { connects++; },
224
- disconnect() {},
225
- });
226
-
227
- class El extends UIElement {}
228
- const tag = registerTestElement(El);
229
- const el = mount(tag);
230
- el.addTrait(myTrait);
231
- el.addTrait(myTrait);
232
- expect(connects).toBe(1);
233
- });
234
-
235
- it('disconnects all applied traits on remove', () => {
236
- let connects = 0, disconnects = 0;
237
- const myTrait = () => ({
238
- connect() { connects++; },
239
- disconnect() { disconnects++; },
240
- });
241
- class El extends UIElement {
242
- static traits = [myTrait];
243
- }
244
- const tag = registerTestElement(El);
245
- const el = mount(tag);
246
- expect(connects).toBe(1);
247
- expect(disconnects).toBe(0);
248
- el.remove();
249
- expect(disconnects).toBe(1);
250
- });
251
- });
252
-
253
- // gh#285 — SSR DOM shims (linkedom et al.) implement no ElementInternals
254
- // and no document.adoptedStyleSheets. These tests simulate that absence
255
- // directly (deleting the real APIs) rather than trusting a mock — the
256
- // bug was an unconditional call crashing the CONSTRUCTOR, before any
257
- // subclass code ran, so the only real proof is exercising construction
258
- // with the API genuinely gone.
259
- describe('UIElement — SSR browser-API absence (gh#285)', () => {
260
- beforeEach(() => { document.body.innerHTML = ''; });
261
-
262
- it('constructs without throwing when attachInternals does not exist', () => {
263
- const original = HTMLElement.prototype.attachInternals;
264
- delete HTMLElement.prototype.attachInternals;
265
- try {
266
- class El extends UIElement {}
267
- const tag = registerTestElement(El);
268
- expect(() => mount(tag)).not.toThrow();
269
- } finally {
270
- HTMLElement.prototype.attachInternals = original;
271
- }
272
- });
273
-
274
- it('the no-op internals shim answers every call site the framework actually makes', () => {
275
- const original = HTMLElement.prototype.attachInternals;
276
- delete HTMLElement.prototype.attachInternals;
277
- let el;
278
- try {
279
- class El extends UIElement {}
280
- const tag = registerTestElement(El);
281
- el = mount(tag);
282
- } finally {
283
- HTMLElement.prototype.attachInternals = original;
284
- }
285
- expect(() => el.internals.setFormValue('x')).not.toThrow();
286
- expect(() => el.internals.setValidity({})).not.toThrow();
287
- expect(el.internals.checkValidity()).toBe(true);
288
- expect(el.internals.reportValidity()).toBe(true);
289
- expect(el.internals.form).toBeNull();
290
- expect(el.internals.labels).toEqual([]);
291
- expect(el.internals.validity).toEqual({});
292
- expect(el.internals.validationMessage).toBe('');
293
- expect(el.internals.willValidate).toBe(false);
294
- });
295
-
296
- it('a real attachInternals is still used when the browser provides one (no regression)', () => {
297
- class El extends UIElement {}
298
- const tag = registerTestElement(El);
299
- const el = mount(tag);
300
- // happy-dom's real attachInternals — not the shim.
301
- expect(el.internals).not.toBe(null);
302
- expect(typeof el.internals.setFormValue).toBe('function');
303
- });
304
-
305
- it('adoptStyles no-ops instead of throwing when adoptedStyleSheets does not exist', () => {
306
- const desc = Object.getOwnPropertyDescriptor(Document.prototype, 'adoptedStyleSheets');
307
- delete Document.prototype.adoptedStyleSheets;
308
- try {
309
- class El extends UIElement {
310
- static styles = ['/* fake sheet */'];
311
- }
312
- const tag = registerTestElement(El);
313
- expect(() => mount(tag)).not.toThrow();
314
- } finally {
315
- if (desc) Object.defineProperty(Document.prototype, 'adoptedStyleSheets', desc);
316
- }
317
- });
318
- });
319
-
320
- // gh#284 — the custom-elements spec's "upgrade an element" algorithm
321
- // (§4.13.5 step 6) requires replaying attributeChangedCallback for every
322
- // attribute already present on an element BEFORE connectedCallback fires
323
- // on upgrade. happy-dom (this repo's own test DOM) skips that replay —
324
- // confirmed directly: a bare custom element upgraded with a pre-existing
325
- // observed attribute gets connectedCallback but never the matching
326
- // attributeChangedCallback. linkedom (the real SSR consumer's DOM shim,
327
- // per adia-ssr) is a similarly from-scratch custom-elements registry, so
328
- // the same gap is expected there too. Simulating the absence directly
329
- // (a real late-upgrade sequence, not a mock) rather than trusting that
330
- // happy-dom already reproduces the SSR shape — it does, which is exactly
331
- // how this was found.
332
- describe('UIElement — SSR attribute-upgrade replay (gh#284)', () => {
333
- beforeEach(() => { document.body.innerHTML = ''; });
334
-
335
- // Parses markup into an UNDEFINED custom element (so its attributes land
336
- // on the element the way a server-rendered / pre-parsed document would),
337
- // THEN defines the tag — matching the "define after parse" shape SSR
338
- // hydration and lazy client registration both produce.
339
- function mountLateUpgrade(tag, BaseImpl, html) {
340
- const container = document.createElement('div');
341
- container.innerHTML = html;
342
- document.body.appendChild(container);
343
- customElements.define(tag, BaseImpl);
344
- return container.querySelector(tag);
345
- }
346
-
347
- it('confirms the environment gap this fix compensates for', () => {
348
- const tag = `test-plain-${++registrationCounter}`;
349
- const calls = [];
350
- class Plain extends HTMLElement {
351
- static get observedAttributes() { return ['foo']; }
352
- attributeChangedCallback(name, oldV, newV) { calls.push([name, oldV, newV]); }
353
- connectedCallback() { calls.push(['connected']); }
354
- }
355
- mountLateUpgrade(tag, Plain, `<${tag} foo="bar"></${tag}>`);
356
- expect(calls).toEqual([['connected']]); // NOT ['foo', null, 'bar'] first, per spec
357
- });
358
-
359
- it('a reflected property set only via pre-parsed HTML is not stuck at its class default', () => {
360
- const tag = `test-late-${++registrationCounter}`;
361
- class El extends UIElement {
362
- static properties = { label: { type: String, default: '', reflect: true } };
363
- }
364
- const el = mountLateUpgrade(tag, El, `<${tag} label="Profile"></${tag}>`);
365
- expect(el.label).toBe('Profile');
366
- });
367
-
368
- it('a reflected boolean attribute present-with-no-value resolves true, not the class default', () => {
369
- const tag = `test-late-bool-${++registrationCounter}`;
370
- class El extends UIElement {
371
- static properties = { active: { type: Boolean, default: false, reflect: true } };
372
- }
373
- const el = mountLateUpgrade(tag, El, `<${tag} active></${tag}>`);
374
- expect(el.active).toBe(true);
375
- });
376
-
377
- it('no regression: normal define-before-parse still gets attributeChangedCallback (no extra render)', async () => {
378
- let renders = 0;
379
- class El extends UIElement {
380
- static properties = { label: { type: String, default: '', reflect: true } };
381
- render() { renders++; }
382
- }
383
- const tag = registerTestElement(El);
384
- const el = mount(tag, { label: 'Profile' });
385
- await tick();
386
- expect(el.label).toBe('Profile');
387
- expect(renders).toBe(1); // the connectedCallback re-sync is a no-op here, not a second render
388
- });
389
- });
@@ -1,187 +0,0 @@
1
- import { describe, it, expect, beforeEach } from 'vitest';
2
- import {
3
- installIconLoaders,
4
- registerIcon,
5
- hasIcon,
6
- isIconName,
7
- getIcon,
8
- listIcons,
9
- } from './icons.js';
10
-
11
- // Tests for `installIconLoaders` + `resolveLoader` + the §140 suffix-
12
- // fallback fix for FEEDBACK-06 (color-app's `installIconLoaders` →
13
- // `warnMissingIcon` failures on `caret-right` / `x` / `caret-up-down`
14
- // when the consumer's `modules` keys didn't match the canonical
15
- // `/node_modules/@phosphor-icons/core/assets/<weight>/<name>.svg` path.
16
- //
17
- // `isIconName` is the SYNC probe — it calls `resolveLoader` without
18
- // triggering an async load. That's what we need to assert the fallback
19
- // logic finds the loader regardless of path shape.
20
-
21
- const CARET_RIGHT_SVG = '<svg data-icon="caret-right"/>';
22
- const X_SVG = '<svg data-icon="x"/>';
23
- const CARET_UP_DOWN_SVG = '<svg data-icon="caret-up-down"/>';
24
-
25
- describe('installIconLoaders + resolveLoader (§140 suffix fallback)', () => {
26
- beforeEach(() => {
27
- // Reset to a known state. Note: the registry Map itself is module-
28
- // singleton + doesn't reset between tests, so we keep test SVG
29
- // contents distinct from any real phosphor names just in case.
30
- installIconLoaders({ regular: {}, thin: {}, light: {}, bold: {}, fill: {}, duotone: {} });
31
- });
32
-
33
- it('resolves by canonical phosphor path (fast path)', () => {
34
- installIconLoaders({
35
- regular: {
36
- '/node_modules/@phosphor-icons/core/assets/regular/caret-right.svg': CARET_RIGHT_SVG,
37
- '/node_modules/@phosphor-icons/core/assets/regular/x.svg': X_SVG,
38
- },
39
- });
40
-
41
- expect(isIconName('caret-right')).toBe(true);
42
- expect(isIconName('x')).toBe(true);
43
- });
44
-
45
- it('resolves by filename suffix when key has a pnpm-style prefix', () => {
46
- // pnpm hoists phosphor inside `.pnpm/<pkg>@<ver>/node_modules/...`
47
- installIconLoaders({
48
- regular: {
49
- '/node_modules/.pnpm/@phosphor-icons+core@2.1.1/node_modules/@phosphor-icons/core/assets/regular/caret-right.svg':
50
- CARET_RIGHT_SVG,
51
- },
52
- });
53
-
54
- expect(isIconName('caret-right')).toBe(true);
55
- });
56
-
57
- it('resolves by filename suffix when key is a custom Vite-root absolute path', () => {
58
- // Custom Vite root puts node_modules at a deeper location.
59
- installIconLoaders({
60
- regular: {
61
- '/packages/my-app/node_modules/@phosphor-icons/core/assets/regular/caret-up-down.svg':
62
- CARET_UP_DOWN_SVG,
63
- },
64
- });
65
-
66
- expect(isIconName('caret-up-down')).toBe(true);
67
- });
68
-
69
- it('resolves by filename suffix for an arbitrary manually-built loader map', () => {
70
- // Manual loader maps may have any keys at all — only the filename matters.
71
- installIconLoaders({
72
- regular: {
73
- '/anywhere/at/all/caret-right.svg': CARET_RIGHT_SVG,
74
- '/another/path/entirely/x.svg': X_SVG,
75
- },
76
- });
77
-
78
- expect(isIconName('caret-right')).toBe(true);
79
- expect(isIconName('x')).toBe(true);
80
- });
81
-
82
- it('returns false on miss across both paths', () => {
83
- installIconLoaders({
84
- regular: {
85
- '/node_modules/@phosphor-icons/core/assets/regular/caret-right.svg': CARET_RIGHT_SVG,
86
- },
87
- });
88
-
89
- expect(isIconName('not-a-real-icon-name')).toBe(false);
90
- });
91
-
92
- it('resolves `-fill` back-compat suffix in name via fast path', () => {
93
- const STAR_FILL_SVG = '<svg data-icon="star-fill"/>';
94
- installIconLoaders({
95
- regular: {},
96
- fill: {
97
- '/node_modules/@phosphor-icons/core/assets/fill/star-fill.svg': STAR_FILL_SVG,
98
- },
99
- });
100
-
101
- // `name="star-fill"` with default weight=regular falls back to fill weight.
102
- expect(isIconName('star-fill')).toBe(true);
103
- });
104
-
105
- it('resolves `-fill` back-compat suffix in name via suffix fallback', () => {
106
- const STAR_FILL_SVG = '<svg data-icon="star-fill"/>';
107
- installIconLoaders({
108
- regular: {},
109
- fill: {
110
- '/anywhere/star-fill.svg': STAR_FILL_SVG,
111
- },
112
- });
113
-
114
- expect(isIconName('star-fill')).toBe(true);
115
- });
116
-
117
- it('resolves non-regular weights (bold, fill, etc.) via suffix fallback', () => {
118
- const HEART_BOLD_SVG = '<svg data-icon="heart-bold"/>';
119
- installIconLoaders({
120
- regular: {},
121
- bold: {
122
- '/anywhere/heart-bold.svg': HEART_BOLD_SVG,
123
- },
124
- });
125
-
126
- expect(isIconName('heart', 'bold')).toBe(true);
127
- });
128
-
129
- it('isIconName returns true via alias resolution + suffix fallback', () => {
130
- // `send` → `paper-plane-right` in ICON_ALIASES. Suffix fallback should
131
- // still match the aliased filename.
132
- const PAPER_PLANE_SVG = '<svg data-icon="paper-plane-right"/>';
133
- installIconLoaders({
134
- regular: {
135
- '/anywhere/paper-plane-right.svg': PAPER_PLANE_SVG,
136
- },
137
- });
138
-
139
- expect(isIconName('send')).toBe(true);
140
- });
141
-
142
- it('end-to-end via DOM re-stamp: installIconLoaders auto-loads existing <icon-ui[name]>', async () => {
143
- // The §140 fix's primary symptom: `installIconLoaders` walks existing
144
- // `<icon-ui[name]>` elements + calls `loadIcon` for each. Without the
145
- // suffix fallback, `loadIcon` → `resolveLoader` returns null →
146
- // `warnMissingIcon` fires. With the fix, the load completes.
147
- const PAW_SVG = '<svg data-icon="paw"/>';
148
-
149
- // Plant an `<icon-ui[name="paw"]>` element. We don't need it to
150
- // actually upgrade; we just need `querySelectorAll('icon-ui[name]')`
151
- // to find it.
152
- const el = document.createElement('icon-ui');
153
- el.setAttribute('name', 'paw');
154
- document.body.appendChild(el);
155
-
156
- // Install a loader map with a pnpm-style path (suffix fallback exercise).
157
- installIconLoaders({
158
- regular: {
159
- '/some/non-canonical/prefix/paw.svg': PAW_SVG,
160
- },
161
- });
162
-
163
- // `loadIcon` runs sync (loader is a string, not a function), so the
164
- // registry is written by the time `installIconLoaders` returns.
165
- expect(hasIcon('paw')).toBe(true);
166
-
167
- document.body.removeChild(el);
168
- });
169
- });
170
-
171
- describe('registerIcon / hasIcon — unchanged by §140', () => {
172
- it('registerIcon adds an icon by name without needing a loader map', () => {
173
- const CUSTOM_SVG = '<svg data-icon="custom-§140-marker"/>';
174
- registerIcon('my-§140-custom-icon', CUSTOM_SVG);
175
-
176
- expect(hasIcon('my-§140-custom-icon')).toBe(true);
177
- expect(getIcon('my-§140-custom-icon')).toBe(CUSTOM_SVG);
178
- });
179
-
180
- it('listIcons returns registered names', () => {
181
- registerIcon('§140-listIcons-marker-one', '<svg/>');
182
- registerIcon('§140-listIcons-marker-two', '<svg/>');
183
-
184
- expect(listIcons().some((k) => k.includes('§140-listIcons-marker-one'))).toBe(true);
185
- expect(listIcons().some((k) => k.includes('§140-listIcons-marker-two'))).toBe(true);
186
- });
187
- });
@@ -1,54 +0,0 @@
1
- import { describe, it, expect, beforeEach } from 'vitest';
2
- import { logicalChildren, logicalSlotted } from './logical-children.js';
3
-
4
- function host(html) {
5
- const el = document.createElement('div');
6
- el.innerHTML = html;
7
- return el;
8
- }
9
-
10
- describe('logical-children', () => {
11
- beforeEach(() => { document.body.innerHTML = ''; });
12
-
13
- it('returns static direct children as-is', () => {
14
- const h = host(`<button slot="primary">A</button><p>body</p><button slot="secondary">B</button>`);
15
- expect(logicalChildren(h).map((e) => e.tagName)).toEqual(['BUTTON', 'P', 'BUTTON']);
16
- expect(logicalSlotted(h, 'primary').length).toBe(1);
17
- expect(logicalSlotted(h, 'secondary')[0].textContent).toBe('B');
18
- expect(logicalSlotted(h, 'missing').length).toBe(0);
19
- });
20
-
21
- it('pierces a single display:contents/role=presentation wrapper', () => {
22
- const h = host(`
23
- <span style="display:contents" role="presentation"><button slot="primary">A</button></span>
24
- <p>body</p>
25
- `);
26
- // Without piercing, h.querySelector(':scope > [slot="primary"]') would be null:
27
- expect(h.querySelector(':scope > [slot="primary"]')).toBeNull();
28
- // With the helper, it's found:
29
- expect(logicalSlotted(h, 'primary').length).toBe(1);
30
- expect(logicalChildren(h).map((e) => e.tagName)).toEqual(['BUTTON', 'P']);
31
- });
32
-
33
- it('pierces NESTED wrappers (the .map() shape)', () => {
34
- const h = host(`
35
- <span style="display:contents" role="presentation">
36
- <span style="display:contents" role="presentation"><button slot="act">A</button></span>
37
- <span style="display:contents" role="presentation"><button slot="act">B</button></span>
38
- </span>
39
- `);
40
- expect(logicalSlotted(h, 'act').map((e) => e.textContent)).toEqual(['A', 'B']);
41
- });
42
-
43
- it('does NOT pierce non-wrapper elements (logical = direct, wrappers aside)', () => {
44
- const h = host(`<div><button slot="primary">nested-in-real-div</button></div>`);
45
- // The button is inside a real <div>, not a transparent wrapper → not a logical child.
46
- expect(logicalSlotted(h, 'primary').length).toBe(0);
47
- expect(logicalChildren(h).map((e) => e.tagName)).toEqual(['DIV']);
48
- });
49
-
50
- it('tolerates a null host', () => {
51
- expect(logicalChildren(null)).toEqual([]);
52
- expect(logicalSlotted(null, 'x')).toEqual([]);
53
- });
54
- });
@@ -1,47 +0,0 @@
1
- import { describe, it, expect } from 'vitest';
2
- import { renderMarkdown } from './markdown.js';
3
-
4
- // ── inline() protect-verbatim-regions regression (site-a2ui/FINDINGS.md
5
- // "No inline-flow construct" — root cause: emphasis regexes ran BEFORE
6
- // code-span extraction, so any code span containing `*`/`_` mangled) ──
7
-
8
- describe('renderMarkdown — inline code spans survive markdown-unsafe content', () => {
9
- it('code span containing an asterisk is not treated as emphasis', () => {
10
- const html = renderMarkdown('Use `--a-chrome-*` for chrome tokens.');
11
- expect(html).toContain('<code>--a-chrome-*</code>');
12
- expect(html).not.toContain('<em>');
13
- expect(html).not.toContain('<strong>');
14
- });
15
-
16
- it('code span containing underscores is not treated as emphasis', () => {
17
- const html = renderMarkdown('Call `foo_bar_baz()` to run it.');
18
- expect(html).toContain('<code>foo_bar_baz()</code>');
19
- expect(html).not.toContain('<em>');
20
- });
21
-
22
- it('code span containing double-asterisks is not treated as bold', () => {
23
- const html = renderMarkdown('The pattern `**not-bold**` is literal.');
24
- expect(html).toContain('<code>**not-bold**</code>');
25
- expect(html).not.toContain('<strong>');
26
- });
27
-
28
- it('bold, italic, code, and link combine correctly within one run', () => {
29
- const html = renderMarkdown('This is **bold**, this is *italic*, this is `code_with_*` and this is [a link](https://example.com).');
30
- expect(html).toContain('<strong>bold</strong>');
31
- expect(html).toContain('<em>italic</em>');
32
- expect(html).toContain('<code>code_with_*</code>');
33
- expect(html).toContain('<a href="https://example.com">a link</a>');
34
- });
35
-
36
- it('adjacent code spans do not bleed into each other', () => {
37
- const html = renderMarkdown('First `a_b` then `c*d` then plain _italic_ text.');
38
- expect(html).toContain('<code>a_b</code>');
39
- expect(html).toContain('<code>c*d</code>');
40
- expect(html).toContain('<em>italic</em>');
41
- });
42
-
43
- it('HTML-unsafe characters inside a code span are still escaped', () => {
44
- const html = renderMarkdown('Use `<script>` tags carefully.');
45
- expect(html).toContain('<code>&lt;script&gt;</code>');
46
- });
47
- });