@instructure/ui-a11y-utils 9.2.1-snapshot-3 → 9.2.1-snapshot-5
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 +1 -1
- package/es/__new-tests__/FocusRegionManager.test.js +163 -0
- package/es/__new-tests__/ScreenReaderFocusRegion.test.js +200 -0
- package/es/__new-tests__/hasVisibleChildren.test.js +37 -0
- package/es/__new-tests__/scopeTab.test.js +81 -0
- package/lib/__new-tests__/FocusRegionManager.test.js +166 -0
- package/lib/__new-tests__/ScreenReaderFocusRegion.test.js +202 -0
- package/lib/__new-tests__/hasVisibleChildren.test.js +39 -0
- package/lib/__new-tests__/scopeTab.test.js +83 -0
- package/package.json +12 -10
- package/src/__new-tests__/FocusRegionManager.test.tsx +152 -0
- package/src/__new-tests__/ScreenReaderFocusRegion.test.tsx +240 -0
- package/src/__new-tests__/hasVisibleChildren.test.tsx +40 -0
- package/src/__new-tests__/scopeTab.test.tsx +106 -0
- package/tsconfig.build.json +1 -2
- package/tsconfig.build.tsbuildinfo +1 -1
- package/types/__new-tests__/FocusRegionManager.test.d.ts +2 -0
- package/types/__new-tests__/FocusRegionManager.test.d.ts.map +1 -0
- package/types/__new-tests__/ScreenReaderFocusRegion.test.d.ts +2 -0
- package/types/__new-tests__/ScreenReaderFocusRegion.test.d.ts.map +1 -0
- package/types/__new-tests__/hasVisibleChildren.test.d.ts +2 -0
- package/types/__new-tests__/hasVisibleChildren.test.d.ts.map +1 -0
- package/types/__new-tests__/scopeTab.test.d.ts +2 -0
- package/types/__new-tests__/scopeTab.test.d.ts.map +1 -0
package/CHANGELOG.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
|
5
5
|
|
|
6
|
-
## [9.2.1-snapshot-
|
|
6
|
+
## [9.2.1-snapshot-5](https://github.com/instructure/instructure-ui/compare/v9.2.0...v9.2.1-snapshot-5) (2024-07-12)
|
|
7
7
|
|
|
8
8
|
**Note:** Version bump only for package @instructure/ui-a11y-utils
|
|
9
9
|
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
var _div, _div2;
|
|
2
|
+
/*
|
|
3
|
+
* The MIT License (MIT)
|
|
4
|
+
*
|
|
5
|
+
* Copyright (c) 2015 - present Instructure, Inc.
|
|
6
|
+
*
|
|
7
|
+
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
8
|
+
* of this software and associated documentation files (the "Software"), to deal
|
|
9
|
+
* in the Software without restriction, including without limitation the rights
|
|
10
|
+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
11
|
+
* copies of the Software, and to permit persons to whom the Software is
|
|
12
|
+
* furnished to do so, subject to the following conditions:
|
|
13
|
+
*
|
|
14
|
+
* The above copyright notice and this permission notice shall be included in all
|
|
15
|
+
* copies or substantial portions of the Software.
|
|
16
|
+
*
|
|
17
|
+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
18
|
+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
19
|
+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
20
|
+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
21
|
+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
22
|
+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
23
|
+
* SOFTWARE.
|
|
24
|
+
*/
|
|
25
|
+
import React from 'react';
|
|
26
|
+
import { render, screen, waitFor } from '@testing-library/react';
|
|
27
|
+
import '@testing-library/jest-dom';
|
|
28
|
+
import { FocusRegionManager } from '../FocusRegionManager';
|
|
29
|
+
describe('FocusRegionManager', () => {
|
|
30
|
+
beforeEach(async () => {
|
|
31
|
+
FocusRegionManager.clearEntries();
|
|
32
|
+
});
|
|
33
|
+
it('should focus the first tabbable element when focused', async () => {
|
|
34
|
+
render(_div || (_div = /*#__PURE__*/React.createElement("div", {
|
|
35
|
+
"data-test-parent": true,
|
|
36
|
+
role: "main",
|
|
37
|
+
"aria-label": "test app",
|
|
38
|
+
id: "test-parent3"
|
|
39
|
+
}, /*#__PURE__*/React.createElement("div", {
|
|
40
|
+
"data-test-ignore": true,
|
|
41
|
+
role: "alert"
|
|
42
|
+
}, /*#__PURE__*/React.createElement("span", null, "test alert")), /*#__PURE__*/React.createElement("div", {
|
|
43
|
+
"data-test-child": true
|
|
44
|
+
}, /*#__PURE__*/React.createElement("div", {
|
|
45
|
+
"data-test-descendant": true
|
|
46
|
+
}), /*#__PURE__*/React.createElement("div", {
|
|
47
|
+
"data-test-descendant": true
|
|
48
|
+
}, /*#__PURE__*/React.createElement("div", {
|
|
49
|
+
"data-test-descendant": true
|
|
50
|
+
}, /*#__PURE__*/React.createElement("button", {
|
|
51
|
+
"data-testid": "button"
|
|
52
|
+
}, "click me")))), /*#__PURE__*/React.createElement("div", {
|
|
53
|
+
"data-test-parent": true,
|
|
54
|
+
"aria-hidden": "true",
|
|
55
|
+
id: "test-parent2"
|
|
56
|
+
}, /*#__PURE__*/React.createElement("div", {
|
|
57
|
+
"data-test-child": true
|
|
58
|
+
}), /*#__PURE__*/React.createElement("div", {
|
|
59
|
+
role: "dialog",
|
|
60
|
+
"aria-label": "some content",
|
|
61
|
+
"data-test-parent": true,
|
|
62
|
+
id: "test-parent1"
|
|
63
|
+
}, /*#__PURE__*/React.createElement("div", {
|
|
64
|
+
"data-testid": "content"
|
|
65
|
+
}, /*#__PURE__*/React.createElement("div", null, "Hello world"), /*#__PURE__*/React.createElement("button", {
|
|
66
|
+
"data-testid": "first-tabbable"
|
|
67
|
+
}, "click me"), /*#__PURE__*/React.createElement("button", null, "or click me")), /*#__PURE__*/React.createElement("span", {
|
|
68
|
+
"data-test-child": true
|
|
69
|
+
}, /*#__PURE__*/React.createElement("ul", {
|
|
70
|
+
"data-test-descendant": true
|
|
71
|
+
}, /*#__PURE__*/React.createElement("li", {
|
|
72
|
+
"data-test-descendant": true
|
|
73
|
+
}, "item 1"), /*#__PURE__*/React.createElement("li", {
|
|
74
|
+
"data-test-descendant": true
|
|
75
|
+
}, "item 2"), /*#__PURE__*/React.createElement("li", {
|
|
76
|
+
"data-test-descendant": true
|
|
77
|
+
}, "item 3"))))), /*#__PURE__*/React.createElement("div", {
|
|
78
|
+
"data-test-child": true,
|
|
79
|
+
"aria-hidden": "true"
|
|
80
|
+
}, /*#__PURE__*/React.createElement("div", {
|
|
81
|
+
"data-test-descendant": true
|
|
82
|
+
}), /*#__PURE__*/React.createElement("div", {
|
|
83
|
+
"data-test-descendant": true
|
|
84
|
+
})), /*#__PURE__*/React.createElement("iframe", {
|
|
85
|
+
id: "frame",
|
|
86
|
+
title: "frame"
|
|
87
|
+
}))));
|
|
88
|
+
const button = screen.getByTestId('button');
|
|
89
|
+
const content = screen.getByTestId('content');
|
|
90
|
+
const firstTabbable = screen.getByTestId('first-tabbable');
|
|
91
|
+
button.focus();
|
|
92
|
+
expect(document.activeElement).toBe(button);
|
|
93
|
+
FocusRegionManager.focusRegion(content);
|
|
94
|
+
await waitFor(() => {
|
|
95
|
+
expect(document.activeElement).toBe(firstTabbable);
|
|
96
|
+
});
|
|
97
|
+
});
|
|
98
|
+
it('should return focus when blurred', async () => {
|
|
99
|
+
render(_div2 || (_div2 = /*#__PURE__*/React.createElement("div", {
|
|
100
|
+
"data-test-parent": true,
|
|
101
|
+
role: "main",
|
|
102
|
+
"aria-label": "test app",
|
|
103
|
+
id: "test-parent3"
|
|
104
|
+
}, /*#__PURE__*/React.createElement("div", {
|
|
105
|
+
"data-test-ignore": true,
|
|
106
|
+
role: "alert"
|
|
107
|
+
}, /*#__PURE__*/React.createElement("span", null, "test alert")), /*#__PURE__*/React.createElement("div", {
|
|
108
|
+
"data-test-child": true
|
|
109
|
+
}, /*#__PURE__*/React.createElement("div", {
|
|
110
|
+
"data-test-descendant": true
|
|
111
|
+
}), /*#__PURE__*/React.createElement("div", {
|
|
112
|
+
"data-test-descendant": true
|
|
113
|
+
}, /*#__PURE__*/React.createElement("div", {
|
|
114
|
+
"data-test-descendant": true
|
|
115
|
+
}, /*#__PURE__*/React.createElement("button", {
|
|
116
|
+
"data-testid": "button"
|
|
117
|
+
}, "click me")))), /*#__PURE__*/React.createElement("div", {
|
|
118
|
+
"data-test-parent": true,
|
|
119
|
+
"aria-hidden": "true",
|
|
120
|
+
id: "test-parent2"
|
|
121
|
+
}, /*#__PURE__*/React.createElement("div", {
|
|
122
|
+
"data-test-child": true
|
|
123
|
+
}), /*#__PURE__*/React.createElement("div", {
|
|
124
|
+
role: "dialog",
|
|
125
|
+
"aria-label": "some content",
|
|
126
|
+
"data-test-parent": true,
|
|
127
|
+
id: "test-parent1"
|
|
128
|
+
}, /*#__PURE__*/React.createElement("div", {
|
|
129
|
+
"data-testid": "content"
|
|
130
|
+
}, /*#__PURE__*/React.createElement("div", null, "Hello world"), /*#__PURE__*/React.createElement("button", {
|
|
131
|
+
"data-test-first-tabbable": true
|
|
132
|
+
}, "click me"), /*#__PURE__*/React.createElement("button", null, "or click me")), /*#__PURE__*/React.createElement("span", {
|
|
133
|
+
"data-test-child": true
|
|
134
|
+
}, /*#__PURE__*/React.createElement("ul", {
|
|
135
|
+
"data-test-descendant": true
|
|
136
|
+
}, /*#__PURE__*/React.createElement("li", {
|
|
137
|
+
"data-test-descendant": true
|
|
138
|
+
}, "item 1"), /*#__PURE__*/React.createElement("li", {
|
|
139
|
+
"data-test-descendant": true
|
|
140
|
+
}, "item 2"), /*#__PURE__*/React.createElement("li", {
|
|
141
|
+
"data-test-descendant": true
|
|
142
|
+
}, "item 3"))))), /*#__PURE__*/React.createElement("div", {
|
|
143
|
+
"data-test-child": true,
|
|
144
|
+
"aria-hidden": "true"
|
|
145
|
+
}, /*#__PURE__*/React.createElement("div", {
|
|
146
|
+
"data-test-descendant": true
|
|
147
|
+
}), /*#__PURE__*/React.createElement("div", {
|
|
148
|
+
"data-test-descendant": true
|
|
149
|
+
})), /*#__PURE__*/React.createElement("iframe", {
|
|
150
|
+
id: "frame",
|
|
151
|
+
title: "frame"
|
|
152
|
+
}))));
|
|
153
|
+
const button = screen.getByTestId('button');
|
|
154
|
+
const content = screen.getByTestId('content');
|
|
155
|
+
button.focus();
|
|
156
|
+
expect(document.activeElement).toBe(button);
|
|
157
|
+
FocusRegionManager.focusRegion(content);
|
|
158
|
+
FocusRegionManager.blurRegion(content);
|
|
159
|
+
await waitFor(() => {
|
|
160
|
+
expect(document.activeElement).toBe(button);
|
|
161
|
+
});
|
|
162
|
+
});
|
|
163
|
+
});
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
var _div, _div2;
|
|
2
|
+
/*
|
|
3
|
+
* The MIT License (MIT)
|
|
4
|
+
*
|
|
5
|
+
* Copyright (c) 2015 - present Instructure, Inc.
|
|
6
|
+
*
|
|
7
|
+
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
8
|
+
* of this software and associated documentation files (the "Software"), to deal
|
|
9
|
+
* in the Software without restriction, including without limitation the rights
|
|
10
|
+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
11
|
+
* copies of the Software, and to permit persons to whom the Software is
|
|
12
|
+
* furnished to do so, subject to the following conditions:
|
|
13
|
+
*
|
|
14
|
+
* The above copyright notice and this permission notice shall be included in all
|
|
15
|
+
* copies or substantial portions of the Software.
|
|
16
|
+
*
|
|
17
|
+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
18
|
+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
19
|
+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
20
|
+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
21
|
+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
22
|
+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
23
|
+
* SOFTWARE.
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
import React from 'react';
|
|
27
|
+
import { render, screen, waitFor } from '@testing-library/react';
|
|
28
|
+
import '@testing-library/jest-dom';
|
|
29
|
+
import { ScreenReaderFocusRegion } from '../ScreenReaderFocusRegion';
|
|
30
|
+
describe('ScreenReaderFocusRegion', () => {
|
|
31
|
+
const element = _div || (_div = /*#__PURE__*/React.createElement("div", {
|
|
32
|
+
"data-testid": "parent",
|
|
33
|
+
role: "main",
|
|
34
|
+
"aria-label": "test app",
|
|
35
|
+
id: "test-parent3"
|
|
36
|
+
}, /*#__PURE__*/React.createElement("div", {
|
|
37
|
+
"data-testid": "ignore",
|
|
38
|
+
role: "alert"
|
|
39
|
+
}, /*#__PURE__*/React.createElement("span", null, "test alert")), /*#__PURE__*/React.createElement("div", {
|
|
40
|
+
"data-testid": "child"
|
|
41
|
+
}, /*#__PURE__*/React.createElement("div", {
|
|
42
|
+
"data-testid": "descendant"
|
|
43
|
+
}, "foo"), /*#__PURE__*/React.createElement("div", {
|
|
44
|
+
"data-testid": "descendant"
|
|
45
|
+
}, /*#__PURE__*/React.createElement("div", {
|
|
46
|
+
"data-testid": "descendant"
|
|
47
|
+
}, "bar"))), /*#__PURE__*/React.createElement("div", {
|
|
48
|
+
"data-testid": "parent",
|
|
49
|
+
"aria-hidden": "true",
|
|
50
|
+
id: "test-parent2"
|
|
51
|
+
}, /*#__PURE__*/React.createElement("div", {
|
|
52
|
+
"data-testid": "child"
|
|
53
|
+
}), /*#__PURE__*/React.createElement("div", {
|
|
54
|
+
role: "dialog",
|
|
55
|
+
"aria-label": "some content",
|
|
56
|
+
"data-testid": "parent",
|
|
57
|
+
id: "test-parent1"
|
|
58
|
+
}, /*#__PURE__*/React.createElement("div", {
|
|
59
|
+
"data-testid": "content"
|
|
60
|
+
}, /*#__PURE__*/React.createElement("div", null, "Hello world"), /*#__PURE__*/React.createElement("button", null, "click me"), /*#__PURE__*/React.createElement("button", null, "or click me")), /*#__PURE__*/React.createElement("span", {
|
|
61
|
+
"data-testid": "child"
|
|
62
|
+
}, /*#__PURE__*/React.createElement("ul", {
|
|
63
|
+
"data-testid": "descendant"
|
|
64
|
+
}, /*#__PURE__*/React.createElement("li", {
|
|
65
|
+
"data-testid": "descendant"
|
|
66
|
+
}, "item 1"), /*#__PURE__*/React.createElement("li", {
|
|
67
|
+
"data-testid": "descendant"
|
|
68
|
+
}, "item 2"), /*#__PURE__*/React.createElement("li", {
|
|
69
|
+
"data-testid": "descendant"
|
|
70
|
+
}, "item 3"))))), /*#__PURE__*/React.createElement("div", {
|
|
71
|
+
"data-testid": "child-initial-hidden",
|
|
72
|
+
"aria-hidden": "true"
|
|
73
|
+
}, /*#__PURE__*/React.createElement("div", {
|
|
74
|
+
"data-testid": "descendant"
|
|
75
|
+
}, "foo"), /*#__PURE__*/React.createElement("div", {
|
|
76
|
+
"data-testid": "descendant"
|
|
77
|
+
}, "bar"))));
|
|
78
|
+
it('should accept a function for liveRegion', async () => {
|
|
79
|
+
render(element);
|
|
80
|
+
const ignore = screen.getByTestId('ignore');
|
|
81
|
+
const content = screen.getByTestId('content');
|
|
82
|
+
const screenReaderFocusRegion = new ScreenReaderFocusRegion(content, {
|
|
83
|
+
liveRegion: () => ignore,
|
|
84
|
+
shouldContainFocus: true
|
|
85
|
+
});
|
|
86
|
+
screenReaderFocusRegion.activate();
|
|
87
|
+
await waitFor(() => {
|
|
88
|
+
expect(ignore).not.toHaveAttribute('aria-hidden');
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
it("should apply aria-hidden to all children of content's parent nodes unless they are live regions", async () => {
|
|
92
|
+
render(element);
|
|
93
|
+
const ignore = screen.getByTestId('ignore');
|
|
94
|
+
const content = screen.getByTestId('content');
|
|
95
|
+
const children = screen.getAllByTestId('child');
|
|
96
|
+
const screenReaderFocusRegion = new ScreenReaderFocusRegion(content, {
|
|
97
|
+
liveRegion: ignore,
|
|
98
|
+
shouldContainFocus: true
|
|
99
|
+
});
|
|
100
|
+
screenReaderFocusRegion.activate();
|
|
101
|
+
children.forEach(node => {
|
|
102
|
+
expect(node).toHaveAttribute('aria-hidden');
|
|
103
|
+
});
|
|
104
|
+
expect(ignore).not.toHaveAttribute('aria-hidden');
|
|
105
|
+
});
|
|
106
|
+
it("should mute designated attributes for content's parent nodes", async () => {
|
|
107
|
+
render(element);
|
|
108
|
+
const content = screen.getByTestId('content');
|
|
109
|
+
const parents = screen.getAllByTestId('parent');
|
|
110
|
+
const screenReaderFocusRegion = new ScreenReaderFocusRegion(content);
|
|
111
|
+
screenReaderFocusRegion.activate();
|
|
112
|
+
parents.forEach(node => {
|
|
113
|
+
expect(node).not.toHaveAttribute('aria-hidden');
|
|
114
|
+
expect(node).not.toHaveAttribute('aria-label');
|
|
115
|
+
expect(node).not.toHaveAttribute('role');
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
it('should not apply aria-hidden to descendants', async () => {
|
|
119
|
+
render(element);
|
|
120
|
+
const content = screen.getByTestId('content');
|
|
121
|
+
const descendants = screen.getAllByTestId('descendant');
|
|
122
|
+
const screenReaderFocusRegion = new ScreenReaderFocusRegion(content);
|
|
123
|
+
screenReaderFocusRegion.activate();
|
|
124
|
+
descendants.forEach(node => {
|
|
125
|
+
expect(node).not.toHaveAttribute('aria-hidden');
|
|
126
|
+
});
|
|
127
|
+
});
|
|
128
|
+
it('should not apply aria-hidden to dynamically added descendants of content', async () => {
|
|
129
|
+
render(element);
|
|
130
|
+
const content = screen.getByTestId('content');
|
|
131
|
+
const screenReaderFocusRegion = new ScreenReaderFocusRegion(content);
|
|
132
|
+
screenReaderFocusRegion.activate();
|
|
133
|
+
const desc = document.createElement('div');
|
|
134
|
+
content.appendChild(desc);
|
|
135
|
+
screenReaderFocusRegion.handleDOMMutation([{
|
|
136
|
+
addedNodes: [desc],
|
|
137
|
+
removedNodes: []
|
|
138
|
+
}]);
|
|
139
|
+
Array.from(content.childNodes).forEach(node => {
|
|
140
|
+
expect(node).not.toHaveAttribute('aria-hidden');
|
|
141
|
+
});
|
|
142
|
+
});
|
|
143
|
+
it('should remove aria-hidden from children unless they had aria-hidden before', async () => {
|
|
144
|
+
render(element);
|
|
145
|
+
const content = screen.getByTestId('content');
|
|
146
|
+
const screenReaderFocusRegion = new ScreenReaderFocusRegion(content);
|
|
147
|
+
const childNodes = screen.getAllByTestId('child');
|
|
148
|
+
const exception = screen.getByTestId('child-initial-hidden');
|
|
149
|
+
screenReaderFocusRegion.activate();
|
|
150
|
+
screenReaderFocusRegion.deactivate();
|
|
151
|
+
childNodes.forEach(node => {
|
|
152
|
+
expect(node).not.toHaveAttribute('aria-hidden');
|
|
153
|
+
});
|
|
154
|
+
expect(exception).toHaveAttribute('aria-hidden');
|
|
155
|
+
});
|
|
156
|
+
it('should properly restore and unmute parent attributes', async () => {
|
|
157
|
+
render(element);
|
|
158
|
+
const content = screen.getByTestId('content');
|
|
159
|
+
const screenReaderFocusRegion = new ScreenReaderFocusRegion(content);
|
|
160
|
+
const parentNodes = screen.getAllByTestId('parent');
|
|
161
|
+
const attrsMap = {};
|
|
162
|
+
parentNodes.forEach(node => {
|
|
163
|
+
attrsMap[node.getAttribute('id')] = [...node.attributes];
|
|
164
|
+
});
|
|
165
|
+
screenReaderFocusRegion.activate();
|
|
166
|
+
screenReaderFocusRegion.deactivate();
|
|
167
|
+
parentNodes.forEach(node => {
|
|
168
|
+
const preNodeAttrs = attrsMap[node.getAttribute('id')];
|
|
169
|
+
const postNodeAttrs = [...node.attributes];
|
|
170
|
+
|
|
171
|
+
// both should have same number of attributes
|
|
172
|
+
expect(preNodeAttrs.length).toEqual(postNodeAttrs.length);
|
|
173
|
+
preNodeAttrs.forEach(preNodeAttribute => {
|
|
174
|
+
const matchingAttribute = postNodeAttrs.filter(postNodeAttribute => preNodeAttribute.name === postNodeAttribute.name)[0];
|
|
175
|
+
expect(matchingAttribute.value).toEqual(preNodeAttribute.value);
|
|
176
|
+
});
|
|
177
|
+
});
|
|
178
|
+
});
|
|
179
|
+
it('should not apply aria-hidden to elements that have aria-live attributes', async () => {
|
|
180
|
+
render(_div2 || (_div2 = /*#__PURE__*/React.createElement("div", {
|
|
181
|
+
"data-testid": "main",
|
|
182
|
+
role: "main",
|
|
183
|
+
"aria-label": "test app"
|
|
184
|
+
}, /*#__PURE__*/React.createElement("div", {
|
|
185
|
+
"data-testid": "live",
|
|
186
|
+
"aria-live": "assertive"
|
|
187
|
+
}), /*#__PURE__*/React.createElement("div", {
|
|
188
|
+
"data-testid": "regular"
|
|
189
|
+
}), /*#__PURE__*/React.createElement("div", {
|
|
190
|
+
"data-testid": "content"
|
|
191
|
+
}))));
|
|
192
|
+
const content = screen.getByTestId('content');
|
|
193
|
+
const screenReaderFocusRegion = new ScreenReaderFocusRegion(content);
|
|
194
|
+
screenReaderFocusRegion.activate();
|
|
195
|
+
const liveRegion = screen.getByTestId('live');
|
|
196
|
+
const regularRegion = screen.getByTestId('regular');
|
|
197
|
+
expect(liveRegion).not.toHaveAttribute('aria-hidden');
|
|
198
|
+
expect(regularRegion).toHaveAttribute('aria-hidden');
|
|
199
|
+
});
|
|
200
|
+
});
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
var _ScreenReaderContent, _div;
|
|
2
|
+
/*
|
|
3
|
+
* The MIT License (MIT)
|
|
4
|
+
*
|
|
5
|
+
* Copyright (c) 2015 - present Instructure, Inc.
|
|
6
|
+
*
|
|
7
|
+
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
8
|
+
* of this software and associated documentation files (the "Software"), to deal
|
|
9
|
+
* in the Software without restriction, including without limitation the rights
|
|
10
|
+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
11
|
+
* copies of the Software, and to permit persons to whom the Software is
|
|
12
|
+
* furnished to do so, subject to the following conditions:
|
|
13
|
+
*
|
|
14
|
+
* The above copyright notice and this permission notice shall be included in all
|
|
15
|
+
* copies or substantial portions of the Software.
|
|
16
|
+
*
|
|
17
|
+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
18
|
+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
19
|
+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
20
|
+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
21
|
+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
22
|
+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
23
|
+
* SOFTWARE.
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
import React from 'react';
|
|
27
|
+
import '@testing-library/jest-dom';
|
|
28
|
+
import { ScreenReaderContent } from '@instructure/ui-a11y-content';
|
|
29
|
+
import { hasVisibleChildren } from '../hasVisibleChildren';
|
|
30
|
+
describe('hasVisibleChildren', () => {
|
|
31
|
+
it('should not count ScreenReaderContent as visible content', async () => {
|
|
32
|
+
expect(hasVisibleChildren(_ScreenReaderContent || (_ScreenReaderContent = /*#__PURE__*/React.createElement(ScreenReaderContent, null, "Foo")))).toBe(false);
|
|
33
|
+
});
|
|
34
|
+
it('should count everything else as visible', async () => {
|
|
35
|
+
expect(hasVisibleChildren(_div || (_div = /*#__PURE__*/React.createElement("div", null, "Foo")))).toBe(true);
|
|
36
|
+
});
|
|
37
|
+
});
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
var _div, _div2, _div3;
|
|
2
|
+
/*
|
|
3
|
+
* The MIT License (MIT)
|
|
4
|
+
*
|
|
5
|
+
* Copyright (c) 2015 - present Instructure, Inc.
|
|
6
|
+
*
|
|
7
|
+
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
8
|
+
* of this software and associated documentation files (the "Software"), to deal
|
|
9
|
+
* in the Software without restriction, including without limitation the rights
|
|
10
|
+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
11
|
+
* copies of the Software, and to permit persons to whom the Software is
|
|
12
|
+
* furnished to do so, subject to the following conditions:
|
|
13
|
+
*
|
|
14
|
+
* The above copyright notice and this permission notice shall be included in all
|
|
15
|
+
* copies or substantial portions of the Software.
|
|
16
|
+
*
|
|
17
|
+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
18
|
+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
19
|
+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
20
|
+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
21
|
+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
22
|
+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
23
|
+
* SOFTWARE.
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
import React from 'react';
|
|
27
|
+
import { render, screen, waitFor } from '@testing-library/react';
|
|
28
|
+
import { vi } from 'vitest';
|
|
29
|
+
import '@testing-library/jest-dom';
|
|
30
|
+
import { scopeTab } from '../scopeTab';
|
|
31
|
+
const MOCK_EVENT = new KeyboardEvent('mockEvent', {
|
|
32
|
+
shiftKey: false
|
|
33
|
+
});
|
|
34
|
+
MOCK_EVENT.preventDefault = () => {};
|
|
35
|
+
describe('scopeTab', () => {
|
|
36
|
+
it('should scope tab within container', async () => {
|
|
37
|
+
render(_div || (_div = /*#__PURE__*/React.createElement("div", null, /*#__PURE__*/React.createElement("div", {
|
|
38
|
+
"data-testid": "container"
|
|
39
|
+
}, /*#__PURE__*/React.createElement("input", {
|
|
40
|
+
"data-testid": "first"
|
|
41
|
+
}), /*#__PURE__*/React.createElement("input", {
|
|
42
|
+
"data-testid": "second"
|
|
43
|
+
})))));
|
|
44
|
+
const container = screen.getByTestId('container');
|
|
45
|
+
const first = screen.getByTestId('first');
|
|
46
|
+
const second = screen.getByTestId('second');
|
|
47
|
+
second.focus();
|
|
48
|
+
await waitFor(() => {
|
|
49
|
+
expect(document.activeElement).toBe(second);
|
|
50
|
+
});
|
|
51
|
+
scopeTab(container, MOCK_EVENT);
|
|
52
|
+
await waitFor(() => {
|
|
53
|
+
expect(document.activeElement).toBe(first);
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
it('should not attempt scoping when no tabbable children', async () => {
|
|
57
|
+
render(_div2 || (_div2 = /*#__PURE__*/React.createElement("div", null, /*#__PURE__*/React.createElement("div", {
|
|
58
|
+
"data-testid": "container"
|
|
59
|
+
}, "Hello"), /*#__PURE__*/React.createElement("input", null))));
|
|
60
|
+
const input = screen.getByRole('textbox');
|
|
61
|
+
const container = screen.getByTestId('container');
|
|
62
|
+
input.focus();
|
|
63
|
+
scopeTab(container, MOCK_EVENT);
|
|
64
|
+
await waitFor(() => {
|
|
65
|
+
expect(document.activeElement).toBe(input);
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
it('should execute callback when provided instead of default behavior', async () => {
|
|
69
|
+
const cb = vi.fn();
|
|
70
|
+
render(_div3 || (_div3 = /*#__PURE__*/React.createElement("div", null, /*#__PURE__*/React.createElement("div", {
|
|
71
|
+
"data-testid": "container"
|
|
72
|
+
}, /*#__PURE__*/React.createElement("input", null)))));
|
|
73
|
+
const input = screen.getByRole('textbox');
|
|
74
|
+
const container = screen.getByTestId('container');
|
|
75
|
+
input.focus();
|
|
76
|
+
scopeTab(container, MOCK_EVENT, cb);
|
|
77
|
+
await waitFor(() => {
|
|
78
|
+
expect(cb).toHaveBeenCalled();
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
});
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault").default;
|
|
4
|
+
var _react = _interopRequireDefault(require("react"));
|
|
5
|
+
var _react2 = require("@testing-library/react");
|
|
6
|
+
require("@testing-library/jest-dom");
|
|
7
|
+
var _FocusRegionManager = require("../FocusRegionManager");
|
|
8
|
+
var _div, _div2;
|
|
9
|
+
/*
|
|
10
|
+
* The MIT License (MIT)
|
|
11
|
+
*
|
|
12
|
+
* Copyright (c) 2015 - present Instructure, Inc.
|
|
13
|
+
*
|
|
14
|
+
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
15
|
+
* of this software and associated documentation files (the "Software"), to deal
|
|
16
|
+
* in the Software without restriction, including without limitation the rights
|
|
17
|
+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
18
|
+
* copies of the Software, and to permit persons to whom the Software is
|
|
19
|
+
* furnished to do so, subject to the following conditions:
|
|
20
|
+
*
|
|
21
|
+
* The above copyright notice and this permission notice shall be included in all
|
|
22
|
+
* copies or substantial portions of the Software.
|
|
23
|
+
*
|
|
24
|
+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
25
|
+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
26
|
+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
27
|
+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
28
|
+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
29
|
+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
30
|
+
* SOFTWARE.
|
|
31
|
+
*/
|
|
32
|
+
describe('FocusRegionManager', () => {
|
|
33
|
+
beforeEach(async () => {
|
|
34
|
+
_FocusRegionManager.FocusRegionManager.clearEntries();
|
|
35
|
+
});
|
|
36
|
+
it('should focus the first tabbable element when focused', async () => {
|
|
37
|
+
(0, _react2.render)(_div || (_div = /*#__PURE__*/_react.default.createElement("div", {
|
|
38
|
+
"data-test-parent": true,
|
|
39
|
+
role: "main",
|
|
40
|
+
"aria-label": "test app",
|
|
41
|
+
id: "test-parent3"
|
|
42
|
+
}, /*#__PURE__*/_react.default.createElement("div", {
|
|
43
|
+
"data-test-ignore": true,
|
|
44
|
+
role: "alert"
|
|
45
|
+
}, /*#__PURE__*/_react.default.createElement("span", null, "test alert")), /*#__PURE__*/_react.default.createElement("div", {
|
|
46
|
+
"data-test-child": true
|
|
47
|
+
}, /*#__PURE__*/_react.default.createElement("div", {
|
|
48
|
+
"data-test-descendant": true
|
|
49
|
+
}), /*#__PURE__*/_react.default.createElement("div", {
|
|
50
|
+
"data-test-descendant": true
|
|
51
|
+
}, /*#__PURE__*/_react.default.createElement("div", {
|
|
52
|
+
"data-test-descendant": true
|
|
53
|
+
}, /*#__PURE__*/_react.default.createElement("button", {
|
|
54
|
+
"data-testid": "button"
|
|
55
|
+
}, "click me")))), /*#__PURE__*/_react.default.createElement("div", {
|
|
56
|
+
"data-test-parent": true,
|
|
57
|
+
"aria-hidden": "true",
|
|
58
|
+
id: "test-parent2"
|
|
59
|
+
}, /*#__PURE__*/_react.default.createElement("div", {
|
|
60
|
+
"data-test-child": true
|
|
61
|
+
}), /*#__PURE__*/_react.default.createElement("div", {
|
|
62
|
+
role: "dialog",
|
|
63
|
+
"aria-label": "some content",
|
|
64
|
+
"data-test-parent": true,
|
|
65
|
+
id: "test-parent1"
|
|
66
|
+
}, /*#__PURE__*/_react.default.createElement("div", {
|
|
67
|
+
"data-testid": "content"
|
|
68
|
+
}, /*#__PURE__*/_react.default.createElement("div", null, "Hello world"), /*#__PURE__*/_react.default.createElement("button", {
|
|
69
|
+
"data-testid": "first-tabbable"
|
|
70
|
+
}, "click me"), /*#__PURE__*/_react.default.createElement("button", null, "or click me")), /*#__PURE__*/_react.default.createElement("span", {
|
|
71
|
+
"data-test-child": true
|
|
72
|
+
}, /*#__PURE__*/_react.default.createElement("ul", {
|
|
73
|
+
"data-test-descendant": true
|
|
74
|
+
}, /*#__PURE__*/_react.default.createElement("li", {
|
|
75
|
+
"data-test-descendant": true
|
|
76
|
+
}, "item 1"), /*#__PURE__*/_react.default.createElement("li", {
|
|
77
|
+
"data-test-descendant": true
|
|
78
|
+
}, "item 2"), /*#__PURE__*/_react.default.createElement("li", {
|
|
79
|
+
"data-test-descendant": true
|
|
80
|
+
}, "item 3"))))), /*#__PURE__*/_react.default.createElement("div", {
|
|
81
|
+
"data-test-child": true,
|
|
82
|
+
"aria-hidden": "true"
|
|
83
|
+
}, /*#__PURE__*/_react.default.createElement("div", {
|
|
84
|
+
"data-test-descendant": true
|
|
85
|
+
}), /*#__PURE__*/_react.default.createElement("div", {
|
|
86
|
+
"data-test-descendant": true
|
|
87
|
+
})), /*#__PURE__*/_react.default.createElement("iframe", {
|
|
88
|
+
id: "frame",
|
|
89
|
+
title: "frame"
|
|
90
|
+
}))));
|
|
91
|
+
const button = _react2.screen.getByTestId('button');
|
|
92
|
+
const content = _react2.screen.getByTestId('content');
|
|
93
|
+
const firstTabbable = _react2.screen.getByTestId('first-tabbable');
|
|
94
|
+
button.focus();
|
|
95
|
+
expect(document.activeElement).toBe(button);
|
|
96
|
+
_FocusRegionManager.FocusRegionManager.focusRegion(content);
|
|
97
|
+
await (0, _react2.waitFor)(() => {
|
|
98
|
+
expect(document.activeElement).toBe(firstTabbable);
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
it('should return focus when blurred', async () => {
|
|
102
|
+
(0, _react2.render)(_div2 || (_div2 = /*#__PURE__*/_react.default.createElement("div", {
|
|
103
|
+
"data-test-parent": true,
|
|
104
|
+
role: "main",
|
|
105
|
+
"aria-label": "test app",
|
|
106
|
+
id: "test-parent3"
|
|
107
|
+
}, /*#__PURE__*/_react.default.createElement("div", {
|
|
108
|
+
"data-test-ignore": true,
|
|
109
|
+
role: "alert"
|
|
110
|
+
}, /*#__PURE__*/_react.default.createElement("span", null, "test alert")), /*#__PURE__*/_react.default.createElement("div", {
|
|
111
|
+
"data-test-child": true
|
|
112
|
+
}, /*#__PURE__*/_react.default.createElement("div", {
|
|
113
|
+
"data-test-descendant": true
|
|
114
|
+
}), /*#__PURE__*/_react.default.createElement("div", {
|
|
115
|
+
"data-test-descendant": true
|
|
116
|
+
}, /*#__PURE__*/_react.default.createElement("div", {
|
|
117
|
+
"data-test-descendant": true
|
|
118
|
+
}, /*#__PURE__*/_react.default.createElement("button", {
|
|
119
|
+
"data-testid": "button"
|
|
120
|
+
}, "click me")))), /*#__PURE__*/_react.default.createElement("div", {
|
|
121
|
+
"data-test-parent": true,
|
|
122
|
+
"aria-hidden": "true",
|
|
123
|
+
id: "test-parent2"
|
|
124
|
+
}, /*#__PURE__*/_react.default.createElement("div", {
|
|
125
|
+
"data-test-child": true
|
|
126
|
+
}), /*#__PURE__*/_react.default.createElement("div", {
|
|
127
|
+
role: "dialog",
|
|
128
|
+
"aria-label": "some content",
|
|
129
|
+
"data-test-parent": true,
|
|
130
|
+
id: "test-parent1"
|
|
131
|
+
}, /*#__PURE__*/_react.default.createElement("div", {
|
|
132
|
+
"data-testid": "content"
|
|
133
|
+
}, /*#__PURE__*/_react.default.createElement("div", null, "Hello world"), /*#__PURE__*/_react.default.createElement("button", {
|
|
134
|
+
"data-test-first-tabbable": true
|
|
135
|
+
}, "click me"), /*#__PURE__*/_react.default.createElement("button", null, "or click me")), /*#__PURE__*/_react.default.createElement("span", {
|
|
136
|
+
"data-test-child": true
|
|
137
|
+
}, /*#__PURE__*/_react.default.createElement("ul", {
|
|
138
|
+
"data-test-descendant": true
|
|
139
|
+
}, /*#__PURE__*/_react.default.createElement("li", {
|
|
140
|
+
"data-test-descendant": true
|
|
141
|
+
}, "item 1"), /*#__PURE__*/_react.default.createElement("li", {
|
|
142
|
+
"data-test-descendant": true
|
|
143
|
+
}, "item 2"), /*#__PURE__*/_react.default.createElement("li", {
|
|
144
|
+
"data-test-descendant": true
|
|
145
|
+
}, "item 3"))))), /*#__PURE__*/_react.default.createElement("div", {
|
|
146
|
+
"data-test-child": true,
|
|
147
|
+
"aria-hidden": "true"
|
|
148
|
+
}, /*#__PURE__*/_react.default.createElement("div", {
|
|
149
|
+
"data-test-descendant": true
|
|
150
|
+
}), /*#__PURE__*/_react.default.createElement("div", {
|
|
151
|
+
"data-test-descendant": true
|
|
152
|
+
})), /*#__PURE__*/_react.default.createElement("iframe", {
|
|
153
|
+
id: "frame",
|
|
154
|
+
title: "frame"
|
|
155
|
+
}))));
|
|
156
|
+
const button = _react2.screen.getByTestId('button');
|
|
157
|
+
const content = _react2.screen.getByTestId('content');
|
|
158
|
+
button.focus();
|
|
159
|
+
expect(document.activeElement).toBe(button);
|
|
160
|
+
_FocusRegionManager.FocusRegionManager.focusRegion(content);
|
|
161
|
+
_FocusRegionManager.FocusRegionManager.blurRegion(content);
|
|
162
|
+
await (0, _react2.waitFor)(() => {
|
|
163
|
+
expect(document.activeElement).toBe(button);
|
|
164
|
+
});
|
|
165
|
+
});
|
|
166
|
+
});
|