@nuvia-ui/components 4.0.1
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/package.json +27 -0
- package/src/ds-accordion/ds-accordion-item.js +288 -0
- package/src/ds-accordion/ds-accordion-item.stories.js +82 -0
- package/src/ds-accordion/ds-accordion.a11y.test.js +92 -0
- package/src/ds-accordion/ds-accordion.js +68 -0
- package/src/ds-accordion/ds-accordion.stories.js +118 -0
- package/src/ds-accordion/ds-accordion.test.js +146 -0
- package/src/ds-accordion/index.js +2 -0
- package/src/ds-action-bar/ds-action-bar.js +116 -0
- package/src/ds-action-bar/ds-action-bar.stories.js +86 -0
- package/src/ds-action-bar/ds-action-bar.test.js +64 -0
- package/src/ds-action-bar/index.js +1 -0
- package/src/ds-alert/ds-alert.a11y.test.js +151 -0
- package/src/ds-alert/ds-alert.js +223 -0
- package/src/ds-alert/ds-alert.mdx +142 -0
- package/src/ds-alert/ds-alert.stories.js +166 -0
- package/src/ds-alert/ds-alert.test.js +256 -0
- package/src/ds-alert/index.js +1 -0
- package/src/ds-avatar/ds-avatar.a11y.test.js +45 -0
- package/src/ds-avatar/ds-avatar.js +216 -0
- package/src/ds-avatar/ds-avatar.stories.js +120 -0
- package/src/ds-avatar/ds-avatar.test.js +83 -0
- package/src/ds-avatar/index.js +1 -0
- package/src/ds-avatar-extended/ds-avatar-extended.a11y.test.js +29 -0
- package/src/ds-avatar-extended/ds-avatar-extended.js +108 -0
- package/src/ds-avatar-extended/ds-avatar-extended.stories.js +93 -0
- package/src/ds-avatar-extended/ds-avatar-extended.test.js +66 -0
- package/src/ds-avatar-extended/index.js +1 -0
- package/src/ds-banner/ds-banner.a11y.test.js +51 -0
- package/src/ds-banner/ds-banner.js +233 -0
- package/src/ds-banner/ds-banner.stories.js +185 -0
- package/src/ds-banner/ds-banner.test.js +116 -0
- package/src/ds-banner/index.js +1 -0
- package/src/ds-breadcrumb-item/ds-breadcrumb-item.js +135 -0
- package/src/ds-breadcrumb-item/ds-breadcrumb-item.stories.js +49 -0
- package/src/ds-breadcrumb-item/ds-breadcrumb-item.test.js +55 -0
- package/src/ds-breadcrumbs/ds-breadcrumbs.js +194 -0
- package/src/ds-breadcrumbs/ds-breadcrumbs.stories.js +54 -0
- package/src/ds-breadcrumbs/ds-breadcrumbs.test.js +33 -0
- package/src/ds-button/ds-button.a11y.test.js +49 -0
- package/src/ds-button/ds-button.js +205 -0
- package/src/ds-button/ds-button.mdx +141 -0
- package/src/ds-button/ds-button.stories.js +152 -0
- package/src/ds-button/ds-button.test.js +62 -0
- package/src/ds-button/index.js +1 -0
- package/src/ds-button-group/ds-button-group.js +82 -0
- package/src/ds-button-group/ds-button-group.mdx +39 -0
- package/src/ds-button-group/ds-button-group.stories.js +47 -0
- package/src/ds-button-group/ds-button-group.test.js +47 -0
- package/src/ds-button-group/index.js +1 -0
- package/src/ds-checkbox/ds-checkbox.a11y.test.js +79 -0
- package/src/ds-checkbox/ds-checkbox.js +271 -0
- package/src/ds-checkbox/ds-checkbox.stories.js +77 -0
- package/src/ds-checkbox/ds-checkbox.test.js +191 -0
- package/src/ds-checkbox/index.js +1 -0
- package/src/ds-checkbox-group/ds-checkbox-group.a11y.test.js +146 -0
- package/src/ds-checkbox-group/ds-checkbox-group.js +235 -0
- package/src/ds-checkbox-group/ds-checkbox-group.stories.js +210 -0
- package/src/ds-checkbox-group/ds-checkbox-group.test.js +150 -0
- package/src/ds-checkbox-group/index.js +1 -0
- package/src/ds-dialog/ds-dialog.js +466 -0
- package/src/ds-dialog/ds-dialog.stories.js +274 -0
- package/src/ds-dialog/ds-dialog.test.js +441 -0
- package/src/ds-dialog/index.js +1 -0
- package/src/ds-dropdown/ds-dropdown.a11y.test.js +80 -0
- package/src/ds-dropdown/ds-dropdown.js +891 -0
- package/src/ds-dropdown/ds-dropdown.stories.js +259 -0
- package/src/ds-dropdown/ds-dropdown.test.js +268 -0
- package/src/ds-dropdown/index.js +1 -0
- package/src/ds-dropdown-group/ds-dropdown-group.js +55 -0
- package/src/ds-dropdown-panel/ds-dropdown-panel.js +34 -0
- package/src/ds-file-uploaded/ds-file-uploaded.a11y.test.js +40 -0
- package/src/ds-file-uploaded/ds-file-uploaded.js +135 -0
- package/src/ds-file-uploaded/ds-file-uploaded.mdx +33 -0
- package/src/ds-file-uploaded/ds-file-uploaded.stories.js +81 -0
- package/src/ds-file-uploaded/ds-file-uploaded.test.js +85 -0
- package/src/ds-file-uploader/ds-file-uploader.a11y.test.js +61 -0
- package/src/ds-file-uploader/ds-file-uploader.js +442 -0
- package/src/ds-file-uploader/ds-file-uploader.mdx +44 -0
- package/src/ds-file-uploader/ds-file-uploader.stories.js +76 -0
- package/src/ds-file-uploader/ds-file-uploader.test.js +142 -0
- package/src/ds-header/ds-header.a11y.test.js +38 -0
- package/src/ds-header/ds-header.js +149 -0
- package/src/ds-header/ds-header.stories.js +63 -0
- package/src/ds-header/ds-header.test.js +52 -0
- package/src/ds-header/index.js +1 -0
- package/src/ds-header-nav/ds-header-nav.a11y.test.js +69 -0
- package/src/ds-header-nav/ds-header-nav.js +114 -0
- package/src/ds-header-nav/ds-header-nav.stories.js +17 -0
- package/src/ds-header-nav/ds-header-nav.test.js +93 -0
- package/src/ds-header-nav-item/ds-header-nav-item.a11y.test.js +71 -0
- package/src/ds-header-nav-item/ds-header-nav-item.js +124 -0
- package/src/ds-header-nav-item/ds-header-nav-item.stories.js +43 -0
- package/src/ds-header-nav-item/ds-header-nav-item.test.js +61 -0
- package/src/ds-icon/ds-icon.a11y.test.js +49 -0
- package/src/ds-icon/ds-icon.js +75 -0
- package/src/ds-icon/ds-icon.mdx +36 -0
- package/src/ds-icon/ds-icon.stories.js +88 -0
- package/src/ds-icon/ds-icon.test.js +97 -0
- package/src/ds-icon/index.js +1 -0
- package/src/ds-icon-button/ds-icon-button.a11y.test.js +55 -0
- package/src/ds-icon-button/ds-icon-button.js +224 -0
- package/src/ds-icon-button/ds-icon-button.mdx +131 -0
- package/src/ds-icon-button/ds-icon-button.stories.js +128 -0
- package/src/ds-icon-button/ds-icon-button.test.js +90 -0
- package/src/ds-icon-button/index.js +1 -0
- package/src/ds-input/ds-input.a11y.test.js +145 -0
- package/src/ds-input/ds-input.js +645 -0
- package/src/ds-input/ds-input.mdx +251 -0
- package/src/ds-input/ds-input.stories.js +298 -0
- package/src/ds-input/ds-input.test.js +792 -0
- package/src/ds-input/index.js +1 -0
- package/src/ds-link/ds-link.js +111 -0
- package/src/ds-link/ds-link.stories.js +56 -0
- package/src/ds-link/ds-link.test.js +74 -0
- package/src/ds-list-item/ds-list-item.a11y.test.js +39 -0
- package/src/ds-list-item/ds-list-item.js +292 -0
- package/src/ds-list-item/ds-list-item.stories.js +101 -0
- package/src/ds-list-item/ds-list-item.test.js +63 -0
- package/src/ds-menu/ds-menu.js +30 -0
- package/src/ds-menu/ds-menu.stories.js +120 -0
- package/src/ds-menu/ds-menu.test.js +123 -0
- package/src/ds-menu-group/ds-menu-group.js +101 -0
- package/src/ds-menu-group/ds-menu-group.stories.js +99 -0
- package/src/ds-nav-item/ds-nav-item.a11y.test.js +91 -0
- package/src/ds-nav-item/ds-nav-item.js +307 -0
- package/src/ds-nav-item/ds-nav-item.stories.js +99 -0
- package/src/ds-nav-item/ds-nav-item.test.js +169 -0
- package/src/ds-nav-item/index.js +1 -0
- package/src/ds-nav-vertical/ds-nav-vertical.a11y.test.js +69 -0
- package/src/ds-nav-vertical/ds-nav-vertical.js +173 -0
- package/src/ds-nav-vertical/ds-nav-vertical.stories.js +124 -0
- package/src/ds-nav-vertical/ds-nav-vertical.test.js +176 -0
- package/src/ds-nav-vertical/index.js +1 -0
- package/src/ds-pagination/ds-pagination.a11y.test.js +50 -0
- package/src/ds-pagination/ds-pagination.js +232 -0
- package/src/ds-pagination/ds-pagination.stories.js +63 -0
- package/src/ds-pagination/ds-pagination.test.js +141 -0
- package/src/ds-pagination/index.js +1 -0
- package/src/ds-progress-bar/ds-progress-bar.a11y.test.js +25 -0
- package/src/ds-progress-bar/ds-progress-bar.js +81 -0
- package/src/ds-progress-bar/ds-progress-bar.stories.js +69 -0
- package/src/ds-progress-bar/ds-progress-bar.test.js +60 -0
- package/src/ds-radio/ds-radio.a11y.test.js +69 -0
- package/src/ds-radio/ds-radio.js +240 -0
- package/src/ds-radio/ds-radio.stories.js +102 -0
- package/src/ds-radio/ds-radio.test.js +114 -0
- package/src/ds-radio/index.js +1 -0
- package/src/ds-radio-group/ds-radio-group.a11y.test.js +164 -0
- package/src/ds-radio-group/ds-radio-group.js +257 -0
- package/src/ds-radio-group/ds-radio-group.stories.js +247 -0
- package/src/ds-radio-group/ds-radio-group.test.js +194 -0
- package/src/ds-radio-group/index.js +1 -0
- package/src/ds-rich-list/ds-rich-list.js +246 -0
- package/src/ds-rich-list/ds-rich-list.stories.js +368 -0
- package/src/ds-rich-list/ds-rich-list.test.js +293 -0
- package/src/ds-rich-list-item/ds-rich-list-item.js +579 -0
- package/src/ds-rich-list-item/ds-rich-list-item.stories.js +197 -0
- package/src/ds-rich-list-item/ds-rich-list-item.test.js +434 -0
- package/src/ds-slider/ds-slider.js +399 -0
- package/src/ds-slider/ds-slider.stories.js +107 -0
- package/src/ds-slider/ds-slider.test.js +308 -0
- package/src/ds-spinner/ds-spinner.js +173 -0
- package/src/ds-spinner/ds-spinner.stories.js +52 -0
- package/src/ds-spinner/ds-spinner.test.js +50 -0
- package/src/ds-status-border/ds-status-border.js +88 -0
- package/src/ds-status-border/ds-status-border.stories.js +242 -0
- package/src/ds-status-border/ds-status-border.test.js +168 -0
- package/src/ds-stepper/ds-stepper.a11y.test.js +198 -0
- package/src/ds-stepper/ds-stepper.js +207 -0
- package/src/ds-stepper/ds-stepper.stories.js +530 -0
- package/src/ds-stepper/ds-stepper.test.js +311 -0
- package/src/ds-stepper-item/ds-stepper-item.js +485 -0
- package/src/ds-stepper-item/ds-stepper-item.stories.js +288 -0
- package/src/ds-switch/ds-switch.js +348 -0
- package/src/ds-switch/ds-switch.stories.js +145 -0
- package/src/ds-switch/ds-switch.test.js +226 -0
- package/src/ds-switch/index.js +1 -0
- package/src/ds-tab-item/ds-tab-item.js +341 -0
- package/src/ds-tab-item/ds-tab-item.stories.js +69 -0
- package/src/ds-tabs/ds-tab-panel.js +48 -0
- package/src/ds-tabs/ds-tabs.a11y.test.js +56 -0
- package/src/ds-tabs/ds-tabs.js +180 -0
- package/src/ds-tabs/ds-tabs.stories.js +152 -0
- package/src/ds-tabs/ds-tabs.test.js +306 -0
- package/src/ds-tabs/index.js +3 -0
- package/src/ds-tag-action/ds-tag-action.a11y.test.js +32 -0
- package/src/ds-tag-action/ds-tag-action.js +185 -0
- package/src/ds-tag-action/ds-tag-action.stories.js +55 -0
- package/src/ds-tag-action/ds-tag-action.test.js +44 -0
- package/src/ds-tag-removable/ds-tag-removable.a11y.test.js +24 -0
- package/src/ds-tag-removable/ds-tag-removable.js +146 -0
- package/src/ds-tag-removable/ds-tag-removable.stories.js +52 -0
- package/src/ds-tag-removable/ds-tag-removable.test.js +46 -0
- package/src/ds-tag-status/ds-tag-status.a11y.test.js +93 -0
- package/src/ds-tag-status/ds-tag-status.js +164 -0
- package/src/ds-tag-status/ds-tag-status.stories.js +200 -0
- package/src/ds-tag-status/ds-tag-status.test.js +140 -0
- package/src/ds-tag-status/index.js +1 -0
- package/src/ds-textarea/ds-textarea-clearable.test.js +89 -0
- package/src/ds-textarea/ds-textarea.a11y.test.js +66 -0
- package/src/ds-textarea/ds-textarea.js +505 -0
- package/src/ds-textarea/ds-textarea.stories.js +335 -0
- package/src/ds-textarea/ds-textarea.test.js +218 -0
- package/src/ds-textarea/index.js +1 -0
- package/src/ds-thumbnail/ds-thumbnail.js +207 -0
- package/src/ds-thumbnail/ds-thumbnail.stories.js +217 -0
- package/src/ds-thumbnail/ds-thumbnail.test.js +220 -0
- package/src/ds-toast/ds-toast-provider.js +110 -0
- package/src/ds-toast/ds-toast.a11y.test.js +34 -0
- package/src/ds-toast/ds-toast.js +243 -0
- package/src/ds-toast/ds-toast.stories.js +143 -0
- package/src/ds-toast/ds-toast.test.js +93 -0
- package/src/ds-toast/index.js +2 -0
- package/src/ds-tooltip/ds-tooltip.a11y.test.js +110 -0
- package/src/ds-tooltip/ds-tooltip.js +217 -0
- package/src/ds-tooltip/ds-tooltip.mdx +75 -0
- package/src/ds-tooltip/ds-tooltip.stories.js +72 -0
- package/src/ds-tooltip/ds-tooltip.test.js +191 -0
- package/src/ds-tooltip/index.js +1 -0
- package/src/ds-tooltip/positioner.js +117 -0
- package/src/index.js +50 -0
- package/src/mixins/field-label.mixin.js +113 -0
- package/src/mixins/field-message.mixin.js +66 -0
- package/src/token-provider/index.js +1 -0
- package/src/token-provider/token-provider.a11y.test.js +44 -0
- package/src/token-provider/token-provider.js +85 -0
- package/src/token-provider/token-provider.stories.js +105 -0
- package/src/token-provider/token-provider.test.js +134 -0
- package/src/utils/number-input.utils.js +42 -0
|
@@ -0,0 +1,441 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
|
|
2
|
+
import './ds-dialog.js';
|
|
3
|
+
|
|
4
|
+
describe('ds-dialog', () => {
|
|
5
|
+
let container;
|
|
6
|
+
|
|
7
|
+
beforeEach(() => {
|
|
8
|
+
container = document.createElement('div');
|
|
9
|
+
document.body.appendChild(container);
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
afterEach(() => {
|
|
13
|
+
container.remove();
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it('renders closed by default', async () => {
|
|
17
|
+
container.innerHTML = '<ds-dialog heading="Test Dialog"></ds-dialog>';
|
|
18
|
+
const el = container.querySelector('ds-dialog');
|
|
19
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
20
|
+
|
|
21
|
+
const dialog = el.shadowRoot.querySelector('dialog');
|
|
22
|
+
expect(dialog.hasAttribute('open')).toBe(false); // Native dialog behavior
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it('opens when open prop is set', async () => {
|
|
26
|
+
container.innerHTML = '<ds-dialog heading="Test Dialog" open></ds-dialog>';
|
|
27
|
+
const el = container.querySelector('ds-dialog');
|
|
28
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
29
|
+
|
|
30
|
+
const dialog = el.shadowRoot.querySelector('dialog');
|
|
31
|
+
expect(dialog.getAttribute('open')).toBe(''); // Native dialog reflects open attribute
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it('emits ds-close event on close button click even if prevent-close is true', async () => {
|
|
35
|
+
const el = document.createElement('ds-dialog');
|
|
36
|
+
el.open = true;
|
|
37
|
+
el.heading = "Test";
|
|
38
|
+
el.preventClose = true; // Set prevent-close
|
|
39
|
+
container.appendChild(el);
|
|
40
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
41
|
+
|
|
42
|
+
const closeSpy = vi.fn();
|
|
43
|
+
el.addEventListener('ds-close', closeSpy);
|
|
44
|
+
|
|
45
|
+
const closeBtn = el.shadowRoot.querySelector('ds-icon-button');
|
|
46
|
+
closeBtn.click();
|
|
47
|
+
|
|
48
|
+
expect(closeSpy).toHaveBeenCalled(); // Should still close
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it('does NOT emit ds-close event on backdrop click if prevent-close is true', async () => {
|
|
52
|
+
const el = document.createElement('ds-dialog');
|
|
53
|
+
el.open = true;
|
|
54
|
+
el.heading = "Test";
|
|
55
|
+
el.preventClose = true;
|
|
56
|
+
el.modal = true; // Ensure it is modal for backdrop test
|
|
57
|
+
container.appendChild(el);
|
|
58
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
59
|
+
|
|
60
|
+
const closeSpy = vi.fn();
|
|
61
|
+
el.addEventListener('ds-close', closeSpy);
|
|
62
|
+
|
|
63
|
+
const dialog = el.shadowRoot.querySelector('dialog');
|
|
64
|
+
// Simulate backdrop click (click on dialog element but outside content)
|
|
65
|
+
// Mock getBoundingClientRect to ensure click is outside
|
|
66
|
+
dialog.getBoundingClientRect = () => ({
|
|
67
|
+
top: 100, left: 100, width: 200, height: 200, bottom: 300, right: 300
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
dialog.click(); // Click event on dialog itself (backdrop)
|
|
71
|
+
// But coordinates need to be outside (0,0 is outside 100,100)
|
|
72
|
+
// Note: Manual click() doesn't set clientX/Y. We need to dispatch event manually.
|
|
73
|
+
|
|
74
|
+
const event = new MouseEvent('click', {
|
|
75
|
+
bubbles: true,
|
|
76
|
+
cancelable: true,
|
|
77
|
+
clientX: 0,
|
|
78
|
+
clientY: 0
|
|
79
|
+
});
|
|
80
|
+
dialog.dispatchEvent(event);
|
|
81
|
+
|
|
82
|
+
expect(closeSpy).not.toHaveBeenCalled();
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it('renders heading correctly', async () => {
|
|
86
|
+
container.innerHTML = '<ds-dialog heading="My Title" open></ds-dialog>';
|
|
87
|
+
const el = container.querySelector('ds-dialog');
|
|
88
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
89
|
+
|
|
90
|
+
const title = el.shadowRoot.querySelector('.header-title');
|
|
91
|
+
expect(title.textContent).toBe('My Title');
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it('renders footer slot content', async () => {
|
|
95
|
+
container.innerHTML = `
|
|
96
|
+
<ds-dialog open>
|
|
97
|
+
<div slot="footer" class="test-footer">Footer Content</div>
|
|
98
|
+
</ds-dialog>
|
|
99
|
+
`;
|
|
100
|
+
const el = container.querySelector('ds-dialog');
|
|
101
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
102
|
+
|
|
103
|
+
const footerSlot = el.shadowRoot.querySelector('slot[name="footer"]');
|
|
104
|
+
expect(footerSlot).toBeTruthy();
|
|
105
|
+
// In light DOM check
|
|
106
|
+
const footerContent = el.querySelector('.test-footer');
|
|
107
|
+
expect(footerContent).toBeTruthy();
|
|
108
|
+
});
|
|
109
|
+
it('switches from modal to non-modal while open', async () => {
|
|
110
|
+
const el = document.createElement('ds-dialog');
|
|
111
|
+
el.open = true;
|
|
112
|
+
el.modal = true;
|
|
113
|
+
el.heading = "Switch Test";
|
|
114
|
+
container.appendChild(el);
|
|
115
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
116
|
+
|
|
117
|
+
const dialog = el.shadowRoot.querySelector('dialog');
|
|
118
|
+
expect(dialog.getAttribute('open')).toBe(''); // Should be open
|
|
119
|
+
|
|
120
|
+
// Switch to non-modal
|
|
121
|
+
el.modal = false;
|
|
122
|
+
await new Promise(resolve => setTimeout(resolve, 100)); // Allow updated() to run
|
|
123
|
+
|
|
124
|
+
expect(dialog.getAttribute('open')).toBe(''); // Should still be open
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
// SIZE TESTS
|
|
128
|
+
it('renders size="sm" with correct width constraints', async () => {
|
|
129
|
+
const el = document.createElement('ds-dialog');
|
|
130
|
+
el.size = 'sm';
|
|
131
|
+
el.open = true;
|
|
132
|
+
el.heading = "Small Dialog";
|
|
133
|
+
container.appendChild(el);
|
|
134
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
135
|
+
|
|
136
|
+
expect(el.getAttribute('size')).toBe('sm');
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
it('renders size="md" by default', async () => {
|
|
140
|
+
const el = document.createElement('ds-dialog');
|
|
141
|
+
el.open = true;
|
|
142
|
+
el.heading = "Medium Dialog";
|
|
143
|
+
container.appendChild(el);
|
|
144
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
145
|
+
|
|
146
|
+
expect(el.size).toBe('md');
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
it('renders size="lg" with correct width', async () => {
|
|
150
|
+
const el = document.createElement('ds-dialog');
|
|
151
|
+
el.size = 'lg';
|
|
152
|
+
el.open = true;
|
|
153
|
+
el.heading = "Large Dialog";
|
|
154
|
+
container.appendChild(el);
|
|
155
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
156
|
+
|
|
157
|
+
expect(el.getAttribute('size')).toBe('lg');
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
it('renders size="fullscreen" with 100vh/vw', async () => {
|
|
161
|
+
const el = document.createElement('ds-dialog');
|
|
162
|
+
el.size = 'fullscreen';
|
|
163
|
+
el.open = true;
|
|
164
|
+
el.heading = "Fullscreen Dialog";
|
|
165
|
+
container.appendChild(el);
|
|
166
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
167
|
+
|
|
168
|
+
expect(el.getAttribute('size')).toBe('fullscreen');
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
// POSITION TESTS (non-modal)
|
|
172
|
+
it('renders non-modal at position="center" by default', async () => {
|
|
173
|
+
const el = document.createElement('ds-dialog');
|
|
174
|
+
el.open = true;
|
|
175
|
+
el.modal = false;
|
|
176
|
+
el.heading = "Centered";
|
|
177
|
+
container.appendChild(el);
|
|
178
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
179
|
+
|
|
180
|
+
expect(el.position).toBe('center');
|
|
181
|
+
const dialog = el.shadowRoot.querySelector('dialog');
|
|
182
|
+
expect(dialog.classList.contains('is-modal')).toBe(false);
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
it('renders non-modal at position="top-left"', async () => {
|
|
186
|
+
const el = document.createElement('ds-dialog');
|
|
187
|
+
el.open = true;
|
|
188
|
+
el.modal = false;
|
|
189
|
+
el.position = 'top-left';
|
|
190
|
+
el.heading = "Top Left";
|
|
191
|
+
container.appendChild(el);
|
|
192
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
193
|
+
|
|
194
|
+
expect(el.getAttribute('position')).toBe('top-left');
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
it('renders non-modal at position="top-right"', async () => {
|
|
198
|
+
const el = document.createElement('ds-dialog');
|
|
199
|
+
el.open = true;
|
|
200
|
+
el.modal = false;
|
|
201
|
+
el.position = 'top-right';
|
|
202
|
+
el.heading = "Top Right";
|
|
203
|
+
container.appendChild(el);
|
|
204
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
205
|
+
|
|
206
|
+
expect(el.getAttribute('position')).toBe('top-right');
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
it('renders non-modal at position="bottom-left"', async () => {
|
|
210
|
+
const el = document.createElement('ds-dialog');
|
|
211
|
+
el.open = true;
|
|
212
|
+
el.modal = false;
|
|
213
|
+
el.position = 'bottom-left';
|
|
214
|
+
el.heading = "Bottom Left";
|
|
215
|
+
container.appendChild(el);
|
|
216
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
217
|
+
|
|
218
|
+
expect(el.getAttribute('position')).toBe('bottom-left');
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
it('renders non-modal at position="bottom-right"', async () => {
|
|
222
|
+
const el = document.createElement('ds-dialog');
|
|
223
|
+
el.open = true;
|
|
224
|
+
el.modal = false;
|
|
225
|
+
el.position = 'bottom-right';
|
|
226
|
+
el.heading = "Bottom Right";
|
|
227
|
+
container.appendChild(el);
|
|
228
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
229
|
+
|
|
230
|
+
expect(el.getAttribute('position')).toBe('bottom-right');
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
// KEYBOARD TESTS
|
|
234
|
+
it('closes on Escape key (non-modal, prevent-close=false)', async () => {
|
|
235
|
+
const el = document.createElement('ds-dialog');
|
|
236
|
+
el.open = true;
|
|
237
|
+
el.modal = false;
|
|
238
|
+
el.heading = "Escape Test";
|
|
239
|
+
container.appendChild(el);
|
|
240
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
241
|
+
|
|
242
|
+
const closeSpy = vi.fn();
|
|
243
|
+
el.addEventListener('ds-close', closeSpy);
|
|
244
|
+
|
|
245
|
+
const dialog = el.shadowRoot.querySelector('dialog');
|
|
246
|
+
const event = new KeyboardEvent('keydown', { key: 'Escape', bubbles: true });
|
|
247
|
+
dialog.dispatchEvent(event);
|
|
248
|
+
|
|
249
|
+
expect(closeSpy).toHaveBeenCalled();
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
it('does NOT close on Escape if prevent-close=true', async () => {
|
|
253
|
+
const el = document.createElement('ds-dialog');
|
|
254
|
+
el.open = true;
|
|
255
|
+
el.modal = false;
|
|
256
|
+
el.preventClose = true;
|
|
257
|
+
el.heading = "Escape Prevented";
|
|
258
|
+
container.appendChild(el);
|
|
259
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
260
|
+
|
|
261
|
+
const closeSpy = vi.fn();
|
|
262
|
+
el.addEventListener('ds-close', closeSpy);
|
|
263
|
+
|
|
264
|
+
const dialog = el.shadowRoot.querySelector('dialog');
|
|
265
|
+
const event = new KeyboardEvent('keydown', { key: 'Escape', bubbles: true });
|
|
266
|
+
dialog.dispatchEvent(event);
|
|
267
|
+
|
|
268
|
+
expect(closeSpy).not.toHaveBeenCalled();
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
it('traps Tab focus in modal mode', async () => {
|
|
272
|
+
container.innerHTML = `
|
|
273
|
+
<ds-dialog open modal heading="Focus Trap">
|
|
274
|
+
<button id="btn1">Button 1</button>
|
|
275
|
+
<button id="btn2">Button 2</button>
|
|
276
|
+
</ds-dialog>
|
|
277
|
+
`;
|
|
278
|
+
const el = container.querySelector('ds-dialog');
|
|
279
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
280
|
+
|
|
281
|
+
const btn1 = el.querySelector('#btn1');
|
|
282
|
+
const btn2 = el.querySelector('#btn2');
|
|
283
|
+
|
|
284
|
+
// Tab trap should be active
|
|
285
|
+
expect(el.modal).toBe(true);
|
|
286
|
+
expect(btn1).toBeTruthy();
|
|
287
|
+
expect(btn2).toBeTruthy();
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
it('cycles Tab forward through focusables in modal', async () => {
|
|
291
|
+
container.innerHTML = `
|
|
292
|
+
<ds-dialog open modal heading="Forward Tab">
|
|
293
|
+
<button id="first">First</button>
|
|
294
|
+
<button id="last">Last</button>
|
|
295
|
+
</ds-dialog>
|
|
296
|
+
`;
|
|
297
|
+
const el = container.querySelector('ds-dialog');
|
|
298
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
299
|
+
|
|
300
|
+
const dialog = el.shadowRoot.querySelector('dialog');
|
|
301
|
+
const lastBtn = el.querySelector('#last');
|
|
302
|
+
|
|
303
|
+
// Simulate Tab on last element
|
|
304
|
+
lastBtn.focus();
|
|
305
|
+
const event = new KeyboardEvent('keydown', { key: 'Tab', bubbles: true });
|
|
306
|
+
dialog.dispatchEvent(event);
|
|
307
|
+
|
|
308
|
+
// Should cycle (preventDefault should have been called)
|
|
309
|
+
expect(el.modal).toBe(true);
|
|
310
|
+
});
|
|
311
|
+
|
|
312
|
+
it('cycles Shift+Tab backwards in modal', async () => {
|
|
313
|
+
container.innerHTML = `
|
|
314
|
+
<ds-dialog open modal heading="Backward Tab">
|
|
315
|
+
<button id="first">First</button>
|
|
316
|
+
<button id="last">Last</button>
|
|
317
|
+
</ds-dialog>
|
|
318
|
+
`;
|
|
319
|
+
const el = container.querySelector('ds-dialog');
|
|
320
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
321
|
+
|
|
322
|
+
const dialog = el.shadowRoot.querySelector('dialog');
|
|
323
|
+
const firstBtn = el.querySelector('#first');
|
|
324
|
+
|
|
325
|
+
// Simulate Shift+Tab on first element
|
|
326
|
+
firstBtn.focus();
|
|
327
|
+
const event = new KeyboardEvent('keydown', { key: 'Tab', shiftKey: true, bubbles: true });
|
|
328
|
+
dialog.dispatchEvent(event);
|
|
329
|
+
|
|
330
|
+
expect(el.modal).toBe(true);
|
|
331
|
+
});
|
|
332
|
+
|
|
333
|
+
// SLOT TESTS
|
|
334
|
+
it('renders custom header slot (overrides heading prop)', async () => {
|
|
335
|
+
container.innerHTML = `
|
|
336
|
+
<ds-dialog open>
|
|
337
|
+
<div slot="header" id="custom-header">Custom Header Content</div>
|
|
338
|
+
</ds-dialog>
|
|
339
|
+
`;
|
|
340
|
+
const el = container.querySelector('ds-dialog');
|
|
341
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
342
|
+
|
|
343
|
+
const customHeader = el.querySelector('#custom-header');
|
|
344
|
+
expect(customHeader).toBeTruthy();
|
|
345
|
+
expect(customHeader.textContent).toBe('Custom Header Content');
|
|
346
|
+
});
|
|
347
|
+
|
|
348
|
+
it('renders body slot content', async () => {
|
|
349
|
+
container.innerHTML = `
|
|
350
|
+
<ds-dialog open>
|
|
351
|
+
<div slot="body" id="custom-body">Custom Body</div>
|
|
352
|
+
</ds-dialog>
|
|
353
|
+
`;
|
|
354
|
+
const el = container.querySelector('ds-dialog');
|
|
355
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
356
|
+
|
|
357
|
+
const customBody = el.querySelector('#custom-body');
|
|
358
|
+
expect(customBody).toBeTruthy();
|
|
359
|
+
});
|
|
360
|
+
|
|
361
|
+
// SCROLL LOCK TESTS
|
|
362
|
+
it('locks body scroll when modal=true opens', async () => {
|
|
363
|
+
const el = document.createElement('ds-dialog');
|
|
364
|
+
el.modal = true;
|
|
365
|
+
el.heading = "Scroll Lock Test";
|
|
366
|
+
container.appendChild(el);
|
|
367
|
+
|
|
368
|
+
// Initially no lock
|
|
369
|
+
expect(document.body.style.overflow).toBe('');
|
|
370
|
+
|
|
371
|
+
el.open = true;
|
|
372
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
373
|
+
|
|
374
|
+
expect(document.body.style.overflow).toBe('hidden');
|
|
375
|
+
});
|
|
376
|
+
|
|
377
|
+
it('unlocks body scroll when modal dialog closes', async () => {
|
|
378
|
+
const el = document.createElement('ds-dialog');
|
|
379
|
+
el.modal = true;
|
|
380
|
+
el.open = true;
|
|
381
|
+
el.heading = "Unlock Test";
|
|
382
|
+
container.appendChild(el);
|
|
383
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
384
|
+
|
|
385
|
+
expect(document.body.style.overflow).toBe('hidden');
|
|
386
|
+
|
|
387
|
+
el.open = false;
|
|
388
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
389
|
+
|
|
390
|
+
expect(document.body.style.overflow).toBe('');
|
|
391
|
+
});
|
|
392
|
+
|
|
393
|
+
it('unlocks scroll on component disconnect', async () => {
|
|
394
|
+
const el = document.createElement('ds-dialog');
|
|
395
|
+
el.modal = true;
|
|
396
|
+
el.open = true;
|
|
397
|
+
el.heading = "Disconnect Test";
|
|
398
|
+
container.appendChild(el);
|
|
399
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
400
|
+
|
|
401
|
+
expect(document.body.style.overflow).toBe('hidden');
|
|
402
|
+
|
|
403
|
+
container.removeChild(el);
|
|
404
|
+
await new Promise(resolve => setTimeout(resolve, 50));
|
|
405
|
+
|
|
406
|
+
expect(document.body.style.overflow).toBe('');
|
|
407
|
+
});
|
|
408
|
+
|
|
409
|
+
// EDGE CASES
|
|
410
|
+
it('switches from non-modal to modal while open', async () => {
|
|
411
|
+
const el = document.createElement('ds-dialog');
|
|
412
|
+
el.open = true;
|
|
413
|
+
el.modal = false;
|
|
414
|
+
el.heading = "Modal Switch";
|
|
415
|
+
container.appendChild(el);
|
|
416
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
417
|
+
|
|
418
|
+
const dialog = el.shadowRoot.querySelector('dialog');
|
|
419
|
+
expect(dialog.classList.contains('is-modal')).toBe(false);
|
|
420
|
+
|
|
421
|
+
el.modal = true;
|
|
422
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
423
|
+
|
|
424
|
+
expect(dialog.classList.contains('is-modal')).toBe(true);
|
|
425
|
+
});
|
|
426
|
+
|
|
427
|
+
it('emits ds-open event when opened', async () => {
|
|
428
|
+
const el = document.createElement('ds-dialog');
|
|
429
|
+
el.heading = "Open Event Test";
|
|
430
|
+
container.appendChild(el);
|
|
431
|
+
await new Promise(resolve => setTimeout(resolve, 50));
|
|
432
|
+
|
|
433
|
+
const openSpy = vi.fn();
|
|
434
|
+
el.addEventListener('ds-open', openSpy);
|
|
435
|
+
|
|
436
|
+
el.open = true;
|
|
437
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
438
|
+
|
|
439
|
+
expect(openSpy).toHaveBeenCalled();
|
|
440
|
+
});
|
|
441
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './ds-dialog.js';
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
|
|
2
|
+
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
3
|
+
import axe from 'axe-core';
|
|
4
|
+
import './ds-dropdown.js';
|
|
5
|
+
|
|
6
|
+
describe('ds-dropdown accessibility', () => {
|
|
7
|
+
let container;
|
|
8
|
+
|
|
9
|
+
beforeEach(() => {
|
|
10
|
+
container = document.createElement('div');
|
|
11
|
+
document.body.appendChild(container);
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
afterEach(() => {
|
|
15
|
+
container.remove();
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
async function runAxe(html) {
|
|
19
|
+
container.innerHTML = html;
|
|
20
|
+
const element = container.querySelector('ds-dropdown');
|
|
21
|
+
await new Promise(resolve => setTimeout(resolve, 150));
|
|
22
|
+
|
|
23
|
+
const results = await axe.run(container);
|
|
24
|
+
if (results.violations.length > 0) {
|
|
25
|
+
console.log('Axe Violations:', JSON.stringify(results.violations, null, 2));
|
|
26
|
+
}
|
|
27
|
+
return results;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
it('should pass axe accessibility checks for default state', async () => {
|
|
31
|
+
const results = await runAxe(`
|
|
32
|
+
<ds-dropdown label="Country">
|
|
33
|
+
<ds-list-item value="pt" label="Portugal"></ds-list-item>
|
|
34
|
+
<ds-list-item value="es" label="Spain"></ds-list-item>
|
|
35
|
+
</ds-dropdown>
|
|
36
|
+
`);
|
|
37
|
+
expect(results.violations).toHaveLength(0);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it('should pass axe accessibility checks with value', async () => {
|
|
41
|
+
const results = await runAxe(`
|
|
42
|
+
<ds-dropdown label="Country" value="pt">
|
|
43
|
+
<ds-list-item value="pt" label="Portugal" selected></ds-list-item>
|
|
44
|
+
<ds-list-item value="es" label="Spain"></ds-list-item>
|
|
45
|
+
</ds-dropdown>
|
|
46
|
+
`);
|
|
47
|
+
expect(results.violations).toHaveLength(0);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it('should pass axe accessibility checks for disabled state', async () => {
|
|
51
|
+
const results = await runAxe(`
|
|
52
|
+
<ds-dropdown label="Country" disabled>
|
|
53
|
+
<ds-list-item value="pt" label="Portugal"></ds-list-item>
|
|
54
|
+
</ds-dropdown>
|
|
55
|
+
`);
|
|
56
|
+
expect(results.violations).toHaveLength(0);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('should pass axe accessibility checks for required field', async () => {
|
|
60
|
+
const results = await runAxe(`
|
|
61
|
+
<ds-dropdown label="Country" required>
|
|
62
|
+
<ds-list-item value="pt" label="Portugal"></ds-list-item>
|
|
63
|
+
</ds-dropdown>
|
|
64
|
+
`);
|
|
65
|
+
expect(results.violations).toHaveLength(0);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it('should pass axe accessibility checks for error state', async () => {
|
|
69
|
+
const results = await runAxe(`
|
|
70
|
+
<ds-dropdown
|
|
71
|
+
label="Country"
|
|
72
|
+
validation-status="error"
|
|
73
|
+
validation-message="This field is required"
|
|
74
|
+
>
|
|
75
|
+
<ds-list-item value="pt" label="Portugal"></ds-list-item>
|
|
76
|
+
</ds-dropdown>
|
|
77
|
+
`);
|
|
78
|
+
expect(results.violations).toHaveLength(0);
|
|
79
|
+
});
|
|
80
|
+
});
|