@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.
- package/CHANGELOG.md +26 -0
- package/components/card/card.a2ui.json +6 -3
- package/components/card/card.css +15 -1
- package/components/card/card.yaml +5 -3
- package/components/nav-group/nav-group.a2ui.json +5 -0
- package/components/nav-group/nav-group.class.js +14 -6
- package/components/nav-group/nav-group.css +5 -2
- package/components/nav-group/nav-group.d.ts +2 -0
- package/components/nav-group/nav-group.yaml +4 -0
- package/components/select/select.a2ui.json +3 -2
- package/components/select/select.class.js +14 -1
- package/components/select/select.css +13 -1
- package/components/select/select.yaml +5 -4
- package/components/table/table.class.js +7 -3
- package/components/table/table.css +7 -5
- package/dist/theme-provider.min.js +1 -1
- package/dist/web-components.min.css +1 -1
- package/dist/web-components.min.js +19 -19
- package/dist/web-components.sheet.js +1 -1
- package/package.json +2 -1
- package/components/adia-mark/adia-mark.test.js +0 -69
- package/components/adia-wordmark/adia-wordmark.test.js +0 -69
- package/components/agent-artifact/agent-artifact.test.js +0 -61
- package/components/alert/alert.test.js +0 -364
- package/components/color-picker/color-picker.test.js +0 -96
- package/components/combobox/combobox.test.js +0 -256
- package/components/command/command.test.js +0 -99
- package/components/date-range-picker/date-range-picker.test.js +0 -387
- package/components/datetime-picker/datetime-picker.test.js +0 -454
- package/components/demo-toggle/demo-toggle.test.js +0 -102
- package/components/description-list/description-list.test.js +0 -72
- package/components/display-field/display-field.test.js +0 -115
- package/components/empty-state/empty-state.test.js +0 -77
- package/components/field/field.test.js +0 -260
- package/components/fields/fields.test.js +0 -88
- package/components/inline-message/inline-message.test.js +0 -203
- package/components/input/input.test.js +0 -335
- package/components/integration-card/integration-card.test.js +0 -318
- package/components/link/link.test.js +0 -143
- package/components/list/list.test.js +0 -106
- package/components/list-window/list-window.test.js +0 -303
- package/components/loading-overlay/loading-overlay.test.js +0 -257
- package/components/menu/menu.test.js +0 -176
- package/components/nav/nav.test.js +0 -428
- package/components/nav-group/nav-group.test.js +0 -67
- package/components/option-card/option-card.test.js +0 -32
- package/components/pagination/pagination.test.js +0 -195
- package/components/select/select.test.js +0 -481
- package/components/slider/slider.test.js +0 -202
- package/components/spinner/spinner.test.js +0 -272
- package/components/stat/stat.test.js +0 -108
- package/components/step-progress/step-progress.test.js +0 -129
- package/components/swatch/swatch.test.js +0 -563
- package/components/table/table.test.js +0 -479
- package/components/table-toolbar/table-toolbar.test.js +0 -152
- package/components/tag/tag.test.js +0 -137
- package/components/tags-input/tags-input.test.js +0 -477
- package/components/text/text.test.js +0 -202
- package/components/theme-provider/theme-provider.test.js +0 -94
- package/components/time-picker/time-picker.test.js +0 -287
- package/components/timeline/timeline.test.js +0 -31
- package/components/toggle-scheme/toggle-scheme.test.js +0 -110
- package/components/tree/tree.test.js +0 -312
- package/core/element.test.js +0 -389
- package/core/icons.test.js +0 -187
- package/core/logical-children.test.js +0 -54
- package/core/markdown.test.js +0 -47
- package/core/responsive.test.js +0 -121
- package/core/template.test.js +0 -611
- package/traits/active-state/active-state.test.js +0 -28
- package/traits/anchor-positioning/anchor-positioning.test.js +0 -122
- package/traits/announcer/announcer.test.js +0 -268
- package/traits/arrow-grid-nav/arrow-grid-nav.test.js +0 -375
- package/traits/attention-pulse/attention-pulse.test.js +0 -26
- package/traits/confetti/confetti.test.js +0 -46
- package/traits/confetti-burst/confetti-burst.test.js +0 -46
- package/traits/count-up/count-up.test.js +0 -54
- package/traits/declarative.test.js +0 -138
- package/traits/dirty-state/dirty-state.test.js +0 -45
- package/traits/drag-ghost/drag-ghost.test.js +0 -19
- package/traits/draggable/draggable.test.js +0 -60
- package/traits/draggable-list-item/draggable-list-item.test.js +0 -51
- package/traits/drop-target/drop-target.test.js +0 -241
- package/traits/droppable/droppable.test.js +0 -54
- package/traits/droppable-collection/droppable-collection.test.js +0 -99
- package/traits/error-shake/error-shake.test.js +0 -114
- package/traits/fade-presence/fade-presence.test.js +0 -20
- package/traits/focus-restore/focus-restore.test.js +0 -202
- package/traits/focus-trap/focus-trap.test.js +0 -42
- package/traits/focusable/focusable.test.js +0 -53
- package/traits/glow-focus/glow-focus.test.js +0 -31
- package/traits/gradient-shift/gradient-shift.test.js +0 -22
- package/traits/haptic-feedback/haptic-feedback.test.js +0 -52
- package/traits/hotkey/hotkey.test.js +0 -61
- package/traits/hoverable/hoverable.test.js +0 -24
- package/traits/inertia-drag/inertia-drag.test.js +0 -33
- package/traits/input-mask/input-mask.test.js +0 -151
- package/traits/intersection-observer/intersection-observer.test.js +0 -38
- package/traits/keyboard-nav/keyboard-nav.test.js +0 -41
- package/traits/keyboard-reorderable/keyboard-reorderable.test.js +0 -45
- package/traits/layout-animation/layout-animation.test.js +0 -114
- package/traits/long-press/long-press.test.js +0 -244
- package/traits/magnetic-hover/magnetic-hover.test.js +0 -30
- package/traits/noise-texture/noise-texture.test.js +0 -20
- package/traits/parallax/parallax.test.js +0 -26
- package/traits/portal/portal.test.js +0 -30
- package/traits/pressable/pressable.test.js +0 -73
- package/traits/resettable/resettable.test.js +0 -100
- package/traits/resizable/resizable.test.js +0 -39
- package/traits/resize-observer/resize-observer.test.js +0 -38
- package/traits/ripple/ripple.test.js +0 -32
- package/traits/roving-tabindex/roving-tabindex.test.js +0 -28
- package/traits/scale-press/scale-press.test.js +0 -39
- package/traits/scroll-lock/scroll-lock.test.js +0 -45
- package/traits/scroll-progress/scroll-progress.test.js +0 -182
- package/traits/shimmer-loading/shimmer-loading.test.js +0 -43
- package/traits/smoke.test.js +0 -111
- package/traits/snap-to-grid/snap-to-grid.test.js +0 -40
- package/traits/sound-feedback/sound-feedback.test.js +0 -26
- package/traits/spring-animate/spring-animate.test.js +0 -28
- package/traits/success-checkmark/success-checkmark.test.js +0 -120
- package/traits/tilt-hover/tilt-hover.test.js +0 -32
- package/traits/tossable/tossable.test.js +0 -31
- package/traits/traits-host.test.js +0 -73
- package/traits/typeahead/typeahead.test.js +0 -38
- package/traits/typewriter/typewriter.test.js +0 -47
- package/traits/validation/validation.test.js +0 -93
- package/traits/view-transition/view-transition.test.js +0 -268
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, beforeEach } from 'vitest';
|
|
2
|
-
import { dirtyState } from './dirty-state.js';
|
|
3
|
-
import { mountHost, connectTrait, resetDOM } from '../test-helpers.js';
|
|
4
|
-
|
|
5
|
-
describe('dirty-state', () => {
|
|
6
|
-
beforeEach(resetDOM);
|
|
7
|
-
|
|
8
|
-
it('starts in pristine state', () => {
|
|
9
|
-
const host = mountHost('input', { value: 'initial' });
|
|
10
|
-
connectTrait(dirtyState, host);
|
|
11
|
-
expect(host.hasAttribute('data-dirty-state-pristine')).toBe(true);
|
|
12
|
-
expect(host.hasAttribute('data-dirty-state-dirty')).toBe(false);
|
|
13
|
-
});
|
|
14
|
-
|
|
15
|
-
it('input event with changed value flips to dirty', () => {
|
|
16
|
-
const host = mountHost('input', { value: 'initial' });
|
|
17
|
-
connectTrait(dirtyState, host);
|
|
18
|
-
host.value = 'modified';
|
|
19
|
-
host.dispatchEvent(new Event('input'));
|
|
20
|
-
expect(host.hasAttribute('data-dirty-state-dirty')).toBe(true);
|
|
21
|
-
expect(host.hasAttribute('data-dirty-state-pristine')).toBe(false);
|
|
22
|
-
});
|
|
23
|
-
|
|
24
|
-
it('reverting to initial value flips back to pristine', () => {
|
|
25
|
-
const host = mountHost('input', { value: 'initial' });
|
|
26
|
-
connectTrait(dirtyState, host);
|
|
27
|
-
host.value = 'modified';
|
|
28
|
-
host.dispatchEvent(new Event('input'));
|
|
29
|
-
expect(host.hasAttribute('data-dirty-state-dirty')).toBe(true);
|
|
30
|
-
host.value = 'initial';
|
|
31
|
-
host.dispatchEvent(new Event('input'));
|
|
32
|
-
expect(host.hasAttribute('data-dirty-state-pristine')).toBe(true);
|
|
33
|
-
expect(host.hasAttribute('data-dirty-state-dirty')).toBe(false);
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
it('disconnect clears both state attributes', () => {
|
|
37
|
-
const host = mountHost('input', { value: 'x' });
|
|
38
|
-
const inst = connectTrait(dirtyState, host);
|
|
39
|
-
host.value = 'changed';
|
|
40
|
-
host.dispatchEvent(new Event('input'));
|
|
41
|
-
inst.disconnect(host);
|
|
42
|
-
expect(host.hasAttribute('data-dirty-state-dirty')).toBe(false);
|
|
43
|
-
expect(host.hasAttribute('data-dirty-state-pristine')).toBe(false);
|
|
44
|
-
});
|
|
45
|
-
});
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, beforeEach } from 'vitest';
|
|
2
|
-
import { dragGhost } from './drag-ghost.js';
|
|
3
|
-
import { mountHost, connectTrait, resetDOM } from '../test-helpers.js';
|
|
4
|
-
|
|
5
|
-
describe('drag-ghost', () => {
|
|
6
|
-
beforeEach(resetDOM);
|
|
7
|
-
|
|
8
|
-
it('connect attaches without throwing', () => {
|
|
9
|
-
const host = mountHost();
|
|
10
|
-
expect(() => connectTrait(dragGhost, host)).not.toThrow();
|
|
11
|
-
});
|
|
12
|
-
|
|
13
|
-
it('disconnect clears the active attribute', () => {
|
|
14
|
-
const host = mountHost();
|
|
15
|
-
const inst = connectTrait(dragGhost, host);
|
|
16
|
-
inst.disconnect(host);
|
|
17
|
-
expect(host.hasAttribute('data-drag-ghost-active')).toBe(false);
|
|
18
|
-
});
|
|
19
|
-
});
|
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, beforeEach } from 'vitest';
|
|
2
|
-
import { draggable } from './draggable.js';
|
|
3
|
-
import { mountHost, connectTrait, spyEvent, resetDOM } from '../test-helpers.js';
|
|
4
|
-
|
|
5
|
-
function pointer(host, type, x, y) {
|
|
6
|
-
const ev = new PointerEvent(type, { clientX: x, clientY: y, pointerId: 1, bubbles: true });
|
|
7
|
-
host.dispatchEvent(ev);
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
describe('draggable', () => {
|
|
11
|
-
beforeEach(resetDOM);
|
|
12
|
-
|
|
13
|
-
it('pointerdown sets data-draggable-dragging', () => {
|
|
14
|
-
const host = mountHost();
|
|
15
|
-
connectTrait(draggable, host);
|
|
16
|
-
pointer(host, 'pointerdown', 10, 10);
|
|
17
|
-
expect(host.hasAttribute('data-draggable-dragging')).toBe(true);
|
|
18
|
-
});
|
|
19
|
-
|
|
20
|
-
it('pointermove during drag updates translate', () => {
|
|
21
|
-
const host = mountHost();
|
|
22
|
-
connectTrait(draggable, host);
|
|
23
|
-
pointer(host, 'pointerdown', 10, 10);
|
|
24
|
-
pointer(host, 'pointermove', 30, 50);
|
|
25
|
-
expect(host.style.translate).toContain('20px');
|
|
26
|
-
expect(host.style.translate).toContain('40px');
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
it('pointerup fires drag-end with x,y in detail and clears dragging', () => {
|
|
30
|
-
const host = mountHost();
|
|
31
|
-
connectTrait(draggable, host);
|
|
32
|
-
const spy = spyEvent(host, 'drag-end');
|
|
33
|
-
pointer(host, 'pointerdown', 0, 0);
|
|
34
|
-
pointer(host, 'pointermove', 25, 75);
|
|
35
|
-
pointer(host, 'pointerup', 25, 75);
|
|
36
|
-
expect(host.hasAttribute('data-draggable-dragging')).toBe(false);
|
|
37
|
-
expect(spy.count).toBe(1);
|
|
38
|
-
expect(spy.last.x).toBe(25);
|
|
39
|
-
expect(spy.last.y).toBe(75);
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
it('respects [disabled] — no drag, no event', () => {
|
|
43
|
-
const host = mountHost('div', { disabled: '' });
|
|
44
|
-
connectTrait(draggable, host);
|
|
45
|
-
const spy = spyEvent(host, 'drag-end');
|
|
46
|
-
pointer(host, 'pointerdown', 0, 0);
|
|
47
|
-
pointer(host, 'pointermove', 50, 50);
|
|
48
|
-
pointer(host, 'pointerup', 50, 50);
|
|
49
|
-
expect(host.hasAttribute('data-draggable-dragging')).toBe(false);
|
|
50
|
-
expect(spy.count).toBe(0);
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
it('disconnect removes listeners + dragging attribute', () => {
|
|
54
|
-
const host = mountHost();
|
|
55
|
-
const inst = connectTrait(draggable, host);
|
|
56
|
-
pointer(host, 'pointerdown', 0, 0);
|
|
57
|
-
inst.disconnect(host);
|
|
58
|
-
expect(host.hasAttribute('data-draggable-dragging')).toBe(false);
|
|
59
|
-
});
|
|
60
|
-
});
|
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, beforeEach } from 'vitest';
|
|
2
|
-
import { draggableListItem } from './draggable-list-item.js';
|
|
3
|
-
import { mountHost, connectTrait, resetDOM } from '../test-helpers.js';
|
|
4
|
-
|
|
5
|
-
// Minimal smoke coverage. The draggable-list-item trait is the pointer
|
|
6
|
-
// driver for the apps/tasks/ DnD model. The cases
|
|
7
|
-
// below exercise only its own surface (schema, attribute lifecycle,
|
|
8
|
-
// id assignment, idempotent disconnect). Full pointer-flow tests need a
|
|
9
|
-
// laid-out DOM with sibling droppables and live elementFromPoint —
|
|
10
|
-
// those belong with the playground integration suite, not the trait
|
|
11
|
-
// gate.
|
|
12
|
-
|
|
13
|
-
describe('draggable-list-item', () => {
|
|
14
|
-
beforeEach(resetDOM);
|
|
15
|
-
|
|
16
|
-
it('schema shape is defined', () => {
|
|
17
|
-
expect(draggableListItem.schema.name).toBe('draggable-list-item');
|
|
18
|
-
expect(draggableListItem.schema.category).toBe('motion-positioning');
|
|
19
|
-
expect(draggableListItem.schema.description.length).toBeGreaterThanOrEqual(11);
|
|
20
|
-
expect(draggableListItem.schema.events).toContain('dnd-lift');
|
|
21
|
-
expect(draggableListItem.schema.events).toContain('dnd-drop');
|
|
22
|
-
});
|
|
23
|
-
|
|
24
|
-
it('connect assigns an id + sets the host attribute', () => {
|
|
25
|
-
const host = mountHost('div');
|
|
26
|
-
connectTrait(draggableListItem, host);
|
|
27
|
-
expect(host.getAttribute('data-draggable-list-item-id')).toBeTruthy();
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
it('disconnect clears its attributes', () => {
|
|
31
|
-
const host = mountHost('div');
|
|
32
|
-
const inst = draggableListItem();
|
|
33
|
-
inst.connect(host);
|
|
34
|
-
host.setAttribute('data-draggable-list-item-lifting', '');
|
|
35
|
-
inst.disconnect(host);
|
|
36
|
-
expect(host.hasAttribute('data-draggable-list-item-lifting')).toBe(false);
|
|
37
|
-
expect(host.hasAttribute('data-draggable-list-item-id')).toBe(false);
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
it('reconnect with the same host does not throw', () => {
|
|
41
|
-
const host = mountHost('div');
|
|
42
|
-
const inst = draggableListItem();
|
|
43
|
-
inst.connect(host);
|
|
44
|
-
inst.disconnect(host);
|
|
45
|
-
expect(() => {
|
|
46
|
-
const inst2 = draggableListItem();
|
|
47
|
-
inst2.connect(host);
|
|
48
|
-
inst2.disconnect(host);
|
|
49
|
-
}).not.toThrow();
|
|
50
|
-
});
|
|
51
|
-
});
|
|
@@ -1,241 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, beforeEach } from 'vitest';
|
|
2
|
-
import { dropTarget } from './drop-target.js';
|
|
3
|
-
import { mountHost, connectTrait, spyEvent, resetDOM } from '../test-helpers.js';
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* happy-dom's DragEvent constructor does NOT preserve the `dataTransfer`
|
|
7
|
-
* field passed via the init dict (verified at authoring time:
|
|
8
|
-
* `new DragEvent('dragover', { dataTransfer })` then `.dataTransfer`
|
|
9
|
-
* comes back as null). We synthesize the event by creating an Event,
|
|
10
|
-
* then defining `dataTransfer` as a non-enumerable read-only property —
|
|
11
|
-
* the trait reads it the same way the browser would.
|
|
12
|
-
*/
|
|
13
|
-
function fireDragEvent(host, type, dataTransfer = null) {
|
|
14
|
-
const ev = new Event(type, { bubbles: true, cancelable: true });
|
|
15
|
-
Object.defineProperty(ev, 'dataTransfer', {
|
|
16
|
-
value: dataTransfer,
|
|
17
|
-
writable: false,
|
|
18
|
-
configurable: true,
|
|
19
|
-
});
|
|
20
|
-
host.dispatchEvent(ev);
|
|
21
|
-
return ev;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
function fakeDataTransfer({ types = [], items = [] } = {}) {
|
|
25
|
-
return {
|
|
26
|
-
types,
|
|
27
|
-
items,
|
|
28
|
-
files: items.filter((i) => i.kind === 'file').map((i) => i.file).filter(Boolean),
|
|
29
|
-
getData: () => '',
|
|
30
|
-
setData: () => {},
|
|
31
|
-
};
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
describe('drop-target', () => {
|
|
35
|
-
beforeEach(resetDOM);
|
|
36
|
-
|
|
37
|
-
it('sets data-drop-target-active on connect, removes on disconnect', () => {
|
|
38
|
-
const host = mountHost();
|
|
39
|
-
const inst = connectTrait(dropTarget, host);
|
|
40
|
-
expect(host.hasAttribute('data-drop-target-active')).toBe(true);
|
|
41
|
-
inst.disconnect(host);
|
|
42
|
-
expect(host.hasAttribute('data-drop-target-active')).toBe(false);
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
it('dragenter sets data-drop-target-over and dispatches drop-enter', () => {
|
|
46
|
-
const host = mountHost();
|
|
47
|
-
connectTrait(dropTarget, host);
|
|
48
|
-
const spy = spyEvent(host, 'drop-enter');
|
|
49
|
-
fireDragEvent(host, 'dragenter', fakeDataTransfer());
|
|
50
|
-
expect(host.hasAttribute('data-drop-target-over')).toBe(true);
|
|
51
|
-
expect(spy.count).toBe(1);
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
it('dragleave clears data-drop-target-over and dispatches drop-leave', () => {
|
|
55
|
-
const host = mountHost();
|
|
56
|
-
connectTrait(dropTarget, host);
|
|
57
|
-
const enterSpy = spyEvent(host, 'drop-enter');
|
|
58
|
-
const leaveSpy = spyEvent(host, 'drop-leave');
|
|
59
|
-
fireDragEvent(host, 'dragenter', fakeDataTransfer());
|
|
60
|
-
fireDragEvent(host, 'dragleave', fakeDataTransfer());
|
|
61
|
-
expect(host.hasAttribute('data-drop-target-over')).toBe(false);
|
|
62
|
-
expect(enterSpy.count).toBe(1);
|
|
63
|
-
expect(leaveSpy.count).toBe(1);
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
it('dragenter from descendant nesting only emits one drop-enter', () => {
|
|
67
|
-
const host = mountHost();
|
|
68
|
-
connectTrait(dropTarget, host);
|
|
69
|
-
const enterSpy = spyEvent(host, 'drop-enter');
|
|
70
|
-
const leaveSpy = spyEvent(host, 'drop-leave');
|
|
71
|
-
// Two enters (host then descendant) should produce ONE drop-enter
|
|
72
|
-
// and NOT clear the over-state when one matching leave fires.
|
|
73
|
-
fireDragEvent(host, 'dragenter', fakeDataTransfer());
|
|
74
|
-
fireDragEvent(host, 'dragenter', fakeDataTransfer());
|
|
75
|
-
fireDragEvent(host, 'dragleave', fakeDataTransfer());
|
|
76
|
-
expect(enterSpy.count).toBe(1);
|
|
77
|
-
expect(leaveSpy.count).toBe(0);
|
|
78
|
-
expect(host.hasAttribute('data-drop-target-over')).toBe(true);
|
|
79
|
-
// The matched leave finally clears.
|
|
80
|
-
fireDragEvent(host, 'dragleave', fakeDataTransfer());
|
|
81
|
-
expect(leaveSpy.count).toBe(1);
|
|
82
|
-
expect(host.hasAttribute('data-drop-target-over')).toBe(false);
|
|
83
|
-
});
|
|
84
|
-
|
|
85
|
-
it('dragover calls preventDefault when no accept filter is set', () => {
|
|
86
|
-
const host = mountHost();
|
|
87
|
-
connectTrait(dropTarget, host);
|
|
88
|
-
const ev = fireDragEvent(host, 'dragover', fakeDataTransfer({ types: ['text/plain'] }));
|
|
89
|
-
expect(ev.defaultPrevented).toBe(true);
|
|
90
|
-
});
|
|
91
|
-
|
|
92
|
-
it('dragover with empty accept attribute still accepts (preventDefault)', () => {
|
|
93
|
-
const host = mountHost('div', { 'data-drop-target-accepts': '' });
|
|
94
|
-
connectTrait(dropTarget, host);
|
|
95
|
-
const ev = fireDragEvent(host, 'dragover', fakeDataTransfer({ types: ['text/plain'] }));
|
|
96
|
-
expect(ev.defaultPrevented).toBe(true);
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
it('accepts="files" — accepts file drags, rejects non-file payloads', () => {
|
|
100
|
-
const host = mountHost('div', { 'data-drop-target-accepts': 'files' });
|
|
101
|
-
connectTrait(dropTarget, host);
|
|
102
|
-
const rejectedSpy = spyEvent(host, 'drop-rejected');
|
|
103
|
-
|
|
104
|
-
// File drag — Files token present → accepted
|
|
105
|
-
const fileEv = fireDragEvent(host, 'dragover', fakeDataTransfer({ types: ['Files'] }));
|
|
106
|
-
expect(fileEv.defaultPrevented).toBe(true);
|
|
107
|
-
expect(host.hasAttribute('data-drop-target-rejected')).toBe(false);
|
|
108
|
-
|
|
109
|
-
// Plain text drag — no Files → rejected, no preventDefault
|
|
110
|
-
const textEv = fireDragEvent(host, 'dragover', fakeDataTransfer({ types: ['text/plain'] }));
|
|
111
|
-
expect(textEv.defaultPrevented).toBe(false);
|
|
112
|
-
expect(host.getAttribute('data-drop-target-rejected')).toBe('files-only');
|
|
113
|
-
expect(rejectedSpy.count).toBe(1);
|
|
114
|
-
expect(rejectedSpy.last.reason).toBe('files-only');
|
|
115
|
-
});
|
|
116
|
-
|
|
117
|
-
it('accepts="image/png,image/jpeg" — rejects mismatched MIME types', () => {
|
|
118
|
-
const host = mountHost('div', { 'data-drop-target-accepts': 'image/png,image/jpeg' });
|
|
119
|
-
connectTrait(dropTarget, host);
|
|
120
|
-
|
|
121
|
-
// Direct text type → rejected
|
|
122
|
-
const textEv = fireDragEvent(host, 'dragover', fakeDataTransfer({ types: ['text/plain'] }));
|
|
123
|
-
expect(textEv.defaultPrevented).toBe(false);
|
|
124
|
-
expect(host.getAttribute('data-drop-target-rejected')).toBe('type');
|
|
125
|
-
|
|
126
|
-
// PNG file drag → accepted
|
|
127
|
-
const pngDt = fakeDataTransfer({
|
|
128
|
-
types: ['Files'],
|
|
129
|
-
items: [{ kind: 'file', type: 'image/png' }],
|
|
130
|
-
});
|
|
131
|
-
const pngEv = fireDragEvent(host, 'dragover', pngDt);
|
|
132
|
-
expect(pngEv.defaultPrevented).toBe(true);
|
|
133
|
-
expect(host.hasAttribute('data-drop-target-rejected')).toBe(false);
|
|
134
|
-
|
|
135
|
-
// PDF file drag → rejected
|
|
136
|
-
const pdfDt = fakeDataTransfer({
|
|
137
|
-
types: ['Files'],
|
|
138
|
-
items: [{ kind: 'file', type: 'application/pdf' }],
|
|
139
|
-
});
|
|
140
|
-
const pdfEv = fireDragEvent(host, 'dragover', pdfDt);
|
|
141
|
-
expect(pdfEv.defaultPrevented).toBe(false);
|
|
142
|
-
expect(host.getAttribute('data-drop-target-rejected')).toBe('type');
|
|
143
|
-
});
|
|
144
|
-
|
|
145
|
-
it('drop dispatches drop-receive with dataTransfer detail', () => {
|
|
146
|
-
const host = mountHost();
|
|
147
|
-
connectTrait(dropTarget, host);
|
|
148
|
-
const spy = spyEvent(host, 'drop-receive');
|
|
149
|
-
const dt = fakeDataTransfer({ types: ['text/plain'] });
|
|
150
|
-
|
|
151
|
-
fireDragEvent(host, 'dragenter', dt);
|
|
152
|
-
fireDragEvent(host, 'dragover', dt);
|
|
153
|
-
const dropEv = fireDragEvent(host, 'drop', dt);
|
|
154
|
-
expect(dropEv.defaultPrevented).toBe(true);
|
|
155
|
-
expect(spy.count).toBe(1);
|
|
156
|
-
expect(spy.last.dataTransfer).toBe(dt);
|
|
157
|
-
});
|
|
158
|
-
|
|
159
|
-
it('drop clears data-drop-target-over after firing drop-receive', () => {
|
|
160
|
-
const host = mountHost();
|
|
161
|
-
connectTrait(dropTarget, host);
|
|
162
|
-
const dt = fakeDataTransfer({ types: ['text/plain'] });
|
|
163
|
-
fireDragEvent(host, 'dragenter', dt);
|
|
164
|
-
expect(host.hasAttribute('data-drop-target-over')).toBe(true);
|
|
165
|
-
fireDragEvent(host, 'drop', dt);
|
|
166
|
-
expect(host.hasAttribute('data-drop-target-over')).toBe(false);
|
|
167
|
-
});
|
|
168
|
-
|
|
169
|
-
it('drop on rejected payload dispatches drop-rejected, not drop-receive', () => {
|
|
170
|
-
const host = mountHost('div', { 'data-drop-target-accepts': 'image/png' });
|
|
171
|
-
connectTrait(dropTarget, host);
|
|
172
|
-
const receiveSpy = spyEvent(host, 'drop-receive');
|
|
173
|
-
const rejectedSpy = spyEvent(host, 'drop-rejected');
|
|
174
|
-
const dt = fakeDataTransfer({ types: ['text/plain'] });
|
|
175
|
-
|
|
176
|
-
fireDragEvent(host, 'drop', dt);
|
|
177
|
-
expect(receiveSpy.count).toBe(0);
|
|
178
|
-
expect(rejectedSpy.count).toBe(1);
|
|
179
|
-
expect(rejectedSpy.last.reason).toBe('type');
|
|
180
|
-
});
|
|
181
|
-
|
|
182
|
-
it('respects [disabled] — no event, no over-state', () => {
|
|
183
|
-
const host = mountHost('div', { disabled: '' });
|
|
184
|
-
connectTrait(dropTarget, host);
|
|
185
|
-
const enterSpy = spyEvent(host, 'drop-enter');
|
|
186
|
-
const receiveSpy = spyEvent(host, 'drop-receive');
|
|
187
|
-
|
|
188
|
-
fireDragEvent(host, 'dragenter', fakeDataTransfer());
|
|
189
|
-
fireDragEvent(host, 'dragover', fakeDataTransfer());
|
|
190
|
-
fireDragEvent(host, 'drop', fakeDataTransfer());
|
|
191
|
-
|
|
192
|
-
expect(host.hasAttribute('data-drop-target-over')).toBe(false);
|
|
193
|
-
expect(enterSpy.count).toBe(0);
|
|
194
|
-
expect(receiveSpy.count).toBe(0);
|
|
195
|
-
});
|
|
196
|
-
|
|
197
|
-
it('disconnect removes listeners and clears managed attributes', () => {
|
|
198
|
-
const host = mountHost();
|
|
199
|
-
const inst = connectTrait(dropTarget, host);
|
|
200
|
-
fireDragEvent(host, 'dragenter', fakeDataTransfer());
|
|
201
|
-
expect(host.hasAttribute('data-drop-target-over')).toBe(true);
|
|
202
|
-
inst.disconnect(host);
|
|
203
|
-
expect(host.hasAttribute('data-drop-target-over')).toBe(false);
|
|
204
|
-
expect(host.hasAttribute('data-drop-target-active')).toBe(false);
|
|
205
|
-
expect(host.hasAttribute('data-drop-target-rejected')).toBe(false);
|
|
206
|
-
|
|
207
|
-
// Re-firing post-disconnect is a no-op.
|
|
208
|
-
const spy = spyEvent(host, 'drop-enter');
|
|
209
|
-
fireDragEvent(host, 'dragenter', fakeDataTransfer());
|
|
210
|
-
expect(spy.count).toBe(0);
|
|
211
|
-
});
|
|
212
|
-
|
|
213
|
-
it('clears data-drop-target-rejected when dragover later sees an accepted payload', () => {
|
|
214
|
-
const host = mountHost('div', { 'data-drop-target-accepts': 'image/png' });
|
|
215
|
-
connectTrait(dropTarget, host);
|
|
216
|
-
|
|
217
|
-
fireDragEvent(host, 'dragover', fakeDataTransfer({ types: ['text/plain'] }));
|
|
218
|
-
expect(host.getAttribute('data-drop-target-rejected')).toBe('type');
|
|
219
|
-
|
|
220
|
-
const pngDt = fakeDataTransfer({
|
|
221
|
-
types: ['Files'],
|
|
222
|
-
items: [{ kind: 'file', type: 'image/png' }],
|
|
223
|
-
});
|
|
224
|
-
fireDragEvent(host, 'dragover', pngDt);
|
|
225
|
-
expect(host.hasAttribute('data-drop-target-rejected')).toBe(false);
|
|
226
|
-
});
|
|
227
|
-
|
|
228
|
-
it('dispatches drop-rejected only once per rejection reason on consecutive dragover', () => {
|
|
229
|
-
const host = mountHost('div', { 'data-drop-target-accepts': 'image/png' });
|
|
230
|
-
connectTrait(dropTarget, host);
|
|
231
|
-
const rejectedSpy = spyEvent(host, 'drop-rejected');
|
|
232
|
-
const dt = fakeDataTransfer({ types: ['text/plain'] });
|
|
233
|
-
|
|
234
|
-
// Three consecutive dragover events with the same rejection reason
|
|
235
|
-
// should emit ONE drop-rejected, not three.
|
|
236
|
-
fireDragEvent(host, 'dragover', dt);
|
|
237
|
-
fireDragEvent(host, 'dragover', dt);
|
|
238
|
-
fireDragEvent(host, 'dragover', dt);
|
|
239
|
-
expect(rejectedSpy.count).toBe(1);
|
|
240
|
-
});
|
|
241
|
-
});
|
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, beforeEach } from 'vitest';
|
|
2
|
-
import { droppable } from './droppable.js';
|
|
3
|
-
import { mountHost, connectTrait, resetDOM } from '../test-helpers.js';
|
|
4
|
-
|
|
5
|
-
// Minimal smoke coverage. The droppable trait is authored against the
|
|
6
|
-
// apps/tasks/ DnD coordinator and assumes a sibling
|
|
7
|
-
// `draggable-list-item` trait dispatches `dnd:drop-target-change` /
|
|
8
|
-
// `dnd:drop` on the document. The cases below only exercise the trait's
|
|
9
|
-
// own surface (schema, attribute lifecycle, registry entry, cleanup) so
|
|
10
|
-
// the verify:traits gate stays green; the higher-fidelity DnD-flow tests
|
|
11
|
-
// belong with the Tasks UI initiative that owns the coordinator.
|
|
12
|
-
|
|
13
|
-
describe('droppable', () => {
|
|
14
|
-
beforeEach(resetDOM);
|
|
15
|
-
|
|
16
|
-
it('schema shape is defined', () => {
|
|
17
|
-
expect(droppable.schema.name).toBe('droppable');
|
|
18
|
-
expect(droppable.schema.category).toBe('input-interaction');
|
|
19
|
-
expect(droppable.schema.description.length).toBeGreaterThanOrEqual(11);
|
|
20
|
-
expect(droppable.schema.events).toContain('dnd-drop-enter');
|
|
21
|
-
// Source-side `dnd-drop` (on document, from the lifter) and target-side
|
|
22
|
-
// `dnd-drop-receive` (on host) MUST be distinct names — sharing the name
|
|
23
|
-
// recurses infinitely when the host event bubbles back to the document.
|
|
24
|
-
expect(droppable.schema.events).toContain('dnd-drop-receive');
|
|
25
|
-
expect(droppable.schema.events).not.toContain('dnd-drop');
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
it('connect assigns an id + sets the host attribute', () => {
|
|
29
|
-
const host = mountHost('div');
|
|
30
|
-
connectTrait(droppable, host);
|
|
31
|
-
expect(host.getAttribute('data-droppable-id')).toBeTruthy();
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
it('disconnect removes the over attribute if set', () => {
|
|
35
|
-
const host = mountHost('div');
|
|
36
|
-
const inst = droppable();
|
|
37
|
-
inst.connect(host);
|
|
38
|
-
host.setAttribute('data-droppable-over', '');
|
|
39
|
-
inst.disconnect(host);
|
|
40
|
-
expect(host.hasAttribute('data-droppable-over')).toBe(false);
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
it('reconnect with the same host does not throw', () => {
|
|
44
|
-
const host = mountHost('div');
|
|
45
|
-
const inst = droppable();
|
|
46
|
-
inst.connect(host);
|
|
47
|
-
inst.disconnect(host);
|
|
48
|
-
expect(() => {
|
|
49
|
-
const inst2 = droppable();
|
|
50
|
-
inst2.connect(host);
|
|
51
|
-
inst2.disconnect(host);
|
|
52
|
-
}).not.toThrow();
|
|
53
|
-
});
|
|
54
|
-
});
|
|
@@ -1,99 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, beforeEach } from 'vitest';
|
|
2
|
-
import { droppableCollection } from './droppable-collection.js';
|
|
3
|
-
import { mountHost, connectTrait, spyEvent, resetDOM } from '../test-helpers.js';
|
|
4
|
-
|
|
5
|
-
// Minimal smoke coverage. The droppable-collection trait coordinates
|
|
6
|
-
// child droppables of the apps/tasks/ DnD model;
|
|
7
|
-
// the cases below verify only the trait's own surface (schema, attribute
|
|
8
|
-
// lifecycle, child-drop re-emission) so the verify:traits gate stays
|
|
9
|
-
// green. End-to-end multi-column behavior belongs in the playground tests.
|
|
10
|
-
|
|
11
|
-
describe('droppable-collection', () => {
|
|
12
|
-
beforeEach(resetDOM);
|
|
13
|
-
|
|
14
|
-
it('schema shape is defined', () => {
|
|
15
|
-
expect(droppableCollection.schema.name).toBe('droppable-collection');
|
|
16
|
-
expect(droppableCollection.schema.category).toBe('input-interaction');
|
|
17
|
-
expect(droppableCollection.schema.description.length).toBeGreaterThanOrEqual(11);
|
|
18
|
-
expect(droppableCollection.schema.events).toContain('dnd-collection-drop');
|
|
19
|
-
});
|
|
20
|
-
|
|
21
|
-
it('connect sets data-droppable-collection-active', () => {
|
|
22
|
-
const host = mountHost('div');
|
|
23
|
-
connectTrait(droppableCollection, host);
|
|
24
|
-
expect(host.hasAttribute('data-droppable-collection-active')).toBe(true);
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
it('disconnect clears its attributes', () => {
|
|
28
|
-
const host = mountHost('div');
|
|
29
|
-
const inst = droppableCollection();
|
|
30
|
-
inst.connect(host);
|
|
31
|
-
host.setAttribute('data-droppable-collection-dragging', '');
|
|
32
|
-
inst.disconnect(host);
|
|
33
|
-
expect(host.hasAttribute('data-droppable-collection-active')).toBe(false);
|
|
34
|
-
expect(host.hasAttribute('data-droppable-collection-dragging')).toBe(false);
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
it('re-emits a child dnd-drop-receive as dnd-collection-drop on the host', () => {
|
|
38
|
-
// The collection listens for the TARGET-side `dnd-drop-receive` (emitted
|
|
39
|
-
// by the [droppable] trait on its host), NOT the source-side `dnd-drop`
|
|
40
|
-
// (emitted by the lifter on document). The two events are deliberately
|
|
41
|
-
// distinct names so that the target re-emit doesn't bubble back into
|
|
42
|
-
// the lifter's document-level listener and recurse.
|
|
43
|
-
const host = mountHost('div');
|
|
44
|
-
const child = document.createElement('div');
|
|
45
|
-
host.appendChild(child);
|
|
46
|
-
connectTrait(droppableCollection, host);
|
|
47
|
-
|
|
48
|
-
const spy = spyEvent(host, 'dnd-collection-drop');
|
|
49
|
-
child.dispatchEvent(new CustomEvent('dnd-drop-receive', {
|
|
50
|
-
bubbles: true,
|
|
51
|
-
composed: false,
|
|
52
|
-
detail: {
|
|
53
|
-
source_id: 't-1',
|
|
54
|
-
source_index: 0,
|
|
55
|
-
source_container_id: 'col-a',
|
|
56
|
-
target_container_id: 'col-b',
|
|
57
|
-
target_index: 2,
|
|
58
|
-
},
|
|
59
|
-
}));
|
|
60
|
-
|
|
61
|
-
expect(spy.count).toBe(1);
|
|
62
|
-
expect(spy.last).toMatchObject({
|
|
63
|
-
source_id: 't-1',
|
|
64
|
-
target_container_id: 'col-b',
|
|
65
|
-
target_index: 2,
|
|
66
|
-
});
|
|
67
|
-
spy.cleanup();
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
it('does NOT react to the source-side dnd-drop (would recurse)', () => {
|
|
71
|
-
// Regression guard: a source-side `dnd-drop` bubbling up from a child
|
|
72
|
-
// must NOT trigger `dnd-collection-drop`. Only the target-side
|
|
73
|
-
// `dnd-drop-receive` is the trigger.
|
|
74
|
-
const host = mountHost('div');
|
|
75
|
-
const child = document.createElement('div');
|
|
76
|
-
host.appendChild(child);
|
|
77
|
-
connectTrait(droppableCollection, host);
|
|
78
|
-
|
|
79
|
-
const spy = spyEvent(host, 'dnd-collection-drop');
|
|
80
|
-
child.dispatchEvent(new CustomEvent('dnd-drop', {
|
|
81
|
-
bubbles: true,
|
|
82
|
-
composed: false,
|
|
83
|
-
detail: { source_id: 't-1', target_container_id: 'col-b', target_index: 0 },
|
|
84
|
-
}));
|
|
85
|
-
expect(spy.count).toBe(0);
|
|
86
|
-
spy.cleanup();
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
it('reflects dragging state from dnd-lift / dnd-drop-cancel', () => {
|
|
90
|
-
const host = mountHost('div');
|
|
91
|
-
connectTrait(droppableCollection, host);
|
|
92
|
-
|
|
93
|
-
host.dispatchEvent(new CustomEvent('dnd-lift', { bubbles: true, detail: {} }));
|
|
94
|
-
expect(host.hasAttribute('data-droppable-collection-dragging')).toBe(true);
|
|
95
|
-
|
|
96
|
-
host.dispatchEvent(new CustomEvent('dnd-drop-cancel', { bubbles: true, detail: {} }));
|
|
97
|
-
expect(host.hasAttribute('data-droppable-collection-dragging')).toBe(false);
|
|
98
|
-
});
|
|
99
|
-
});
|