@classic-homes/theme-svelte 1.22.0 → 1.22.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.
Files changed (77) hide show
  1. package/dist/lib/components/Alert.svelte +8 -10
  2. package/dist/lib/components/DataPanel.svelte +19 -5
  3. package/dist/lib/components/HeaderSearch.svelte +18 -10
  4. package/dist/lib/components/Skeleton.svelte.d.ts +1 -1
  5. package/dist/lib/components/layout/AppShell.svelte +1 -1
  6. package/dist/lib/components/layout/DashboardLayout.svelte +15 -6
  7. package/dist/lib/components/layout/Footer.svelte +6 -6
  8. package/dist/lib/components/layout/FormPageLayout.svelte +4 -3
  9. package/dist/lib/components/layout/Sidebar.svelte +25 -21
  10. package/dist/lib/components/media/Image.svelte.d.ts +1 -1
  11. package/dist/lib/components/media/Lightbox.svelte +13 -9
  12. package/dist/lib/components/media/MediaCarousel.svelte +5 -0
  13. package/dist/lib/components/media/MediaGrid.svelte +4 -1
  14. package/dist/lib/components/notifications/NotificationDialog.svelte +8 -5
  15. package/dist/lib/components/notifications/NotificationItem.svelte +16 -6
  16. package/dist/lib/components/studio/InsertPalette.svelte +4 -1
  17. package/dist/lib/components/studio/StudioCanvas.svelte +23 -2
  18. package/dist/lib/components/studio/StudioCanvas.svelte.d.ts +5 -1
  19. package/dist/lib/composables/useFocusTrap.svelte.js +9 -2
  20. package/dist/lib/notices/sanitize.js +10 -3
  21. package/dist/lib/notices/store.d.ts +3 -3
  22. package/dist/lib/notices/store.js +14 -2
  23. package/dist/lib/notices/types.d.ts +8 -8
  24. package/dist/lib/notices/types.js +5 -1
  25. package/dist/lib/stores/sidebar.svelte.d.ts +6 -0
  26. package/dist/lib/stores/sidebar.svelte.js +61 -26
  27. package/dist/lib/utils/media.js +4 -3
  28. package/dist/lib/utils/url.d.ts +10 -0
  29. package/dist/lib/utils/url.js +21 -0
  30. package/dist/lib/utils.js +14 -1
  31. package/package.json +5 -4
  32. package/dist/__tests__/FileUpload.test.js +0 -45
  33. package/dist/__tests__/a11y/Alert.a11y.test.js +0 -84
  34. package/dist/__tests__/a11y/AlertDialog.a11y.test.js +0 -126
  35. package/dist/__tests__/a11y/Button.a11y.test.js +0 -66
  36. package/dist/__tests__/a11y/Card.a11y.test.js +0 -77
  37. package/dist/__tests__/a11y/Combobox.a11y.test.js +0 -209
  38. package/dist/__tests__/a11y/Dialog.a11y.test.js +0 -103
  39. package/dist/__tests__/a11y/DropdownMenu.a11y.test.js +0 -132
  40. package/dist/__tests__/a11y/FileUpload.a11y.test.js +0 -158
  41. package/dist/__tests__/a11y/FilterToolbar.a11y.test.js +0 -62
  42. package/dist/__tests__/a11y/Icon.a11y.test.js +0 -119
  43. package/dist/__tests__/a11y/Input.a11y.test.js +0 -96
  44. package/dist/__tests__/a11y/LiveRegion.a11y.test.js +0 -102
  45. package/dist/__tests__/a11y/Media.a11y.test.js +0 -145
  46. package/dist/__tests__/a11y/PublicLayout.a11y.test.js +0 -139
  47. package/dist/__tests__/a11y/SegmentedControl.a11y.test.js +0 -50
  48. package/dist/__tests__/a11y/Switch.a11y.test.js +0 -95
  49. package/dist/__tests__/a11y/Tabs.a11y.test.js +0 -116
  50. package/dist/__tests__/a11y/Textarea.a11y.test.js +0 -111
  51. package/dist/__tests__/a11y/Toolbar.a11y.test.js +0 -31
  52. package/dist/__tests__/a11y/ViewToggle.a11y.test.js +0 -40
  53. package/dist/__tests__/action-header.test.js +0 -150
  54. package/dist/__tests__/alert-dialog-dismiss.test.js +0 -54
  55. package/dist/__tests__/code-field.test.js +0 -65
  56. package/dist/__tests__/control-text-color.test.js +0 -29
  57. package/dist/__tests__/data-panel-card-gestures.test.js +0 -138
  58. package/dist/__tests__/data-panel-viewport.test.js +0 -45
  59. package/dist/__tests__/dropdown-trigger.test.js +0 -39
  60. package/dist/__tests__/header-search.test.js +0 -90
  61. package/dist/__tests__/media-components.test.js +0 -206
  62. package/dist/__tests__/media-hls.test.js +0 -273
  63. package/dist/__tests__/media-matterport.test.js +0 -296
  64. package/dist/__tests__/media-players.test.js +0 -244
  65. package/dist/__tests__/media-scrubber.test.js +0 -44
  66. package/dist/__tests__/media-utils.test.js +0 -427
  67. package/dist/__tests__/nav-access.test.js +0 -145
  68. package/dist/__tests__/page-header.test.js +0 -36
  69. package/dist/__tests__/public-layout-drawer.test.js +0 -116
  70. package/dist/__tests__/setup.js +0 -56
  71. package/dist/__tests__/sidebar-collapse.test.js +0 -58
  72. package/dist/__tests__/sidebar-docs-parity.test.js +0 -134
  73. package/dist/__tests__/sidebar-expansion.test.js +0 -59
  74. package/dist/__tests__/sidebar-items.test.js +0 -59
  75. package/dist/__tests__/sidebar-mobile.test.js +0 -86
  76. package/dist/__tests__/studio-canvas-item.svelte.test.js +0 -98
  77. package/dist/__tests__/studio.test.js +0 -240
@@ -1,126 +0,0 @@
1
- import { describe, it, expect } from 'vitest';
2
- import { screen } from '@testing-library/svelte';
3
- import userEvent from '@testing-library/user-event';
4
- import { axe } from 'vitest-axe';
5
- describe('AlertDialog accessibility', () => {
6
- it('should have no axe violations when open', async () => {
7
- const container = document.createElement('div');
8
- document.body.appendChild(container);
9
- container.innerHTML = `
10
- <div role="alertdialog" aria-modal="true" aria-labelledby="alertdialog-title" aria-describedby="alertdialog-desc" class="fixed z-50 bg-background p-6 shadow-lg">
11
- <h2 id="alertdialog-title" class="text-lg font-semibold">Confirm Action</h2>
12
- <p id="alertdialog-desc" class="text-sm text-muted-foreground">Are you sure you want to proceed?</p>
13
- <div>
14
- <button type="button">Cancel</button>
15
- <button type="button">Continue</button>
16
- </div>
17
- </div>
18
- `;
19
- const results = await axe(container);
20
- expect(results).toHaveNoViolations();
21
- document.body.removeChild(container);
22
- });
23
- it('should have role="alertdialog"', () => {
24
- const container = document.createElement('div');
25
- document.body.appendChild(container);
26
- container.innerHTML = `
27
- <div role="alertdialog" aria-modal="true" aria-labelledby="alertdialog-title">
28
- <h2 id="alertdialog-title">Confirm Action</h2>
29
- <button type="button">Cancel</button>
30
- <button type="button">Continue</button>
31
- </div>
32
- `;
33
- const dialog = screen.getByRole('alertdialog');
34
- expect(dialog).toBeInTheDocument();
35
- document.body.removeChild(container);
36
- });
37
- it('should have aria-modal="true"', () => {
38
- const container = document.createElement('div');
39
- document.body.appendChild(container);
40
- container.innerHTML = `
41
- <div role="alertdialog" aria-modal="true" aria-labelledby="alertdialog-title">
42
- <h2 id="alertdialog-title">Confirm Action</h2>
43
- <button type="button">Cancel</button>
44
- <button type="button">Continue</button>
45
- </div>
46
- `;
47
- const dialog = screen.getByRole('alertdialog');
48
- expect(dialog).toHaveAttribute('aria-modal', 'true');
49
- document.body.removeChild(container);
50
- });
51
- it('should have accessible title', () => {
52
- const container = document.createElement('div');
53
- document.body.appendChild(container);
54
- container.innerHTML = `
55
- <div role="alertdialog" aria-modal="true" aria-labelledby="alertdialog-title">
56
- <h2 id="alertdialog-title">Delete Item</h2>
57
- <button type="button">Cancel</button>
58
- <button type="button">Delete</button>
59
- </div>
60
- `;
61
- expect(screen.getByText('Delete Item')).toBeInTheDocument();
62
- document.body.removeChild(container);
63
- });
64
- it('should have accessible description', () => {
65
- const container = document.createElement('div');
66
- document.body.appendChild(container);
67
- container.innerHTML = `
68
- <div role="alertdialog" aria-modal="true" aria-labelledby="alertdialog-title" aria-describedby="alertdialog-desc">
69
- <h2 id="alertdialog-title">Delete Item</h2>
70
- <p id="alertdialog-desc">This action cannot be undone.</p>
71
- <button type="button">Cancel</button>
72
- <button type="button">Delete</button>
73
- </div>
74
- `;
75
- expect(screen.getByText('This action cannot be undone.')).toBeInTheDocument();
76
- document.body.removeChild(container);
77
- });
78
- it('should have cancel and confirm buttons', () => {
79
- const container = document.createElement('div');
80
- document.body.appendChild(container);
81
- container.innerHTML = `
82
- <div role="alertdialog" aria-modal="true" aria-labelledby="alertdialog-title">
83
- <h2 id="alertdialog-title">Confirm Action</h2>
84
- <button type="button">Cancel</button>
85
- <button type="button">Confirm</button>
86
- </div>
87
- `;
88
- expect(screen.getByRole('button', { name: 'Cancel' })).toBeInTheDocument();
89
- expect(screen.getByRole('button', { name: 'Confirm' })).toBeInTheDocument();
90
- document.body.removeChild(container);
91
- });
92
- it('should be keyboard accessible', async () => {
93
- const user = userEvent.setup();
94
- const container = document.createElement('div');
95
- document.body.appendChild(container);
96
- container.innerHTML = `
97
- <div role="alertdialog" aria-modal="true" aria-labelledby="alertdialog-title">
98
- <h2 id="alertdialog-title">Confirm Action</h2>
99
- <button type="button" id="cancel-btn">Cancel</button>
100
- <button type="button" id="confirm-btn">Confirm</button>
101
- </div>
102
- `;
103
- const cancelBtn = container.querySelector('#cancel-btn');
104
- const confirmBtn = container.querySelector('#confirm-btn');
105
- cancelBtn.focus();
106
- expect(cancelBtn).toHaveFocus();
107
- await user.tab();
108
- expect(confirmBtn).toHaveFocus();
109
- document.body.removeChild(container);
110
- });
111
- it('should have labeled buttons that describe the action', () => {
112
- const container = document.createElement('div');
113
- document.body.appendChild(container);
114
- container.innerHTML = `
115
- <div role="alertdialog" aria-modal="true" aria-labelledby="alertdialog-title" aria-describedby="alertdialog-desc">
116
- <h2 id="alertdialog-title">Delete Account</h2>
117
- <p id="alertdialog-desc">This will permanently delete your account and all associated data.</p>
118
- <button type="button">Cancel</button>
119
- <button type="button">Delete Account</button>
120
- </div>
121
- `;
122
- const deleteBtn = screen.getByRole('button', { name: 'Delete Account' });
123
- expect(deleteBtn).toBeInTheDocument();
124
- document.body.removeChild(container);
125
- });
126
- });
@@ -1,66 +0,0 @@
1
- import { describe, it, expect } from 'vitest';
2
- import { render, screen } from '@testing-library/svelte';
3
- import userEvent from '@testing-library/user-event';
4
- import { axe } from 'vitest-axe';
5
- import { createRawSnippet } from 'svelte';
6
- import Button from '../../lib/components/Button.svelte';
7
- function textSnippet(text) {
8
- return createRawSnippet(() => ({
9
- render: () => `<span>${text}</span>`,
10
- }));
11
- }
12
- describe('Button accessibility', () => {
13
- it('should have no axe violations', async () => {
14
- const { container } = render(Button, {
15
- props: { children: textSnippet('Click me') },
16
- });
17
- const results = await axe(container);
18
- expect(results).toHaveNoViolations();
19
- });
20
- it('should have no axe violations with different variants', async () => {
21
- const { container } = render(Button, {
22
- props: { variant: 'default', children: textSnippet('Default') },
23
- });
24
- expect(await axe(container)).toHaveNoViolations();
25
- const { container: container2 } = render(Button, {
26
- props: { variant: 'secondary', children: textSnippet('Secondary') },
27
- });
28
- expect(await axe(container2)).toHaveNoViolations();
29
- const { container: container3 } = render(Button, {
30
- props: { variant: 'destructive', children: textSnippet('Destructive') },
31
- });
32
- expect(await axe(container3)).toHaveNoViolations();
33
- });
34
- it('should be focusable', async () => {
35
- render(Button, {
36
- props: { children: textSnippet('Click me') },
37
- });
38
- const button = screen.getByRole('button');
39
- await userEvent.tab();
40
- expect(button).toHaveFocus();
41
- });
42
- it('should be activatable with keyboard', async () => {
43
- let clicked = false;
44
- render(Button, {
45
- props: {
46
- children: textSnippet('Click me'),
47
- onclick: () => {
48
- clicked = true;
49
- },
50
- },
51
- });
52
- await userEvent.tab();
53
- await userEvent.keyboard('{Enter}');
54
- expect(clicked).toBe(true);
55
- });
56
- it('should not be focusable when disabled', async () => {
57
- render(Button, {
58
- props: {
59
- disabled: true,
60
- children: textSnippet('Disabled'),
61
- },
62
- });
63
- const button = screen.getByRole('button');
64
- expect(button).toBeDisabled();
65
- });
66
- });
@@ -1,77 +0,0 @@
1
- import { describe, it, expect } from 'vitest';
2
- import { screen } from '@testing-library/svelte';
3
- import { axe } from 'vitest-axe';
4
- describe('Card accessibility', () => {
5
- it('should have no axe violations with basic card structure', async () => {
6
- // Create a card structure manually for testing
7
- const container = document.createElement('div');
8
- document.body.appendChild(container);
9
- container.innerHTML = `
10
- <div class="rounded-md bg-card text-card-foreground border border-border">
11
- <div class="flex flex-col space-y-1.5 p-6">
12
- <h3 class="text-2xl font-semibold leading-none tracking-tight">Card Title</h3>
13
- <p class="text-sm text-muted-foreground">Card description text</p>
14
- </div>
15
- <div class="p-6 pt-0">Card content goes here</div>
16
- <div class="flex items-center p-6 pt-0">Footer content</div>
17
- </div>
18
- `;
19
- const results = await axe(container);
20
- expect(results).toHaveNoViolations();
21
- document.body.removeChild(container);
22
- });
23
- it('should have no axe violations with different heading levels', async () => {
24
- const container = document.createElement('main');
25
- document.body.appendChild(container);
26
- container.innerHTML = `
27
- <h1>Page Title</h1>
28
- <section>
29
- <h2>Section Title</h2>
30
- <div class="rounded-md bg-card text-card-foreground border border-border">
31
- <h3 class="text-2xl font-semibold leading-none tracking-tight">Card Title</h3>
32
- <p>Card content</p>
33
- </div>
34
- </section>
35
- `;
36
- const results = await axe(container);
37
- expect(results).toHaveNoViolations();
38
- document.body.removeChild(container);
39
- });
40
- });
41
- describe('CardTitle heading levels', () => {
42
- it('should support configurable heading level via as prop', async () => {
43
- // Import the actual component to verify the API exists
44
- await import('../../lib/components/CardTitle.svelte');
45
- // Verify the component exists and can be rendered
46
- // We test structure with manual HTML since snippets don't work well in tests
47
- const container = document.createElement('div');
48
- document.body.appendChild(container);
49
- // Test h2 level structure
50
- container.innerHTML = `<h2 class="text-2xl font-semibold leading-none tracking-tight">H2 Title</h2>`;
51
- let heading = screen.getByRole('heading', { level: 2 });
52
- expect(heading).toBeInTheDocument();
53
- expect(heading).toHaveTextContent('H2 Title');
54
- // Clean up and test h3 level
55
- container.innerHTML = `<h3 class="text-2xl font-semibold leading-none tracking-tight">H3 Title</h3>`;
56
- heading = screen.getByRole('heading', { level: 3 });
57
- expect(heading).toBeInTheDocument();
58
- expect(heading).toHaveTextContent('H3 Title');
59
- // Test h4 level
60
- container.innerHTML = `<h4 class="text-2xl font-semibold leading-none tracking-tight">H4 Title</h4>`;
61
- heading = screen.getByRole('heading', { level: 4 });
62
- expect(heading).toBeInTheDocument();
63
- expect(heading).toHaveTextContent('H4 Title');
64
- document.body.removeChild(container);
65
- });
66
- it('should have no axe violations at any heading level', async () => {
67
- const container = document.createElement('div');
68
- document.body.appendChild(container);
69
- // Test each heading level
70
- for (const level of [1, 2, 3, 4, 5, 6]) {
71
- container.innerHTML = `<h${level} class="text-2xl font-semibold leading-none tracking-tight">Heading Level ${level}</h${level}>`;
72
- const results = await axe(container);
73
- expect(results).toHaveNoViolations();
74
- }
75
- document.body.removeChild(container);
76
- });
77
- });
@@ -1,209 +0,0 @@
1
- import { describe, it, expect } from 'vitest';
2
- import { render, screen, waitFor } from '@testing-library/svelte';
3
- import userEvent from '@testing-library/user-event';
4
- import { axe } from 'vitest-axe';
5
- import Combobox from '../../lib/components/Combobox.svelte';
6
- describe('Combobox accessibility', () => {
7
- const options = [
8
- { value: 'apple', label: 'Apple' },
9
- { value: 'banana', label: 'Banana' },
10
- { value: 'cherry', label: 'Cherry' },
11
- { value: 'date', label: 'Date' },
12
- ];
13
- it('should have no axe violations when closed', async () => {
14
- const { container } = render(Combobox, {
15
- props: {
16
- options,
17
- label: 'Select a fruit',
18
- },
19
- });
20
- const results = await axe(container);
21
- expect(results).toHaveNoViolations();
22
- });
23
- it('should open dropdown when clicked', async () => {
24
- render(Combobox, {
25
- props: {
26
- options,
27
- label: 'Select a fruit',
28
- },
29
- });
30
- // Open the combobox
31
- const input = screen.getByRole('combobox');
32
- await userEvent.click(input);
33
- // Wait for dropdown to appear
34
- await waitFor(() => {
35
- expect(screen.getByRole('listbox')).toBeInTheDocument();
36
- });
37
- // Should have options in listbox
38
- const listOptions = screen.getAllByRole('option');
39
- expect(listOptions.length).toBe(options.length);
40
- });
41
- it('should have proper combobox role', () => {
42
- render(Combobox, {
43
- props: {
44
- options,
45
- label: 'Select a fruit',
46
- },
47
- });
48
- const combobox = screen.getByRole('combobox');
49
- expect(combobox).toBeInTheDocument();
50
- });
51
- it('should be keyboard navigable', async () => {
52
- render(Combobox, {
53
- props: {
54
- options,
55
- label: 'Select a fruit',
56
- },
57
- });
58
- const combobox = screen.getByRole('combobox');
59
- await userEvent.tab();
60
- expect(combobox).toHaveFocus();
61
- });
62
- it('should open on focus', async () => {
63
- render(Combobox, {
64
- props: {
65
- options,
66
- label: 'Select a fruit',
67
- },
68
- });
69
- const combobox = screen.getByRole('combobox');
70
- await userEvent.click(combobox);
71
- await waitFor(() => {
72
- expect(screen.getByRole('listbox')).toBeInTheDocument();
73
- });
74
- });
75
- it('should have aria-invalid when error is present', () => {
76
- render(Combobox, {
77
- props: {
78
- options,
79
- label: 'Select a fruit',
80
- error: 'This field is required',
81
- },
82
- });
83
- const combobox = screen.getByRole('combobox');
84
- expect(combobox).toHaveAttribute('aria-invalid', 'true');
85
- });
86
- it('should have aria-describedby pointing to error', () => {
87
- render(Combobox, {
88
- props: {
89
- options,
90
- label: 'Select a fruit',
91
- id: 'fruit-select',
92
- error: 'Please select a fruit',
93
- },
94
- });
95
- const combobox = screen.getByRole('combobox');
96
- expect(combobox).toHaveAttribute('aria-describedby', expect.stringContaining('fruit-select-error'));
97
- const errorMessage = document.getElementById('fruit-select-error');
98
- expect(errorMessage).toHaveTextContent('Please select a fruit');
99
- });
100
- it('error should have role="alert"', () => {
101
- render(Combobox, {
102
- props: {
103
- options,
104
- label: 'Select a fruit',
105
- error: 'Selection required',
106
- },
107
- });
108
- const alert = screen.getByRole('alert');
109
- expect(alert).toHaveTextContent('Selection required');
110
- });
111
- it('should have LiveRegion for screen reader announcements', async () => {
112
- const { container } = render(Combobox, {
113
- props: {
114
- options,
115
- label: 'Select a fruit',
116
- },
117
- });
118
- // The LiveRegion component should be rendered
119
- const liveRegion = container.querySelector('[aria-live]');
120
- expect(liveRegion).toBeInTheDocument();
121
- });
122
- it('should announce number of results when filtering', async () => {
123
- const { container } = render(Combobox, {
124
- props: {
125
- options,
126
- label: 'Select a fruit',
127
- },
128
- });
129
- const combobox = screen.getByRole('combobox');
130
- await userEvent.click(combobox);
131
- // Wait for dropdown
132
- await waitFor(() => {
133
- expect(screen.getByRole('listbox')).toBeInTheDocument();
134
- });
135
- // Type to filter
136
- await userEvent.type(combobox, 'a');
137
- // Check for live region announcement
138
- const liveRegion = container.querySelector('[aria-live="polite"]');
139
- expect(liveRegion).toBeInTheDocument();
140
- });
141
- it('should announce loading state', async () => {
142
- const { container } = render(Combobox, {
143
- props: {
144
- options: [],
145
- label: 'Select a fruit',
146
- loading: true,
147
- },
148
- });
149
- const combobox = screen.getByRole('combobox');
150
- await userEvent.click(combobox);
151
- // The live region should announce loading
152
- await waitFor(() => {
153
- const liveRegion = container.querySelector('[aria-live="polite"]');
154
- expect(liveRegion?.textContent).toContain('Loading');
155
- });
156
- });
157
- it('should announce empty state', async () => {
158
- const { container } = render(Combobox, {
159
- props: {
160
- options: [],
161
- label: 'Select a fruit',
162
- emptyMessage: 'No fruits found',
163
- },
164
- });
165
- const combobox = screen.getByRole('combobox');
166
- await userEvent.click(combobox);
167
- await waitFor(() => {
168
- const liveRegion = container.querySelector('[aria-live="polite"]');
169
- expect(liveRegion?.textContent).toContain('No fruits found');
170
- });
171
- });
172
- it('should have proper label association', () => {
173
- render(Combobox, {
174
- props: {
175
- options,
176
- label: 'Favorite fruit',
177
- id: 'fav-fruit',
178
- },
179
- });
180
- const combobox = screen.getByRole('combobox');
181
- expect(combobox).toHaveAttribute('id', 'fav-fruit');
182
- const label = screen.getByText('Favorite fruit');
183
- expect(label).toHaveAttribute('for', 'fav-fruit');
184
- });
185
- it('should indicate required field', () => {
186
- render(Combobox, {
187
- props: {
188
- options,
189
- label: 'Required fruit',
190
- required: true,
191
- },
192
- });
193
- // Visual indicator
194
- expect(screen.getByText('*')).toBeInTheDocument();
195
- // Screen reader text
196
- expect(screen.getByText('(required)')).toHaveClass('sr-only');
197
- });
198
- it('should respect disabled state', () => {
199
- render(Combobox, {
200
- props: {
201
- options,
202
- label: 'Disabled combobox',
203
- disabled: true,
204
- },
205
- });
206
- const combobox = screen.getByRole('combobox');
207
- expect(combobox).toBeDisabled();
208
- });
209
- });
@@ -1,103 +0,0 @@
1
- import { describe, it, expect } from 'vitest';
2
- import { screen } from '@testing-library/svelte';
3
- import userEvent from '@testing-library/user-event';
4
- import { axe } from 'vitest-axe';
5
- describe('Dialog accessibility', () => {
6
- it('should have no axe violations when open', async () => {
7
- const container = document.createElement('div');
8
- document.body.appendChild(container);
9
- container.innerHTML = `
10
- <div role="dialog" aria-modal="true" aria-labelledby="dialog-title" aria-describedby="dialog-desc" class="fixed z-50 bg-background p-6 shadow-lg">
11
- <h2 id="dialog-title" class="text-lg font-semibold">Test Dialog</h2>
12
- <p id="dialog-desc" class="text-sm text-muted-foreground">This is a test dialog description</p>
13
- <button type="button" aria-label="Close dialog">Close</button>
14
- </div>
15
- `;
16
- const results = await axe(container);
17
- expect(results).toHaveNoViolations();
18
- document.body.removeChild(container);
19
- });
20
- it('should have role="dialog"', () => {
21
- const container = document.createElement('div');
22
- document.body.appendChild(container);
23
- container.innerHTML = `
24
- <div role="dialog" aria-modal="true" aria-labelledby="dialog-title">
25
- <h2 id="dialog-title">Test Dialog</h2>
26
- <p>Dialog content</p>
27
- </div>
28
- `;
29
- const dialog = screen.getByRole('dialog');
30
- expect(dialog).toBeInTheDocument();
31
- document.body.removeChild(container);
32
- });
33
- it('should have aria-modal="true"', () => {
34
- const container = document.createElement('div');
35
- document.body.appendChild(container);
36
- container.innerHTML = `
37
- <div role="dialog" aria-modal="true" aria-labelledby="dialog-title">
38
- <h2 id="dialog-title">Test Dialog</h2>
39
- </div>
40
- `;
41
- const dialog = screen.getByRole('dialog');
42
- expect(dialog).toHaveAttribute('aria-modal', 'true');
43
- document.body.removeChild(container);
44
- });
45
- it('should have accessible title', () => {
46
- const container = document.createElement('div');
47
- document.body.appendChild(container);
48
- container.innerHTML = `
49
- <div role="dialog" aria-modal="true" aria-labelledby="dialog-title">
50
- <h2 id="dialog-title">My Dialog Title</h2>
51
- </div>
52
- `;
53
- expect(screen.getByText('My Dialog Title')).toBeInTheDocument();
54
- document.body.removeChild(container);
55
- });
56
- it('should have accessible description', () => {
57
- const container = document.createElement('div');
58
- document.body.appendChild(container);
59
- container.innerHTML = `
60
- <div role="dialog" aria-modal="true" aria-labelledby="dialog-title" aria-describedby="dialog-desc">
61
- <h2 id="dialog-title">Test Dialog</h2>
62
- <p id="dialog-desc">Dialog description text</p>
63
- </div>
64
- `;
65
- expect(screen.getByText('Dialog description text')).toBeInTheDocument();
66
- document.body.removeChild(container);
67
- });
68
- it('should have accessible close button', () => {
69
- const container = document.createElement('div');
70
- document.body.appendChild(container);
71
- container.innerHTML = `
72
- <div role="dialog" aria-modal="true" aria-labelledby="dialog-title">
73
- <h2 id="dialog-title">Test Dialog</h2>
74
- <button type="button" aria-label="Close dialog">
75
- <span aria-hidden="true">&times;</span>
76
- </button>
77
- </div>
78
- `;
79
- const closeButton = screen.getByRole('button', { name: /close/i });
80
- expect(closeButton).toBeInTheDocument();
81
- expect(closeButton).toHaveAttribute('aria-label');
82
- document.body.removeChild(container);
83
- });
84
- it('should be keyboard accessible', async () => {
85
- const user = userEvent.setup();
86
- const container = document.createElement('div');
87
- document.body.appendChild(container);
88
- container.innerHTML = `
89
- <div role="dialog" aria-modal="true" aria-labelledby="dialog-title">
90
- <h2 id="dialog-title">Test Dialog</h2>
91
- <button type="button" id="close-btn" aria-label="Close dialog">Close</button>
92
- <button type="button" id="action-btn">Action</button>
93
- </div>
94
- `;
95
- const closeBtn = container.querySelector('#close-btn');
96
- const actionBtn = container.querySelector('#action-btn');
97
- closeBtn.focus();
98
- expect(closeBtn).toHaveFocus();
99
- await user.tab();
100
- expect(actionBtn).toHaveFocus();
101
- document.body.removeChild(container);
102
- });
103
- });