@pzerelles/headlessui-svelte 2.0.0-next.1 → 2.1.1-next.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/dist/button/Button.svelte +65 -0
- package/dist/button/Button.svelte.d.ts +39 -0
- package/dist/button/index.d.ts +1 -0
- package/dist/button/index.js +1 -0
- package/dist/checkbox/Checkbox.svelte +60 -46
- package/dist/checkbox/Checkbox.svelte.d.ts +1 -1
- package/dist/close-button/CloseButton.svelte +10 -0
- package/dist/close-button/CloseButton.svelte.d.ts +25 -0
- package/dist/close-button/index.d.ts +1 -0
- package/dist/close-button/index.js +1 -0
- package/dist/combobox/Combobox.svelte +6 -0
- package/dist/combobox/Combobox.svelte.d.ts +50 -0
- package/dist/description/Description.svelte +50 -32
- package/dist/description/Description.svelte.d.ts +14 -5
- package/dist/field/Field.svelte +9 -9
- package/dist/fieldset/Fieldset.svelte +9 -9
- package/dist/hooks/document-overflow/adjust-scrollbar-padding.d.ts +2 -0
- package/dist/hooks/document-overflow/adjust-scrollbar-padding.js +18 -0
- package/dist/hooks/document-overflow/handle-ios-locking.d.ts +6 -0
- package/dist/hooks/document-overflow/handle-ios-locking.js +134 -0
- package/dist/hooks/document-overflow/overflow-store.d.ts +19 -0
- package/dist/hooks/document-overflow/overflow-store.js +76 -0
- package/dist/hooks/document-overflow/prevent-scroll.d.ts +2 -0
- package/dist/hooks/document-overflow/prevent-scroll.js +7 -0
- package/dist/hooks/document-overflow/use-document-overflow.svelte.d.ts +7 -0
- package/dist/hooks/document-overflow/use-document-overflow.svelte.js +27 -0
- package/dist/hooks/use-active-press.svelte.d.ts +14 -0
- package/dist/{actions/activePress.svelte.js → hooks/use-active-press.svelte.js} +33 -39
- package/dist/hooks/use-by-comparator.d.ts +2 -0
- package/dist/hooks/use-by-comparator.js +15 -0
- package/dist/hooks/use-controllable.svelte.d.ts +6 -0
- package/dist/hooks/use-controllable.svelte.js +34 -0
- package/dist/hooks/use-did-element-move.svelte.d.ts +6 -0
- package/dist/hooks/use-did-element-move.svelte.js +27 -0
- package/dist/hooks/use-disabled.d.ts +3 -0
- package/dist/hooks/use-disabled.js +9 -0
- package/dist/hooks/use-element-size.svelte.d.ts +7 -0
- package/dist/hooks/use-element-size.svelte.js +36 -0
- package/dist/hooks/use-flags.svelte.d.ts +8 -0
- package/dist/hooks/use-flags.svelte.js +18 -0
- package/dist/hooks/use-focus-ring.svelte.d.ts +10 -0
- package/dist/hooks/use-focus-ring.svelte.js +24 -0
- package/dist/hooks/use-hover.svelte.d.ts +26 -0
- package/dist/hooks/use-hover.svelte.js +124 -0
- package/dist/hooks/use-id.d.ts +1 -0
- package/dist/hooks/use-id.js +1 -0
- package/dist/hooks/use-inert-others.svelte.d.ts +32 -0
- package/dist/hooks/use-inert-others.svelte.js +114 -0
- package/dist/hooks/use-is-top-layer.svelte.d.ts +29 -0
- package/dist/hooks/use-is-top-layer.svelte.js +82 -0
- package/dist/hooks/use-on-disappear.svelte.d.ts +12 -0
- package/dist/hooks/use-on-disappear.svelte.js +38 -0
- package/dist/hooks/use-outside-click.svelte.d.ts +10 -0
- package/dist/hooks/use-outside-click.svelte.js +150 -0
- package/dist/hooks/use-reducer.d.ts +4 -0
- package/dist/hooks/use-reducer.js +11 -0
- package/dist/hooks/use-resolve-button-type.svelte.d.ts +10 -0
- package/dist/hooks/use-resolve-button-type.svelte.js +19 -0
- package/dist/hooks/use-scroll-lock.svelte.d.ts +5 -0
- package/dist/hooks/use-scroll-lock.svelte.js +24 -0
- package/dist/hooks/use-sync-refs.d.ts +7 -0
- package/dist/hooks/use-sync-refs.js +22 -0
- package/dist/hooks/use-text-value.svelte.d.ts +3 -0
- package/dist/hooks/use-text-value.svelte.js +20 -0
- package/dist/hooks/use-tracked-pointer.d.ts +4 -0
- package/dist/hooks/use-tracked-pointer.js +26 -0
- package/dist/hooks/use-transition.svelte.d.ts +20 -0
- package/dist/hooks/use-transition.svelte.js +252 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +4 -0
- package/dist/internal/FocusSentinel.svelte +45 -0
- package/dist/internal/FocusSentinel.svelte.d.ts +17 -0
- package/dist/internal/FormFields.svelte +2 -4
- package/dist/internal/FormFields.svelte.d.ts +5 -6
- package/dist/internal/FormResolver.svelte +11 -16
- package/dist/internal/FormResolver.svelte.d.ts +2 -3
- package/dist/internal/Hidden.svelte +8 -8
- package/dist/internal/Hidden.svelte.d.ts +28 -19
- package/dist/internal/HoistFormFields.svelte.d.ts +1 -1
- package/dist/internal/Portal.svelte.d.ts +1 -1
- package/dist/internal/floating.svelte.d.ts +57 -0
- package/dist/internal/floating.svelte.js +477 -0
- package/dist/internal/frozen.svelte.d.ts +6 -0
- package/dist/internal/frozen.svelte.js +18 -0
- package/dist/internal/id.d.ts +8 -0
- package/dist/internal/id.js +11 -0
- package/dist/internal/open-closed.d.ts +14 -0
- package/dist/internal/open-closed.js +17 -0
- package/dist/internal/portal-force-root.svelte.d.ts +6 -0
- package/dist/internal/portal-force-root.svelte.js +11 -0
- package/dist/label/Label.svelte +53 -32
- package/dist/label/Label.svelte.d.ts +14 -5
- package/dist/legend/Legend.svelte.d.ts +1 -2
- package/dist/listbox/Listbox.svelte +451 -0
- package/dist/listbox/Listbox.svelte.d.ts +107 -0
- package/dist/listbox/ListboxButton.svelte +141 -0
- package/dist/listbox/ListboxButton.svelte.d.ts +41 -0
- package/dist/listbox/ListboxOption.svelte +138 -0
- package/dist/listbox/ListboxOption.svelte.d.ts +39 -0
- package/dist/listbox/ListboxOptions.svelte +267 -0
- package/dist/listbox/ListboxOptions.svelte.d.ts +39 -0
- package/dist/listbox/ListboxSelectedOption.svelte +25 -0
- package/dist/listbox/ListboxSelectedOption.svelte.d.ts +30 -0
- package/dist/listbox/index.d.ts +5 -0
- package/dist/listbox/index.js +5 -0
- package/dist/portal/InternalPortal.svelte +108 -0
- package/dist/portal/InternalPortal.svelte.d.ts +34 -0
- package/dist/portal/Portal.svelte +11 -0
- package/dist/portal/Portal.svelte.d.ts +23 -0
- package/dist/portal/PortalGroup.svelte +15 -0
- package/dist/portal/PortalGroup.svelte.d.ts +31 -0
- package/dist/switch/Switch.svelte +149 -0
- package/dist/switch/Switch.svelte.d.ts +44 -0
- package/dist/switch/SwitchGroup.svelte +38 -0
- package/dist/switch/SwitchGroup.svelte.d.ts +27 -0
- package/dist/switch/index.d.ts +2 -0
- package/dist/switch/index.js +2 -0
- package/dist/tabs/Button.svelte +65 -0
- package/dist/tabs/Button.svelte.d.ts +39 -0
- package/dist/tabs/Tab.svelte +161 -0
- package/dist/tabs/Tab.svelte.d.ts +36 -0
- package/dist/tabs/TabGroup.svelte +244 -0
- package/dist/tabs/TabGroup.svelte.d.ts +54 -0
- package/dist/tabs/TabList.svelte +18 -0
- package/dist/tabs/TabList.svelte.d.ts +28 -0
- package/dist/tabs/TabPanel.svelte +63 -0
- package/dist/tabs/TabPanel.svelte.d.ts +34 -0
- package/dist/tabs/TabPanels.svelte +13 -0
- package/dist/tabs/TabPanels.svelte.d.ts +27 -0
- package/dist/tabs/index.d.ts +5 -0
- package/dist/tabs/index.js +5 -0
- package/dist/test-utils/accessability-assertions.d.ts +271 -0
- package/dist/test-utils/accessability-assertions.js +1572 -0
- package/dist/test-utils/fake-pointer.d.ts +24 -0
- package/dist/test-utils/fake-pointer.js +48 -0
- package/dist/test-utils/interactions.d.ts +61 -0
- package/dist/test-utils/interactions.js +453 -0
- package/dist/test-utils/suppress-console-logs.d.ts +7 -0
- package/dist/test-utils/suppress-console-logs.js +17 -0
- package/dist/utils/StableCollection.svelte +43 -0
- package/dist/utils/StableCollection.svelte.d.ts +19 -0
- package/dist/utils/calculate-active-index.d.ts +25 -0
- package/dist/utils/calculate-active-index.js +74 -0
- package/dist/utils/close.d.ts +2 -0
- package/dist/utils/close.js +3 -0
- package/dist/utils/default-map.d.ts +5 -0
- package/dist/utils/default-map.js +15 -0
- package/dist/utils/disposables.d.ts +14 -12
- package/dist/utils/disposables.js +13 -10
- package/dist/utils/dom.d.ts +0 -2
- package/dist/utils/dom.js +2 -4
- package/dist/utils/env.d.ts +17 -0
- package/dist/utils/env.js +39 -0
- package/dist/utils/focus-management.d.ts +44 -0
- package/dist/utils/focus-management.js +242 -0
- package/dist/utils/focusVisible.svelte.d.ts +3 -3
- package/dist/utils/focusVisible.svelte.js +52 -41
- package/dist/utils/get-text-value.d.ts +1 -0
- package/dist/utils/get-text-value.js +71 -0
- package/dist/utils/id.d.ts +1 -1
- package/dist/utils/match.d.ts +1 -0
- package/dist/utils/match.js +13 -0
- package/dist/utils/once.d.ts +1 -0
- package/dist/utils/once.js +9 -0
- package/dist/utils/owner.d.ts +1 -0
- package/dist/utils/owner.js +8 -0
- package/dist/utils/platform.d.ts +2 -0
- package/dist/utils/platform.js +17 -0
- package/dist/utils/ref.svelte.d.ts +4 -0
- package/dist/utils/ref.svelte.js +4 -0
- package/dist/utils/render.d.ts +31 -0
- package/dist/utils/render.js +56 -0
- package/dist/utils/store.d.ts +11 -0
- package/dist/utils/store.js +20 -0
- package/dist/utils/types.d.ts +27 -0
- package/dist/utils/types.js +6 -0
- package/package.json +28 -21
- package/dist/actions/activePress.svelte.d.ts +0 -8
- package/dist/actions/focusRing.svelte.d.ts +0 -9
- package/dist/actions/focusRing.svelte.js +0 -34
- package/dist/utils/disabled.d.ts +0 -3
- package/dist/utils/disabled.js +0 -2
|
@@ -0,0 +1,1572 @@
|
|
|
1
|
+
import { FocusableMode, isFocusableElement } from "../utils/focus-management.js";
|
|
2
|
+
function assertNever(x) {
|
|
3
|
+
throw new Error("Unexpected object: " + x);
|
|
4
|
+
}
|
|
5
|
+
// ---
|
|
6
|
+
export function getLabel() {
|
|
7
|
+
return document.querySelector('label,[id^="headlessui-label-"]');
|
|
8
|
+
}
|
|
9
|
+
export function getLabels() {
|
|
10
|
+
return Array.from(document.querySelectorAll('label,[id^="headlessui-label-"]'));
|
|
11
|
+
}
|
|
12
|
+
export function getDescription() {
|
|
13
|
+
return document.querySelector('[id^="headlessui-description-"]');
|
|
14
|
+
}
|
|
15
|
+
export function getDescriptions() {
|
|
16
|
+
return Array.from(document.querySelectorAll('[id^="headlessui-description-"]'));
|
|
17
|
+
}
|
|
18
|
+
export function getControl() {
|
|
19
|
+
return document.querySelector('[id^="headlessui-control-"]');
|
|
20
|
+
}
|
|
21
|
+
export function getInput() {
|
|
22
|
+
return document.querySelector("input");
|
|
23
|
+
}
|
|
24
|
+
export function getSelect() {
|
|
25
|
+
return document.querySelector("select");
|
|
26
|
+
}
|
|
27
|
+
export function getTextarea() {
|
|
28
|
+
return document.querySelector("textarea");
|
|
29
|
+
}
|
|
30
|
+
export function getCheckbox() {
|
|
31
|
+
return document.querySelector('input[type="checkbox"],[role="checkbox"]');
|
|
32
|
+
}
|
|
33
|
+
export var CheckboxState;
|
|
34
|
+
(function (CheckboxState) {
|
|
35
|
+
/** The checkbox is checked. */
|
|
36
|
+
CheckboxState[CheckboxState["Checked"] = 0] = "Checked";
|
|
37
|
+
/** The checkbox is unchecked. */
|
|
38
|
+
CheckboxState[CheckboxState["Unchecked"] = 1] = "Unchecked";
|
|
39
|
+
/** The checkbox is indeterminate. */
|
|
40
|
+
CheckboxState[CheckboxState["Indeterminate"] = 2] = "Indeterminate";
|
|
41
|
+
})(CheckboxState || (CheckboxState = {}));
|
|
42
|
+
export function assertCheckbox(options, checkbox = getCheckbox()) {
|
|
43
|
+
try {
|
|
44
|
+
switch (options.state) {
|
|
45
|
+
case CheckboxState.Checked:
|
|
46
|
+
if (checkbox === null)
|
|
47
|
+
return expect(checkbox).not.toBe(null);
|
|
48
|
+
expect(checkbox).toHaveAttribute("aria-checked", "true");
|
|
49
|
+
expect(checkbox).toHaveAttribute("role", "checkbox");
|
|
50
|
+
expect(checkbox).toHaveAttribute("tabindex", "0");
|
|
51
|
+
expect(checkbox).toHaveAttribute("data-checked");
|
|
52
|
+
for (let attributeName in options.attributes) {
|
|
53
|
+
expect(checkbox).toHaveAttribute(attributeName, options.attributes[attributeName]);
|
|
54
|
+
}
|
|
55
|
+
break;
|
|
56
|
+
case CheckboxState.Unchecked:
|
|
57
|
+
if (checkbox === null)
|
|
58
|
+
return expect(checkbox).not.toBe(null);
|
|
59
|
+
expect(checkbox).toHaveAttribute("aria-checked", "false");
|
|
60
|
+
expect(checkbox).toHaveAttribute("role", "checkbox");
|
|
61
|
+
expect(checkbox).toHaveAttribute("tabindex", "0");
|
|
62
|
+
for (let attributeName in options.attributes) {
|
|
63
|
+
expect(checkbox).toHaveAttribute(attributeName, options.attributes[attributeName]);
|
|
64
|
+
}
|
|
65
|
+
break;
|
|
66
|
+
case CheckboxState.Indeterminate:
|
|
67
|
+
if (checkbox === null)
|
|
68
|
+
return expect(checkbox).not.toBe(null);
|
|
69
|
+
expect(checkbox).toHaveAttribute("aria-checked", "mixed");
|
|
70
|
+
expect(checkbox).toHaveAttribute("indeterminate", "true");
|
|
71
|
+
expect(checkbox).toHaveAttribute("role", "checkbox");
|
|
72
|
+
expect(checkbox).toHaveAttribute("tabindex", "0");
|
|
73
|
+
expect(checkbox).toHaveAttribute("data-indeterminate");
|
|
74
|
+
for (let attributeName in options.attributes) {
|
|
75
|
+
expect(checkbox).toHaveAttribute(attributeName, options.attributes[attributeName]);
|
|
76
|
+
}
|
|
77
|
+
break;
|
|
78
|
+
default:
|
|
79
|
+
assertNever(options.state);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
catch (err) {
|
|
83
|
+
if (err instanceof Error)
|
|
84
|
+
Error.captureStackTrace(err, assertCheckbox);
|
|
85
|
+
throw err;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
export function assertLinkedWithLabel(element, label) {
|
|
89
|
+
try {
|
|
90
|
+
if (element === null)
|
|
91
|
+
return expect(element).not.toBe(null);
|
|
92
|
+
let labels = Array.isArray(label) ? label : [label];
|
|
93
|
+
expect(element).toHaveAttribute("aria-labelledby");
|
|
94
|
+
let labelledBy = new Set(element.getAttribute("aria-labelledby")?.split(" ") ?? []);
|
|
95
|
+
for (let label of labels) {
|
|
96
|
+
expect(labelledBy).toContain(label.id);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
catch (err) {
|
|
100
|
+
if (err instanceof Error)
|
|
101
|
+
Error.captureStackTrace(err, assertLinkedWithLabel);
|
|
102
|
+
throw err;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
export function assertNotLinkedWithLabel(element, label) {
|
|
106
|
+
try {
|
|
107
|
+
if (element === null)
|
|
108
|
+
return expect(element).not.toBe(null);
|
|
109
|
+
let labels = Array.isArray(label) ? label : [label];
|
|
110
|
+
let labelledBy = new Set(element.getAttribute("aria-labelledby")?.split(" ") ?? []);
|
|
111
|
+
for (let label of labels) {
|
|
112
|
+
expect(labelledBy).not.toContain(label.id);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
catch (err) {
|
|
116
|
+
if (err instanceof Error)
|
|
117
|
+
Error.captureStackTrace(err, assertNotLinkedWithLabel);
|
|
118
|
+
throw err;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
export function assertLinkedWithDescription(element, description) {
|
|
122
|
+
try {
|
|
123
|
+
if (element === null)
|
|
124
|
+
return expect(element).not.toBe(null);
|
|
125
|
+
let descriptions = Array.isArray(description) ? description : [description];
|
|
126
|
+
expect(element).toHaveAttribute("aria-describedby");
|
|
127
|
+
let describedby = new Set(element.getAttribute("aria-describedby")?.split(" ") ?? []);
|
|
128
|
+
for (let description of descriptions) {
|
|
129
|
+
expect(describedby).toContain(description.id);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
catch (err) {
|
|
133
|
+
if (err instanceof Error)
|
|
134
|
+
Error.captureStackTrace(err, assertLinkedWithDescription);
|
|
135
|
+
throw err;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
// ---
|
|
139
|
+
export function getMenuButton() {
|
|
140
|
+
return document.querySelector('button,[role="button"],[id^="headlessui-menu-button-"]');
|
|
141
|
+
}
|
|
142
|
+
export function getMenuButtons() {
|
|
143
|
+
return Array.from(document.querySelectorAll('button,[role="button"]'));
|
|
144
|
+
}
|
|
145
|
+
export function getMenu() {
|
|
146
|
+
return document.querySelector('[role="menu"]');
|
|
147
|
+
}
|
|
148
|
+
export function getMenus() {
|
|
149
|
+
return Array.from(document.querySelectorAll('[role="menu"]'));
|
|
150
|
+
}
|
|
151
|
+
export function getMenuItems() {
|
|
152
|
+
return Array.from(document.querySelectorAll('[role="menuitem"]'));
|
|
153
|
+
}
|
|
154
|
+
// ---
|
|
155
|
+
export var MenuState;
|
|
156
|
+
(function (MenuState) {
|
|
157
|
+
/** The menu is visible to the user. */
|
|
158
|
+
MenuState[MenuState["Visible"] = 0] = "Visible";
|
|
159
|
+
/** The menu is **not** visible to the user. It's still in the DOM, but it is hidden. */
|
|
160
|
+
MenuState[MenuState["InvisibleHidden"] = 1] = "InvisibleHidden";
|
|
161
|
+
/** The menu is **not** visible to the user. It's not in the DOM, it is unmounted. */
|
|
162
|
+
MenuState[MenuState["InvisibleUnmounted"] = 2] = "InvisibleUnmounted";
|
|
163
|
+
})(MenuState || (MenuState = {}));
|
|
164
|
+
export function assertMenuButton(options, button = getMenuButton()) {
|
|
165
|
+
try {
|
|
166
|
+
if (button === null)
|
|
167
|
+
return expect(button).not.toBe(null);
|
|
168
|
+
// Ensure menu button have these properties
|
|
169
|
+
expect(button).toHaveAttribute("id");
|
|
170
|
+
expect(button).toHaveAttribute("aria-haspopup");
|
|
171
|
+
switch (options.state) {
|
|
172
|
+
case MenuState.Visible:
|
|
173
|
+
expect(button).toHaveAttribute("aria-controls");
|
|
174
|
+
expect(button).toHaveAttribute("aria-expanded", "true");
|
|
175
|
+
break;
|
|
176
|
+
case MenuState.InvisibleHidden:
|
|
177
|
+
expect(button).toHaveAttribute("aria-controls");
|
|
178
|
+
expect(button).toHaveAttribute("aria-expanded", "false");
|
|
179
|
+
break;
|
|
180
|
+
case MenuState.InvisibleUnmounted:
|
|
181
|
+
expect(button).not.toHaveAttribute("aria-controls");
|
|
182
|
+
expect(button).toHaveAttribute("aria-expanded", "false");
|
|
183
|
+
break;
|
|
184
|
+
default:
|
|
185
|
+
assertNever(options.state);
|
|
186
|
+
}
|
|
187
|
+
if (options.textContent) {
|
|
188
|
+
expect(button).toHaveTextContent(options.textContent);
|
|
189
|
+
}
|
|
190
|
+
// Ensure menu button has the following attributes
|
|
191
|
+
for (let attributeName in options.attributes) {
|
|
192
|
+
expect(button).toHaveAttribute(attributeName, options.attributes[attributeName]);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
catch (err) {
|
|
196
|
+
if (err instanceof Error)
|
|
197
|
+
Error.captureStackTrace(err, assertMenuButton);
|
|
198
|
+
throw err;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
export function assertMenuButtonLinkedWithMenu(button = getMenuButton(), menu = getMenu()) {
|
|
202
|
+
try {
|
|
203
|
+
if (button === null)
|
|
204
|
+
return expect(button).not.toBe(null);
|
|
205
|
+
if (menu === null)
|
|
206
|
+
return expect(menu).not.toBe(null);
|
|
207
|
+
// Ensure link between button & menu is correct
|
|
208
|
+
expect(button).toHaveAttribute("aria-controls", menu.getAttribute("id"));
|
|
209
|
+
expect(menu).toHaveAttribute("aria-labelledby", button.getAttribute("id"));
|
|
210
|
+
}
|
|
211
|
+
catch (err) {
|
|
212
|
+
if (err instanceof Error)
|
|
213
|
+
Error.captureStackTrace(err, assertMenuButtonLinkedWithMenu);
|
|
214
|
+
throw err;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
export function assertMenuLinkedWithMenuItem(item, menu = getMenu()) {
|
|
218
|
+
try {
|
|
219
|
+
if (menu === null)
|
|
220
|
+
return expect(menu).not.toBe(null);
|
|
221
|
+
if (item === null)
|
|
222
|
+
return expect(item).not.toBe(null);
|
|
223
|
+
// Ensure link between menu & menu item is correct
|
|
224
|
+
expect(menu).toHaveAttribute("aria-activedescendant", item.getAttribute("id"));
|
|
225
|
+
}
|
|
226
|
+
catch (err) {
|
|
227
|
+
if (err instanceof Error)
|
|
228
|
+
Error.captureStackTrace(err, assertMenuLinkedWithMenuItem);
|
|
229
|
+
throw err;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
export function assertNoActiveMenuItem(menu = getMenu()) {
|
|
233
|
+
try {
|
|
234
|
+
if (menu === null)
|
|
235
|
+
return expect(menu).not.toBe(null);
|
|
236
|
+
// Ensure we don't have an active menu
|
|
237
|
+
expect(menu).not.toHaveAttribute("aria-activedescendant");
|
|
238
|
+
}
|
|
239
|
+
catch (err) {
|
|
240
|
+
if (err instanceof Error)
|
|
241
|
+
Error.captureStackTrace(err, assertNoActiveMenuItem);
|
|
242
|
+
throw err;
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
export function assertMenu(options, menu = getMenu()) {
|
|
246
|
+
try {
|
|
247
|
+
switch (options.state) {
|
|
248
|
+
case MenuState.InvisibleHidden:
|
|
249
|
+
if (menu === null)
|
|
250
|
+
return expect(menu).not.toBe(null);
|
|
251
|
+
assertHidden(menu);
|
|
252
|
+
expect(menu).toHaveAttribute("aria-labelledby");
|
|
253
|
+
expect(menu).toHaveAttribute("role", "menu");
|
|
254
|
+
if (options.textContent)
|
|
255
|
+
expect(menu).toHaveTextContent(options.textContent);
|
|
256
|
+
for (let attributeName in options.attributes) {
|
|
257
|
+
expect(menu).toHaveAttribute(attributeName, options.attributes[attributeName]);
|
|
258
|
+
}
|
|
259
|
+
break;
|
|
260
|
+
case MenuState.Visible:
|
|
261
|
+
if (menu === null)
|
|
262
|
+
return expect(menu).not.toBe(null);
|
|
263
|
+
assertVisible(menu);
|
|
264
|
+
expect(menu).toHaveAttribute("aria-labelledby");
|
|
265
|
+
expect(menu).toHaveAttribute("role", "menu");
|
|
266
|
+
if (options.textContent)
|
|
267
|
+
expect(menu).toHaveTextContent(options.textContent);
|
|
268
|
+
for (let attributeName in options.attributes) {
|
|
269
|
+
expect(menu).toHaveAttribute(attributeName, options.attributes[attributeName]);
|
|
270
|
+
}
|
|
271
|
+
break;
|
|
272
|
+
case MenuState.InvisibleUnmounted:
|
|
273
|
+
expect(menu).toBe(null);
|
|
274
|
+
break;
|
|
275
|
+
default:
|
|
276
|
+
assertNever(options.state);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
catch (err) {
|
|
280
|
+
if (err instanceof Error)
|
|
281
|
+
Error.captureStackTrace(err, assertMenu);
|
|
282
|
+
throw err;
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
export function assertMenuItem(item, options) {
|
|
286
|
+
try {
|
|
287
|
+
if (item === null)
|
|
288
|
+
return expect(item).not.toBe(null);
|
|
289
|
+
// Check that some attributes exists, doesn't really matter what the values are at this point in
|
|
290
|
+
// time, we just require them.
|
|
291
|
+
expect(item).toHaveAttribute("id");
|
|
292
|
+
// Check that we have the correct values for certain attributes
|
|
293
|
+
expect(item).toHaveAttribute("role", "menuitem");
|
|
294
|
+
if (!item.getAttribute("aria-disabled"))
|
|
295
|
+
expect(item).toHaveAttribute("tabindex", "-1");
|
|
296
|
+
// Ensure menu button has the following attributes
|
|
297
|
+
if (options) {
|
|
298
|
+
for (let attributeName in options.attributes) {
|
|
299
|
+
expect(item).toHaveAttribute(attributeName, options.attributes[attributeName]);
|
|
300
|
+
}
|
|
301
|
+
if (options.tag) {
|
|
302
|
+
expect(item.tagName.toLowerCase()).toBe(options.tag);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
catch (err) {
|
|
307
|
+
if (err instanceof Error)
|
|
308
|
+
Error.captureStackTrace(err, assertMenuItem);
|
|
309
|
+
throw err;
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
// ---
|
|
313
|
+
export function getComboboxLabel() {
|
|
314
|
+
return document.querySelector('label,[id^="headlessui-combobox-label"],[id^="headlessui-label"]');
|
|
315
|
+
}
|
|
316
|
+
export function getComboboxButton() {
|
|
317
|
+
return document.querySelector('button,[role="button"],[id^="headlessui-combobox-button-"]');
|
|
318
|
+
}
|
|
319
|
+
export function getComboboxButtons() {
|
|
320
|
+
return Array.from(document.querySelectorAll('button,[role="button"]'));
|
|
321
|
+
}
|
|
322
|
+
export function getComboboxInput() {
|
|
323
|
+
return document.querySelector('[role="combobox"]');
|
|
324
|
+
}
|
|
325
|
+
export function getCombobox() {
|
|
326
|
+
return document.querySelector('[role="listbox"]');
|
|
327
|
+
}
|
|
328
|
+
export function getComboboxInputs() {
|
|
329
|
+
return Array.from(document.querySelectorAll('[role="combobox"]'));
|
|
330
|
+
}
|
|
331
|
+
export function getComboboxes() {
|
|
332
|
+
return Array.from(document.querySelectorAll('[role="listbox"]'));
|
|
333
|
+
}
|
|
334
|
+
export function getComboboxOptions() {
|
|
335
|
+
return Array.from(document.querySelectorAll('[role="option"]'));
|
|
336
|
+
}
|
|
337
|
+
// ---
|
|
338
|
+
export var ComboboxState;
|
|
339
|
+
(function (ComboboxState) {
|
|
340
|
+
/** The combobox is visible to the user. */
|
|
341
|
+
ComboboxState[ComboboxState["Visible"] = 0] = "Visible";
|
|
342
|
+
/** The combobox is **not** visible to the user. It's still in the DOM, but it is hidden. */
|
|
343
|
+
ComboboxState[ComboboxState["InvisibleHidden"] = 1] = "InvisibleHidden";
|
|
344
|
+
/** The combobox is **not** visible to the user. It's not in the DOM, it is unmounted. */
|
|
345
|
+
ComboboxState[ComboboxState["InvisibleUnmounted"] = 2] = "InvisibleUnmounted";
|
|
346
|
+
})(ComboboxState || (ComboboxState = {}));
|
|
347
|
+
export var ComboboxMode;
|
|
348
|
+
(function (ComboboxMode) {
|
|
349
|
+
/** The combobox is in the `single` mode. */
|
|
350
|
+
ComboboxMode[ComboboxMode["Single"] = 0] = "Single";
|
|
351
|
+
/** The combobox is in the `multiple` mode. */
|
|
352
|
+
ComboboxMode[ComboboxMode["Multiple"] = 1] = "Multiple";
|
|
353
|
+
})(ComboboxMode || (ComboboxMode = {}));
|
|
354
|
+
export function assertCombobox(options, combobox = getComboboxInput(), listbox = getListbox()) {
|
|
355
|
+
try {
|
|
356
|
+
switch (options.state) {
|
|
357
|
+
case ComboboxState.InvisibleHidden:
|
|
358
|
+
if (combobox === null)
|
|
359
|
+
return expect(combobox).not.toBe(null);
|
|
360
|
+
assertHidden(combobox);
|
|
361
|
+
expect(combobox).toHaveAttribute("role", "combobox");
|
|
362
|
+
if (options.textContent)
|
|
363
|
+
expect(combobox).toHaveTextContent(options.textContent);
|
|
364
|
+
for (let attributeName in options.attributes) {
|
|
365
|
+
expect(combobox).toHaveAttribute(attributeName, options.attributes[attributeName]);
|
|
366
|
+
}
|
|
367
|
+
break;
|
|
368
|
+
case ComboboxState.Visible:
|
|
369
|
+
if (combobox === null)
|
|
370
|
+
return expect(combobox).not.toBe(null);
|
|
371
|
+
assertVisible(combobox);
|
|
372
|
+
expect(combobox).toHaveAttribute("role", "combobox");
|
|
373
|
+
if (options.mode && options.mode === ComboboxMode.Multiple) {
|
|
374
|
+
expect(listbox).toHaveAttribute("aria-multiselectable", "true");
|
|
375
|
+
}
|
|
376
|
+
if (options.textContent)
|
|
377
|
+
expect(combobox).toHaveTextContent(options.textContent);
|
|
378
|
+
for (let attributeName in options.attributes) {
|
|
379
|
+
expect(combobox).toHaveAttribute(attributeName, options.attributes[attributeName]);
|
|
380
|
+
}
|
|
381
|
+
break;
|
|
382
|
+
case ComboboxState.InvisibleUnmounted:
|
|
383
|
+
expect(combobox).toBe(null);
|
|
384
|
+
break;
|
|
385
|
+
default:
|
|
386
|
+
assertNever(options.state);
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
catch (err) {
|
|
390
|
+
if (err instanceof Error)
|
|
391
|
+
Error.captureStackTrace(err, assertCombobox);
|
|
392
|
+
throw err;
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
export function assertComboboxInput(options, input = getComboboxInput()) {
|
|
396
|
+
try {
|
|
397
|
+
if (input === null)
|
|
398
|
+
return expect(input).not.toBe(null);
|
|
399
|
+
// Ensure combobox input has these properties
|
|
400
|
+
expect(input).toHaveAttribute("id");
|
|
401
|
+
switch (options.state) {
|
|
402
|
+
case ComboboxState.Visible:
|
|
403
|
+
expect(input).toHaveAttribute("aria-controls");
|
|
404
|
+
expect(input).toHaveAttribute("aria-expanded", "true");
|
|
405
|
+
break;
|
|
406
|
+
case ComboboxState.InvisibleHidden:
|
|
407
|
+
expect(input).toHaveAttribute("aria-controls");
|
|
408
|
+
expect(input).toHaveAttribute("aria-expanded", "false");
|
|
409
|
+
break;
|
|
410
|
+
case ComboboxState.InvisibleUnmounted:
|
|
411
|
+
expect(input).not.toHaveAttribute("aria-controls");
|
|
412
|
+
expect(input).toHaveAttribute("aria-expanded", "false");
|
|
413
|
+
break;
|
|
414
|
+
default:
|
|
415
|
+
assertNever(options.state);
|
|
416
|
+
}
|
|
417
|
+
// Ensure combobox input has the following attributes
|
|
418
|
+
for (let attributeName in options.attributes) {
|
|
419
|
+
expect(input).toHaveAttribute(attributeName, options.attributes[attributeName]);
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
catch (err) {
|
|
423
|
+
if (err instanceof Error)
|
|
424
|
+
Error.captureStackTrace(err, assertComboboxInput);
|
|
425
|
+
throw err;
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
export function assertComboboxList(options, listbox = getCombobox()) {
|
|
429
|
+
try {
|
|
430
|
+
switch (options.state) {
|
|
431
|
+
case ComboboxState.InvisibleHidden:
|
|
432
|
+
if (listbox === null)
|
|
433
|
+
return expect(listbox).not.toBe(null);
|
|
434
|
+
assertHidden(listbox);
|
|
435
|
+
expect(listbox).toHaveAttribute("aria-labelledby");
|
|
436
|
+
expect(listbox).toHaveAttribute("role", "listbox");
|
|
437
|
+
if (options.textContent)
|
|
438
|
+
expect(listbox).toHaveTextContent(options.textContent);
|
|
439
|
+
for (let attributeName in options.attributes) {
|
|
440
|
+
expect(listbox).toHaveAttribute(attributeName, options.attributes[attributeName]);
|
|
441
|
+
}
|
|
442
|
+
break;
|
|
443
|
+
case ComboboxState.Visible:
|
|
444
|
+
if (listbox === null)
|
|
445
|
+
return expect(listbox).not.toBe(null);
|
|
446
|
+
assertVisible(listbox);
|
|
447
|
+
expect(listbox).toHaveAttribute("aria-labelledby");
|
|
448
|
+
expect(listbox).toHaveAttribute("role", "listbox");
|
|
449
|
+
if (options.textContent)
|
|
450
|
+
expect(listbox).toHaveTextContent(options.textContent);
|
|
451
|
+
for (let attributeName in options.attributes) {
|
|
452
|
+
expect(listbox).toHaveAttribute(attributeName, options.attributes[attributeName]);
|
|
453
|
+
}
|
|
454
|
+
break;
|
|
455
|
+
case ComboboxState.InvisibleUnmounted:
|
|
456
|
+
expect(listbox).toBe(null);
|
|
457
|
+
break;
|
|
458
|
+
default:
|
|
459
|
+
assertNever(options.state);
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
catch (err) {
|
|
463
|
+
if (err instanceof Error)
|
|
464
|
+
Error.captureStackTrace(err, assertCombobox);
|
|
465
|
+
throw err;
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
export function assertComboboxButton(options, button = getComboboxButton()) {
|
|
469
|
+
try {
|
|
470
|
+
if (button === null)
|
|
471
|
+
return expect(button).not.toBe(null);
|
|
472
|
+
// Ensure menu button have these properties
|
|
473
|
+
expect(button).toHaveAttribute("id");
|
|
474
|
+
expect(button).toHaveAttribute("aria-haspopup");
|
|
475
|
+
switch (options.state) {
|
|
476
|
+
case ComboboxState.Visible:
|
|
477
|
+
expect(button).toHaveAttribute("aria-controls");
|
|
478
|
+
expect(button).toHaveAttribute("aria-expanded", "true");
|
|
479
|
+
break;
|
|
480
|
+
case ComboboxState.InvisibleHidden:
|
|
481
|
+
expect(button).toHaveAttribute("aria-controls");
|
|
482
|
+
expect(button).toHaveAttribute("aria-expanded", "false");
|
|
483
|
+
break;
|
|
484
|
+
case ComboboxState.InvisibleUnmounted:
|
|
485
|
+
expect(button).not.toHaveAttribute("aria-controls");
|
|
486
|
+
expect(button).toHaveAttribute("aria-expanded", "false");
|
|
487
|
+
break;
|
|
488
|
+
default:
|
|
489
|
+
assertNever(options.state);
|
|
490
|
+
}
|
|
491
|
+
if (options.textContent) {
|
|
492
|
+
expect(button).toHaveTextContent(options.textContent);
|
|
493
|
+
}
|
|
494
|
+
// Ensure menu button has the following attributes
|
|
495
|
+
for (let attributeName in options.attributes) {
|
|
496
|
+
expect(button).toHaveAttribute(attributeName, options.attributes[attributeName]);
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
catch (err) {
|
|
500
|
+
if (err instanceof Error)
|
|
501
|
+
Error.captureStackTrace(err, assertComboboxButton);
|
|
502
|
+
throw err;
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
export function assertComboboxLabel(options, label = getComboboxLabel()) {
|
|
506
|
+
try {
|
|
507
|
+
if (label === null)
|
|
508
|
+
return expect(label).not.toBe(null);
|
|
509
|
+
// Ensure menu button have these properties
|
|
510
|
+
expect(label).toHaveAttribute("id");
|
|
511
|
+
if (options.textContent) {
|
|
512
|
+
expect(label).toHaveTextContent(options.textContent);
|
|
513
|
+
}
|
|
514
|
+
if (options.tag) {
|
|
515
|
+
expect(label.tagName.toLowerCase()).toBe(options.tag);
|
|
516
|
+
}
|
|
517
|
+
// Ensure menu button has the following attributes
|
|
518
|
+
for (let attributeName in options.attributes) {
|
|
519
|
+
expect(label).toHaveAttribute(attributeName, options.attributes[attributeName]);
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
catch (err) {
|
|
523
|
+
if (err instanceof Error)
|
|
524
|
+
Error.captureStackTrace(err, assertComboboxLabel);
|
|
525
|
+
throw err;
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
export function assertComboboxButtonLinkedWithCombobox(button = getComboboxButton(), combobox = getCombobox()) {
|
|
529
|
+
try {
|
|
530
|
+
if (button === null)
|
|
531
|
+
return expect(button).not.toBe(null);
|
|
532
|
+
if (combobox === null)
|
|
533
|
+
return expect(combobox).not.toBe(null);
|
|
534
|
+
// Ensure link between button & combobox is correct
|
|
535
|
+
expect(button).toHaveAttribute("aria-controls", combobox.getAttribute("id"));
|
|
536
|
+
expect(combobox).toHaveAttribute("aria-labelledby", button.getAttribute("id"));
|
|
537
|
+
}
|
|
538
|
+
catch (err) {
|
|
539
|
+
if (err instanceof Error)
|
|
540
|
+
Error.captureStackTrace(err, assertComboboxButtonLinkedWithCombobox);
|
|
541
|
+
throw err;
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
export function assertComboboxLabelLinkedWithCombobox(label = getComboboxLabel(), combobox = getComboboxInput()) {
|
|
545
|
+
try {
|
|
546
|
+
if (label === null)
|
|
547
|
+
return expect(label).not.toBe(null);
|
|
548
|
+
if (combobox === null)
|
|
549
|
+
return expect(combobox).not.toBe(null);
|
|
550
|
+
expect(combobox).toHaveAttribute("aria-labelledby", label.getAttribute("id"));
|
|
551
|
+
}
|
|
552
|
+
catch (err) {
|
|
553
|
+
if (err instanceof Error)
|
|
554
|
+
Error.captureStackTrace(err, assertComboboxLabelLinkedWithCombobox);
|
|
555
|
+
throw err;
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
export function assertComboboxButtonLinkedWithComboboxLabel(button = getComboboxButton(), label = getComboboxLabel()) {
|
|
559
|
+
try {
|
|
560
|
+
if (button === null)
|
|
561
|
+
return expect(button).not.toBe(null);
|
|
562
|
+
if (label === null)
|
|
563
|
+
return expect(label).not.toBe(null);
|
|
564
|
+
// Ensure link between button & label is correct
|
|
565
|
+
expect(button).toHaveAttribute("aria-labelledby", `${label.id} ${button.id}`);
|
|
566
|
+
}
|
|
567
|
+
catch (err) {
|
|
568
|
+
if (err instanceof Error)
|
|
569
|
+
Error.captureStackTrace(err, assertComboboxButtonLinkedWithComboboxLabel);
|
|
570
|
+
throw err;
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
export function assertActiveComboboxOption(item, combobox = getComboboxInput()) {
|
|
574
|
+
try {
|
|
575
|
+
if (combobox === null)
|
|
576
|
+
return expect(combobox).not.toBe(null);
|
|
577
|
+
if (item === null)
|
|
578
|
+
return expect(item).not.toBe(null);
|
|
579
|
+
// Ensure link between combobox & combobox item is correct
|
|
580
|
+
expect(combobox).toHaveAttribute("aria-activedescendant", item.getAttribute("id"));
|
|
581
|
+
}
|
|
582
|
+
catch (err) {
|
|
583
|
+
if (err instanceof Error)
|
|
584
|
+
Error.captureStackTrace(err, assertActiveComboboxOption);
|
|
585
|
+
throw err;
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
export function assertNotActiveComboboxOption(item, combobox = getComboboxInput()) {
|
|
589
|
+
try {
|
|
590
|
+
if (combobox === null)
|
|
591
|
+
return expect(combobox).not.toBe(null);
|
|
592
|
+
if (item === null)
|
|
593
|
+
return expect(item).not.toBe(null);
|
|
594
|
+
// Ensure link between combobox & combobox item does not exist
|
|
595
|
+
expect(combobox).not.toHaveAttribute("aria-activedescendant", item.getAttribute("id"));
|
|
596
|
+
}
|
|
597
|
+
catch (err) {
|
|
598
|
+
if (err instanceof Error)
|
|
599
|
+
Error.captureStackTrace(err, assertNotActiveComboboxOption);
|
|
600
|
+
throw err;
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
export function assertNoActiveComboboxOption(combobox = getComboboxInput()) {
|
|
604
|
+
try {
|
|
605
|
+
if (combobox === null)
|
|
606
|
+
return expect(combobox).not.toBe(null);
|
|
607
|
+
// Ensure we don't have an active combobox
|
|
608
|
+
expect(combobox).not.toHaveAttribute("aria-activedescendant");
|
|
609
|
+
}
|
|
610
|
+
catch (err) {
|
|
611
|
+
if (err instanceof Error)
|
|
612
|
+
Error.captureStackTrace(err, assertNoActiveComboboxOption);
|
|
613
|
+
throw err;
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
export function assertNoSelectedComboboxOption(items = getComboboxOptions()) {
|
|
617
|
+
try {
|
|
618
|
+
for (let item of items)
|
|
619
|
+
expect(item).toHaveAttribute("aria-selected", "false");
|
|
620
|
+
}
|
|
621
|
+
catch (err) {
|
|
622
|
+
if (err instanceof Error)
|
|
623
|
+
Error.captureStackTrace(err, assertNoSelectedComboboxOption);
|
|
624
|
+
throw err;
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
export function assertComboboxOption(item, options) {
|
|
628
|
+
try {
|
|
629
|
+
if (item === null)
|
|
630
|
+
return expect(item).not.toBe(null);
|
|
631
|
+
// Check that some attributes exists, doesn't really matter what the values are at this point in
|
|
632
|
+
// time, we just require them.
|
|
633
|
+
expect(item).toHaveAttribute("id");
|
|
634
|
+
// Check that we have the correct values for certain attributes
|
|
635
|
+
expect(item).toHaveAttribute("role", "option");
|
|
636
|
+
if (!item.getAttribute("aria-disabled"))
|
|
637
|
+
expect(item).toHaveAttribute("tabindex", "-1");
|
|
638
|
+
// Ensure combobox button has the following attributes
|
|
639
|
+
if (!options)
|
|
640
|
+
return;
|
|
641
|
+
for (let attributeName in options.attributes) {
|
|
642
|
+
expect(item).toHaveAttribute(attributeName, options.attributes[attributeName]);
|
|
643
|
+
}
|
|
644
|
+
if (options.tag) {
|
|
645
|
+
expect(item.tagName.toLowerCase()).toBe(options.tag);
|
|
646
|
+
}
|
|
647
|
+
if (options.selected != null) {
|
|
648
|
+
return expect(item).toHaveAttribute("aria-selected", options.selected ? "true" : "false");
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
catch (err) {
|
|
652
|
+
if (err instanceof Error)
|
|
653
|
+
Error.captureStackTrace(err, assertComboboxOption);
|
|
654
|
+
throw err;
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
// ---
|
|
658
|
+
export function getListboxLabel() {
|
|
659
|
+
return document.querySelector('label,[id^="headlessui-listbox-label"],[id^="headlessui-label"]');
|
|
660
|
+
}
|
|
661
|
+
export function getListboxButton() {
|
|
662
|
+
return document.querySelector('button,[role="button"],[id^="headlessui-listbox-button-"]');
|
|
663
|
+
}
|
|
664
|
+
export function getListboxButtons() {
|
|
665
|
+
return Array.from(document.querySelectorAll('button,[role="button"]'));
|
|
666
|
+
}
|
|
667
|
+
export function getListbox() {
|
|
668
|
+
return document.querySelector('[role="listbox"]');
|
|
669
|
+
}
|
|
670
|
+
export function getListboxes() {
|
|
671
|
+
return Array.from(document.querySelectorAll('[role="listbox"]'));
|
|
672
|
+
}
|
|
673
|
+
export function getListboxOptions() {
|
|
674
|
+
return Array.from(document.querySelectorAll('[role="option"]'));
|
|
675
|
+
}
|
|
676
|
+
// ---
|
|
677
|
+
export var ListboxState;
|
|
678
|
+
(function (ListboxState) {
|
|
679
|
+
/** The listbox is visible to the user. */
|
|
680
|
+
ListboxState[ListboxState["Visible"] = 0] = "Visible";
|
|
681
|
+
/** The listbox is **not** visible to the user. It's still in the DOM, but it is hidden. */
|
|
682
|
+
ListboxState[ListboxState["InvisibleHidden"] = 1] = "InvisibleHidden";
|
|
683
|
+
/** The listbox is **not** visible to the user. It's not in the DOM, it is unmounted. */
|
|
684
|
+
ListboxState[ListboxState["InvisibleUnmounted"] = 2] = "InvisibleUnmounted";
|
|
685
|
+
})(ListboxState || (ListboxState = {}));
|
|
686
|
+
export var ListboxMode;
|
|
687
|
+
(function (ListboxMode) {
|
|
688
|
+
/** The listbox is in the `single` mode. */
|
|
689
|
+
ListboxMode[ListboxMode["Single"] = 0] = "Single";
|
|
690
|
+
/** The listbox is in the `multiple` mode. */
|
|
691
|
+
ListboxMode[ListboxMode["Multiple"] = 1] = "Multiple";
|
|
692
|
+
})(ListboxMode || (ListboxMode = {}));
|
|
693
|
+
export function assertListbox(options, listbox = getListbox()) {
|
|
694
|
+
let { orientation = "vertical" } = options;
|
|
695
|
+
try {
|
|
696
|
+
switch (options.state) {
|
|
697
|
+
case ListboxState.InvisibleHidden:
|
|
698
|
+
if (listbox === null)
|
|
699
|
+
return expect(listbox).not.toBe(null);
|
|
700
|
+
assertHidden(listbox);
|
|
701
|
+
expect(listbox).toHaveAttribute("aria-labelledby");
|
|
702
|
+
expect(listbox).toHaveAttribute("aria-orientation", orientation);
|
|
703
|
+
expect(listbox).toHaveAttribute("role", "listbox");
|
|
704
|
+
if (options.textContent)
|
|
705
|
+
expect(listbox).toHaveTextContent(options.textContent);
|
|
706
|
+
for (let attributeName in options.attributes) {
|
|
707
|
+
expect(listbox).toHaveAttribute(attributeName, options.attributes[attributeName]);
|
|
708
|
+
}
|
|
709
|
+
break;
|
|
710
|
+
case ListboxState.Visible:
|
|
711
|
+
if (listbox === null)
|
|
712
|
+
return expect(listbox).not.toBe(null);
|
|
713
|
+
assertVisible(listbox);
|
|
714
|
+
expect(listbox).toHaveAttribute("aria-labelledby");
|
|
715
|
+
expect(listbox).toHaveAttribute("aria-orientation", orientation);
|
|
716
|
+
expect(listbox).toHaveAttribute("role", "listbox");
|
|
717
|
+
if (options.mode && options.mode === ListboxMode.Multiple) {
|
|
718
|
+
expect(listbox).toHaveAttribute("aria-multiselectable", "true");
|
|
719
|
+
}
|
|
720
|
+
if (options.textContent)
|
|
721
|
+
expect(listbox).toHaveTextContent(options.textContent);
|
|
722
|
+
for (let attributeName in options.attributes) {
|
|
723
|
+
expect(listbox).toHaveAttribute(attributeName, options.attributes[attributeName]);
|
|
724
|
+
}
|
|
725
|
+
break;
|
|
726
|
+
case ListboxState.InvisibleUnmounted:
|
|
727
|
+
expect(listbox).toBe(null);
|
|
728
|
+
break;
|
|
729
|
+
default:
|
|
730
|
+
assertNever(options.state);
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
catch (err) {
|
|
734
|
+
if (err instanceof Error)
|
|
735
|
+
Error.captureStackTrace(err, assertListbox);
|
|
736
|
+
throw err;
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
export function assertListboxButton(options, button = getListboxButton()) {
|
|
740
|
+
try {
|
|
741
|
+
if (button === null)
|
|
742
|
+
return expect(button).not.toBe(null);
|
|
743
|
+
// Ensure menu button have these properties
|
|
744
|
+
expect(button).toHaveAttribute("id");
|
|
745
|
+
expect(button).toHaveAttribute("aria-haspopup");
|
|
746
|
+
switch (options.state) {
|
|
747
|
+
case ListboxState.Visible:
|
|
748
|
+
expect(button).toHaveAttribute("aria-controls");
|
|
749
|
+
expect(button).toHaveAttribute("aria-expanded", "true");
|
|
750
|
+
break;
|
|
751
|
+
case ListboxState.InvisibleHidden:
|
|
752
|
+
expect(button).toHaveAttribute("aria-controls");
|
|
753
|
+
expect(button).toHaveAttribute("aria-expanded", "false");
|
|
754
|
+
break;
|
|
755
|
+
case ListboxState.InvisibleUnmounted:
|
|
756
|
+
expect(button).not.toHaveAttribute("aria-controls");
|
|
757
|
+
expect(button).toHaveAttribute("aria-expanded", "false");
|
|
758
|
+
break;
|
|
759
|
+
default:
|
|
760
|
+
assertNever(options.state);
|
|
761
|
+
}
|
|
762
|
+
if (options.textContent) {
|
|
763
|
+
expect(button).toHaveTextContent(options.textContent);
|
|
764
|
+
}
|
|
765
|
+
// Ensure menu button has the following attributes
|
|
766
|
+
for (let attributeName in options.attributes) {
|
|
767
|
+
expect(button).toHaveAttribute(attributeName, options.attributes[attributeName]);
|
|
768
|
+
}
|
|
769
|
+
}
|
|
770
|
+
catch (err) {
|
|
771
|
+
if (err instanceof Error)
|
|
772
|
+
Error.captureStackTrace(err, assertListboxButton);
|
|
773
|
+
throw err;
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
export function assertListboxLabel(options, label = getListboxLabel()) {
|
|
777
|
+
try {
|
|
778
|
+
if (label === null)
|
|
779
|
+
return expect(label).not.toBe(null);
|
|
780
|
+
// Ensure menu button have these properties
|
|
781
|
+
expect(label).toHaveAttribute("id");
|
|
782
|
+
if (options.textContent) {
|
|
783
|
+
expect(label).toHaveTextContent(options.textContent);
|
|
784
|
+
}
|
|
785
|
+
if (options.tag) {
|
|
786
|
+
expect(label.tagName.toLowerCase()).toBe(options.tag);
|
|
787
|
+
}
|
|
788
|
+
// Ensure menu button has the following attributes
|
|
789
|
+
for (let attributeName in options.attributes) {
|
|
790
|
+
expect(label).toHaveAttribute(attributeName, options.attributes[attributeName]);
|
|
791
|
+
}
|
|
792
|
+
}
|
|
793
|
+
catch (err) {
|
|
794
|
+
if (err instanceof Error)
|
|
795
|
+
Error.captureStackTrace(err, assertListboxLabel);
|
|
796
|
+
throw err;
|
|
797
|
+
}
|
|
798
|
+
}
|
|
799
|
+
export function assertListboxButtonLinkedWithListbox(button = getListboxButton(), listbox = getListbox()) {
|
|
800
|
+
try {
|
|
801
|
+
if (button === null)
|
|
802
|
+
return expect(button).not.toBe(null);
|
|
803
|
+
if (listbox === null)
|
|
804
|
+
return expect(listbox).not.toBe(null);
|
|
805
|
+
// Ensure link between button & listbox is correct
|
|
806
|
+
expect(button).toHaveAttribute("aria-controls", listbox.getAttribute("id"));
|
|
807
|
+
expect(listbox).toHaveAttribute("aria-labelledby", button.getAttribute("id"));
|
|
808
|
+
}
|
|
809
|
+
catch (err) {
|
|
810
|
+
if (err instanceof Error)
|
|
811
|
+
Error.captureStackTrace(err, assertListboxButtonLinkedWithListbox);
|
|
812
|
+
throw err;
|
|
813
|
+
}
|
|
814
|
+
}
|
|
815
|
+
export function assertListboxLabelLinkedWithListbox(label = getListboxLabel(), listbox = getListbox()) {
|
|
816
|
+
try {
|
|
817
|
+
if (label === null)
|
|
818
|
+
return expect(label).not.toBe(null);
|
|
819
|
+
if (listbox === null)
|
|
820
|
+
return expect(listbox).not.toBe(null);
|
|
821
|
+
expect(listbox).toHaveAttribute("aria-labelledby", label.getAttribute("id"));
|
|
822
|
+
}
|
|
823
|
+
catch (err) {
|
|
824
|
+
if (err instanceof Error)
|
|
825
|
+
Error.captureStackTrace(err, assertListboxLabelLinkedWithListbox);
|
|
826
|
+
throw err;
|
|
827
|
+
}
|
|
828
|
+
}
|
|
829
|
+
export function assertListboxButtonLinkedWithListboxLabel(button = getListboxButton(), label = getListboxLabel()) {
|
|
830
|
+
try {
|
|
831
|
+
if (button === null)
|
|
832
|
+
return expect(button).not.toBe(null);
|
|
833
|
+
if (label === null)
|
|
834
|
+
return expect(label).not.toBe(null);
|
|
835
|
+
// Ensure link between button & label is correct
|
|
836
|
+
expect(button).toHaveAttribute("aria-labelledby", `${label.id} ${button.id}`);
|
|
837
|
+
}
|
|
838
|
+
catch (err) {
|
|
839
|
+
if (err instanceof Error)
|
|
840
|
+
Error.captureStackTrace(err, assertListboxButtonLinkedWithListboxLabel);
|
|
841
|
+
throw err;
|
|
842
|
+
}
|
|
843
|
+
}
|
|
844
|
+
export function assertActiveListboxOption(item, listbox = getListbox()) {
|
|
845
|
+
try {
|
|
846
|
+
if (listbox === null)
|
|
847
|
+
return expect(listbox).not.toBe(null);
|
|
848
|
+
if (item === null)
|
|
849
|
+
return expect(item).not.toBe(null);
|
|
850
|
+
// Ensure link between listbox & listbox item is correct
|
|
851
|
+
expect(listbox).toHaveAttribute("aria-activedescendant", item.getAttribute("id"));
|
|
852
|
+
}
|
|
853
|
+
catch (err) {
|
|
854
|
+
if (err instanceof Error)
|
|
855
|
+
Error.captureStackTrace(err, assertActiveListboxOption);
|
|
856
|
+
throw err;
|
|
857
|
+
}
|
|
858
|
+
}
|
|
859
|
+
export function assertNoActiveListboxOption(listbox = getListbox()) {
|
|
860
|
+
try {
|
|
861
|
+
if (listbox === null)
|
|
862
|
+
return expect(listbox).not.toBe(null);
|
|
863
|
+
// Ensure we don't have an active listbox
|
|
864
|
+
expect(listbox).not.toHaveAttribute("aria-activedescendant");
|
|
865
|
+
}
|
|
866
|
+
catch (err) {
|
|
867
|
+
if (err instanceof Error)
|
|
868
|
+
Error.captureStackTrace(err, assertNoActiveListboxOption);
|
|
869
|
+
throw err;
|
|
870
|
+
}
|
|
871
|
+
}
|
|
872
|
+
export function assertNoSelectedListboxOption(items = getListboxOptions()) {
|
|
873
|
+
try {
|
|
874
|
+
for (let item of items)
|
|
875
|
+
expect(item).toHaveAttribute("aria-selected", "false");
|
|
876
|
+
}
|
|
877
|
+
catch (err) {
|
|
878
|
+
if (err instanceof Error)
|
|
879
|
+
Error.captureStackTrace(err, assertNoSelectedListboxOption);
|
|
880
|
+
throw err;
|
|
881
|
+
}
|
|
882
|
+
}
|
|
883
|
+
export function assertListboxOption(item, options) {
|
|
884
|
+
try {
|
|
885
|
+
if (item === null)
|
|
886
|
+
return expect(item).not.toBe(null);
|
|
887
|
+
// Check that some attributes exists, doesn't really matter what the values are at this point in
|
|
888
|
+
// time, we just require them.
|
|
889
|
+
expect(item).toHaveAttribute("id");
|
|
890
|
+
// Check that we have the correct values for certain attributes
|
|
891
|
+
expect(item).toHaveAttribute("role", "option");
|
|
892
|
+
if (!item.getAttribute("aria-disabled"))
|
|
893
|
+
expect(item).toHaveAttribute("tabindex", "-1");
|
|
894
|
+
// Ensure listbox button has the following attributes
|
|
895
|
+
if (!options)
|
|
896
|
+
return;
|
|
897
|
+
for (let attributeName in options.attributes) {
|
|
898
|
+
expect(item).toHaveAttribute(attributeName, options.attributes[attributeName]);
|
|
899
|
+
}
|
|
900
|
+
if (options.tag) {
|
|
901
|
+
expect(item.tagName.toLowerCase()).toBe(options.tag);
|
|
902
|
+
}
|
|
903
|
+
if (options.selected != null) {
|
|
904
|
+
return expect(item).toHaveAttribute("aria-selected", options.selected ? "true" : "false");
|
|
905
|
+
}
|
|
906
|
+
}
|
|
907
|
+
catch (err) {
|
|
908
|
+
if (err instanceof Error)
|
|
909
|
+
Error.captureStackTrace(err, assertListboxOption);
|
|
910
|
+
throw err;
|
|
911
|
+
}
|
|
912
|
+
}
|
|
913
|
+
// ---
|
|
914
|
+
export function getSwitch() {
|
|
915
|
+
return document.querySelector('[role="switch"]');
|
|
916
|
+
}
|
|
917
|
+
export function getSwitchLabel() {
|
|
918
|
+
return document.querySelector('label,[id^="headlessui-switch-label"],[id^="headlessui-label"]');
|
|
919
|
+
}
|
|
920
|
+
// ---
|
|
921
|
+
export var SwitchState;
|
|
922
|
+
(function (SwitchState) {
|
|
923
|
+
SwitchState[SwitchState["On"] = 0] = "On";
|
|
924
|
+
SwitchState[SwitchState["Off"] = 1] = "Off";
|
|
925
|
+
})(SwitchState || (SwitchState = {}));
|
|
926
|
+
export function assertSwitch(options, switchElement = getSwitch()) {
|
|
927
|
+
try {
|
|
928
|
+
if (switchElement === null)
|
|
929
|
+
return expect(switchElement).not.toBe(null);
|
|
930
|
+
expect(switchElement).toHaveAttribute("role", "switch");
|
|
931
|
+
let tabIndex = Number(switchElement.getAttribute("tabindex") ?? "0");
|
|
932
|
+
expect(tabIndex).toBeGreaterThanOrEqual(0);
|
|
933
|
+
if (options.textContent) {
|
|
934
|
+
expect(switchElement).toHaveTextContent(options.textContent);
|
|
935
|
+
}
|
|
936
|
+
if (options.tag) {
|
|
937
|
+
expect(switchElement.tagName.toLowerCase()).toBe(options.tag);
|
|
938
|
+
}
|
|
939
|
+
if (options.label) {
|
|
940
|
+
assertLabelValue(switchElement, options.label);
|
|
941
|
+
}
|
|
942
|
+
if (options.description) {
|
|
943
|
+
assertDescriptionValue(switchElement, options.description);
|
|
944
|
+
}
|
|
945
|
+
switch (options.state) {
|
|
946
|
+
case SwitchState.On:
|
|
947
|
+
expect(switchElement).toHaveAttribute("aria-checked", "true");
|
|
948
|
+
break;
|
|
949
|
+
case SwitchState.Off:
|
|
950
|
+
expect(switchElement).toHaveAttribute("aria-checked", "false");
|
|
951
|
+
break;
|
|
952
|
+
default:
|
|
953
|
+
assertNever(options.state);
|
|
954
|
+
}
|
|
955
|
+
// Ensure disclosure button has the following attributes
|
|
956
|
+
for (let attributeName in options.attributes) {
|
|
957
|
+
expect(switchElement).toHaveAttribute(attributeName, options.attributes[attributeName]);
|
|
958
|
+
}
|
|
959
|
+
}
|
|
960
|
+
catch (err) {
|
|
961
|
+
if (err instanceof Error)
|
|
962
|
+
Error.captureStackTrace(err, assertSwitch);
|
|
963
|
+
throw err;
|
|
964
|
+
}
|
|
965
|
+
}
|
|
966
|
+
// ---
|
|
967
|
+
export function getDisclosureButton() {
|
|
968
|
+
return document.querySelector('[id^="headlessui-disclosure-button-"]');
|
|
969
|
+
}
|
|
970
|
+
export function getDisclosurePanel() {
|
|
971
|
+
return document.querySelector('[id^="headlessui-disclosure-panel-"]');
|
|
972
|
+
}
|
|
973
|
+
// ---
|
|
974
|
+
export var DisclosureState;
|
|
975
|
+
(function (DisclosureState) {
|
|
976
|
+
/** The disclosure is visible to the user. */
|
|
977
|
+
DisclosureState[DisclosureState["Visible"] = 0] = "Visible";
|
|
978
|
+
/** The disclosure is **not** visible to the user. It's still in the DOM, but it is hidden. */
|
|
979
|
+
DisclosureState[DisclosureState["InvisibleHidden"] = 1] = "InvisibleHidden";
|
|
980
|
+
/** The disclosure is **not** visible to the user. It's not in the DOM, it is unmounted. */
|
|
981
|
+
DisclosureState[DisclosureState["InvisibleUnmounted"] = 2] = "InvisibleUnmounted";
|
|
982
|
+
})(DisclosureState || (DisclosureState = {}));
|
|
983
|
+
// ---
|
|
984
|
+
export function assertDisclosureButton(options, button = getDisclosureButton()) {
|
|
985
|
+
try {
|
|
986
|
+
if (button === null)
|
|
987
|
+
return expect(button).not.toBe(null);
|
|
988
|
+
// Ensure disclosure button have these properties
|
|
989
|
+
expect(button).toHaveAttribute("id");
|
|
990
|
+
switch (options.state) {
|
|
991
|
+
case DisclosureState.Visible:
|
|
992
|
+
expect(button).toHaveAttribute("aria-controls");
|
|
993
|
+
expect(button).toHaveAttribute("aria-expanded", "true");
|
|
994
|
+
break;
|
|
995
|
+
case DisclosureState.InvisibleHidden:
|
|
996
|
+
expect(button).toHaveAttribute("aria-controls");
|
|
997
|
+
expect(button).toHaveAttribute("aria-expanded", "false");
|
|
998
|
+
break;
|
|
999
|
+
case DisclosureState.InvisibleUnmounted:
|
|
1000
|
+
expect(button).not.toHaveAttribute("aria-controls");
|
|
1001
|
+
expect(button).toHaveAttribute("aria-expanded", "false");
|
|
1002
|
+
break;
|
|
1003
|
+
default:
|
|
1004
|
+
assertNever(options.state);
|
|
1005
|
+
}
|
|
1006
|
+
if (options.textContent) {
|
|
1007
|
+
expect(button).toHaveTextContent(options.textContent);
|
|
1008
|
+
}
|
|
1009
|
+
// Ensure disclosure button has the following attributes
|
|
1010
|
+
for (let attributeName in options.attributes) {
|
|
1011
|
+
expect(button).toHaveAttribute(attributeName, options.attributes[attributeName]);
|
|
1012
|
+
}
|
|
1013
|
+
}
|
|
1014
|
+
catch (err) {
|
|
1015
|
+
if (err instanceof Error)
|
|
1016
|
+
Error.captureStackTrace(err, assertDisclosureButton);
|
|
1017
|
+
throw err;
|
|
1018
|
+
}
|
|
1019
|
+
}
|
|
1020
|
+
export function assertDisclosurePanel(options, panel = getDisclosurePanel()) {
|
|
1021
|
+
try {
|
|
1022
|
+
switch (options.state) {
|
|
1023
|
+
case DisclosureState.InvisibleHidden:
|
|
1024
|
+
if (panel === null)
|
|
1025
|
+
return expect(panel).not.toBe(null);
|
|
1026
|
+
assertHidden(panel);
|
|
1027
|
+
if (options.textContent)
|
|
1028
|
+
expect(panel).toHaveTextContent(options.textContent);
|
|
1029
|
+
for (let attributeName in options.attributes) {
|
|
1030
|
+
expect(panel).toHaveAttribute(attributeName, options.attributes[attributeName]);
|
|
1031
|
+
}
|
|
1032
|
+
break;
|
|
1033
|
+
case DisclosureState.Visible:
|
|
1034
|
+
if (panel === null)
|
|
1035
|
+
return expect(panel).not.toBe(null);
|
|
1036
|
+
assertVisible(panel);
|
|
1037
|
+
if (options.textContent)
|
|
1038
|
+
expect(panel).toHaveTextContent(options.textContent);
|
|
1039
|
+
for (let attributeName in options.attributes) {
|
|
1040
|
+
expect(panel).toHaveAttribute(attributeName, options.attributes[attributeName]);
|
|
1041
|
+
}
|
|
1042
|
+
break;
|
|
1043
|
+
case DisclosureState.InvisibleUnmounted:
|
|
1044
|
+
expect(panel).toBe(null);
|
|
1045
|
+
break;
|
|
1046
|
+
default:
|
|
1047
|
+
assertNever(options.state);
|
|
1048
|
+
}
|
|
1049
|
+
}
|
|
1050
|
+
catch (err) {
|
|
1051
|
+
if (err instanceof Error)
|
|
1052
|
+
Error.captureStackTrace(err, assertDisclosurePanel);
|
|
1053
|
+
throw err;
|
|
1054
|
+
}
|
|
1055
|
+
}
|
|
1056
|
+
// ---
|
|
1057
|
+
export function getPopoverButton() {
|
|
1058
|
+
return document.querySelector('[id^="headlessui-popover-button-"]');
|
|
1059
|
+
}
|
|
1060
|
+
export function getPopoverPanel() {
|
|
1061
|
+
return document.querySelector('[id^="headlessui-popover-panel-"]');
|
|
1062
|
+
}
|
|
1063
|
+
export function getPopoverOverlay() {
|
|
1064
|
+
return document.querySelector('[id^="headlessui-popover-backdrop-"]');
|
|
1065
|
+
}
|
|
1066
|
+
// ---
|
|
1067
|
+
export var PopoverState;
|
|
1068
|
+
(function (PopoverState) {
|
|
1069
|
+
/** The popover is visible to the user. */
|
|
1070
|
+
PopoverState[PopoverState["Visible"] = 0] = "Visible";
|
|
1071
|
+
/** The popover is **not** visible to the user. It's still in the DOM, but it is hidden. */
|
|
1072
|
+
PopoverState[PopoverState["InvisibleHidden"] = 1] = "InvisibleHidden";
|
|
1073
|
+
/** The popover is **not** visible to the user. It's not in the DOM, it is unmounted. */
|
|
1074
|
+
PopoverState[PopoverState["InvisibleUnmounted"] = 2] = "InvisibleUnmounted";
|
|
1075
|
+
})(PopoverState || (PopoverState = {}));
|
|
1076
|
+
// ---
|
|
1077
|
+
export function assertPopoverButton(options, button = getPopoverButton()) {
|
|
1078
|
+
try {
|
|
1079
|
+
if (button === null)
|
|
1080
|
+
return expect(button).not.toBe(null);
|
|
1081
|
+
// Ensure popover button have these properties
|
|
1082
|
+
expect(button).toHaveAttribute("id");
|
|
1083
|
+
switch (options.state) {
|
|
1084
|
+
case PopoverState.Visible:
|
|
1085
|
+
expect(button).toHaveAttribute("aria-controls");
|
|
1086
|
+
expect(button).toHaveAttribute("aria-expanded", "true");
|
|
1087
|
+
break;
|
|
1088
|
+
case PopoverState.InvisibleHidden:
|
|
1089
|
+
expect(button).toHaveAttribute("aria-controls");
|
|
1090
|
+
expect(button).toHaveAttribute("aria-expanded", "false");
|
|
1091
|
+
break;
|
|
1092
|
+
case PopoverState.InvisibleUnmounted:
|
|
1093
|
+
expect(button).not.toHaveAttribute("aria-controls");
|
|
1094
|
+
expect(button).toHaveAttribute("aria-expanded", "false");
|
|
1095
|
+
break;
|
|
1096
|
+
default:
|
|
1097
|
+
assertNever(options.state);
|
|
1098
|
+
}
|
|
1099
|
+
if (options.textContent) {
|
|
1100
|
+
expect(button).toHaveTextContent(options.textContent);
|
|
1101
|
+
}
|
|
1102
|
+
// Ensure popover button has the following attributes
|
|
1103
|
+
for (let attributeName in options.attributes) {
|
|
1104
|
+
expect(button).toHaveAttribute(attributeName, options.attributes[attributeName]);
|
|
1105
|
+
}
|
|
1106
|
+
}
|
|
1107
|
+
catch (err) {
|
|
1108
|
+
if (err instanceof Error)
|
|
1109
|
+
Error.captureStackTrace(err, assertPopoverButton);
|
|
1110
|
+
throw err;
|
|
1111
|
+
}
|
|
1112
|
+
}
|
|
1113
|
+
export function assertPopoverPanel(options, panel = getPopoverPanel()) {
|
|
1114
|
+
try {
|
|
1115
|
+
switch (options.state) {
|
|
1116
|
+
case PopoverState.InvisibleHidden:
|
|
1117
|
+
if (panel === null)
|
|
1118
|
+
return expect(panel).not.toBe(null);
|
|
1119
|
+
assertHidden(panel);
|
|
1120
|
+
if (options.textContent)
|
|
1121
|
+
expect(panel).toHaveTextContent(options.textContent);
|
|
1122
|
+
for (let attributeName in options.attributes) {
|
|
1123
|
+
expect(panel).toHaveAttribute(attributeName, options.attributes[attributeName]);
|
|
1124
|
+
}
|
|
1125
|
+
break;
|
|
1126
|
+
case PopoverState.Visible:
|
|
1127
|
+
if (panel === null)
|
|
1128
|
+
return expect(panel).not.toBe(null);
|
|
1129
|
+
assertVisible(panel);
|
|
1130
|
+
if (options.textContent)
|
|
1131
|
+
expect(panel).toHaveTextContent(options.textContent);
|
|
1132
|
+
for (let attributeName in options.attributes) {
|
|
1133
|
+
expect(panel).toHaveAttribute(attributeName, options.attributes[attributeName]);
|
|
1134
|
+
}
|
|
1135
|
+
break;
|
|
1136
|
+
case PopoverState.InvisibleUnmounted:
|
|
1137
|
+
expect(panel).toBe(null);
|
|
1138
|
+
break;
|
|
1139
|
+
default:
|
|
1140
|
+
assertNever(options.state);
|
|
1141
|
+
}
|
|
1142
|
+
}
|
|
1143
|
+
catch (err) {
|
|
1144
|
+
if (err instanceof Error)
|
|
1145
|
+
Error.captureStackTrace(err, assertPopoverPanel);
|
|
1146
|
+
throw err;
|
|
1147
|
+
}
|
|
1148
|
+
}
|
|
1149
|
+
// ---
|
|
1150
|
+
export function assertLabelValue(element, value) {
|
|
1151
|
+
if (element === null)
|
|
1152
|
+
return expect(element).not.toBe(null);
|
|
1153
|
+
if (element.hasAttribute("aria-labelledby")) {
|
|
1154
|
+
let ids = element.getAttribute("aria-labelledby").split(" ");
|
|
1155
|
+
expect(ids.map((id) => document.getElementById(id)?.textContent).join(" ")).toEqual(value);
|
|
1156
|
+
return;
|
|
1157
|
+
}
|
|
1158
|
+
if (element.hasAttribute("aria-label")) {
|
|
1159
|
+
expect(element).toHaveAttribute("aria-label", value);
|
|
1160
|
+
return;
|
|
1161
|
+
}
|
|
1162
|
+
if (element.hasAttribute("id") && document.querySelectorAll(`[for="${element.id}"]`).length > 0) {
|
|
1163
|
+
expect(document.querySelector(`[for="${element.id}"]`)).toHaveTextContent(value);
|
|
1164
|
+
return;
|
|
1165
|
+
}
|
|
1166
|
+
expect(element).toHaveTextContent(value);
|
|
1167
|
+
}
|
|
1168
|
+
// ---
|
|
1169
|
+
export function assertDescriptionValue(element, value) {
|
|
1170
|
+
if (element === null)
|
|
1171
|
+
return expect(element).not.toBe(null);
|
|
1172
|
+
let id = element.getAttribute("aria-describedby");
|
|
1173
|
+
expect(document.getElementById(id)?.textContent).toEqual(value);
|
|
1174
|
+
}
|
|
1175
|
+
// ---
|
|
1176
|
+
export function getDialog() {
|
|
1177
|
+
return document.querySelector('[role="dialog"],[role="alertdialog"]');
|
|
1178
|
+
}
|
|
1179
|
+
export function getDialogs() {
|
|
1180
|
+
return Array.from(document.querySelectorAll('[role="dialog"],[role="alertdialog"]'));
|
|
1181
|
+
}
|
|
1182
|
+
export function getDialogTitle() {
|
|
1183
|
+
return document.querySelector('[id^="headlessui-dialog-title-"]');
|
|
1184
|
+
}
|
|
1185
|
+
export function getDialogDescription() {
|
|
1186
|
+
return document.querySelector('[id^="headlessui-description-"]');
|
|
1187
|
+
}
|
|
1188
|
+
// ---
|
|
1189
|
+
export var DialogState;
|
|
1190
|
+
(function (DialogState) {
|
|
1191
|
+
/** The dialog is visible to the user. */
|
|
1192
|
+
DialogState[DialogState["Visible"] = 0] = "Visible";
|
|
1193
|
+
/** The dialog is **not** visible to the user. It's still in the DOM, but it is hidden. */
|
|
1194
|
+
DialogState[DialogState["InvisibleHidden"] = 1] = "InvisibleHidden";
|
|
1195
|
+
/** The dialog is **not** visible to the user. It's not in the DOM, it is unmounted. */
|
|
1196
|
+
DialogState[DialogState["InvisibleUnmounted"] = 2] = "InvisibleUnmounted";
|
|
1197
|
+
})(DialogState || (DialogState = {}));
|
|
1198
|
+
// ---
|
|
1199
|
+
export function assertDialog(options, dialog = getDialog()) {
|
|
1200
|
+
try {
|
|
1201
|
+
switch (options.state) {
|
|
1202
|
+
case DialogState.InvisibleHidden:
|
|
1203
|
+
if (dialog === null)
|
|
1204
|
+
return expect(dialog).not.toBe(null);
|
|
1205
|
+
assertHidden(dialog);
|
|
1206
|
+
expect(dialog).toHaveAttribute("role", options.attributes?.["role"] ?? "dialog");
|
|
1207
|
+
expect(dialog).not.toHaveAttribute("aria-modal", "true");
|
|
1208
|
+
if (options.textContent)
|
|
1209
|
+
expect(dialog).toHaveTextContent(options.textContent);
|
|
1210
|
+
for (let attributeName in options.attributes) {
|
|
1211
|
+
expect(dialog).toHaveAttribute(attributeName, options.attributes[attributeName]);
|
|
1212
|
+
}
|
|
1213
|
+
break;
|
|
1214
|
+
case DialogState.Visible:
|
|
1215
|
+
if (dialog === null)
|
|
1216
|
+
return expect(dialog).not.toBe(null);
|
|
1217
|
+
assertVisible(dialog);
|
|
1218
|
+
expect(dialog).toHaveAttribute("role", options.attributes?.["role"] ?? "dialog");
|
|
1219
|
+
expect(dialog).toHaveAttribute("aria-modal", "true");
|
|
1220
|
+
if (options.textContent)
|
|
1221
|
+
expect(dialog).toHaveTextContent(options.textContent);
|
|
1222
|
+
for (let attributeName in options.attributes) {
|
|
1223
|
+
expect(dialog).toHaveAttribute(attributeName, options.attributes[attributeName]);
|
|
1224
|
+
}
|
|
1225
|
+
break;
|
|
1226
|
+
case DialogState.InvisibleUnmounted:
|
|
1227
|
+
expect(dialog).toBe(null);
|
|
1228
|
+
break;
|
|
1229
|
+
default:
|
|
1230
|
+
assertNever(options.state);
|
|
1231
|
+
}
|
|
1232
|
+
}
|
|
1233
|
+
catch (err) {
|
|
1234
|
+
if (err instanceof Error)
|
|
1235
|
+
Error.captureStackTrace(err, assertDialog);
|
|
1236
|
+
throw err;
|
|
1237
|
+
}
|
|
1238
|
+
}
|
|
1239
|
+
export function assertDialogTitle(options, title = getDialogTitle(), dialog = getDialog()) {
|
|
1240
|
+
try {
|
|
1241
|
+
switch (options.state) {
|
|
1242
|
+
case DialogState.InvisibleHidden:
|
|
1243
|
+
if (title === null)
|
|
1244
|
+
return expect(title).not.toBe(null);
|
|
1245
|
+
if (dialog === null)
|
|
1246
|
+
return expect(dialog).not.toBe(null);
|
|
1247
|
+
assertHidden(title);
|
|
1248
|
+
expect(title).toHaveAttribute("id");
|
|
1249
|
+
expect(dialog).toHaveAttribute("aria-labelledby", title.id);
|
|
1250
|
+
if (options.textContent)
|
|
1251
|
+
expect(title).toHaveTextContent(options.textContent);
|
|
1252
|
+
for (let attributeName in options.attributes) {
|
|
1253
|
+
expect(title).toHaveAttribute(attributeName, options.attributes[attributeName]);
|
|
1254
|
+
}
|
|
1255
|
+
break;
|
|
1256
|
+
case DialogState.Visible:
|
|
1257
|
+
if (title === null)
|
|
1258
|
+
return expect(title).not.toBe(null);
|
|
1259
|
+
if (dialog === null)
|
|
1260
|
+
return expect(dialog).not.toBe(null);
|
|
1261
|
+
assertVisible(title);
|
|
1262
|
+
expect(title).toHaveAttribute("id");
|
|
1263
|
+
expect(dialog).toHaveAttribute("aria-labelledby", title.id);
|
|
1264
|
+
if (options.textContent)
|
|
1265
|
+
expect(title).toHaveTextContent(options.textContent);
|
|
1266
|
+
for (let attributeName in options.attributes) {
|
|
1267
|
+
expect(title).toHaveAttribute(attributeName, options.attributes[attributeName]);
|
|
1268
|
+
}
|
|
1269
|
+
break;
|
|
1270
|
+
case DialogState.InvisibleUnmounted:
|
|
1271
|
+
expect(title).toBe(null);
|
|
1272
|
+
break;
|
|
1273
|
+
default:
|
|
1274
|
+
assertNever(options.state);
|
|
1275
|
+
}
|
|
1276
|
+
}
|
|
1277
|
+
catch (err) {
|
|
1278
|
+
if (err instanceof Error)
|
|
1279
|
+
Error.captureStackTrace(err, assertDialogTitle);
|
|
1280
|
+
throw err;
|
|
1281
|
+
}
|
|
1282
|
+
}
|
|
1283
|
+
export function assertDialogDescription(options, description = getDialogDescription(), dialog = getDialog()) {
|
|
1284
|
+
try {
|
|
1285
|
+
switch (options.state) {
|
|
1286
|
+
case DialogState.InvisibleHidden:
|
|
1287
|
+
if (description === null)
|
|
1288
|
+
return expect(description).not.toBe(null);
|
|
1289
|
+
if (dialog === null)
|
|
1290
|
+
return expect(dialog).not.toBe(null);
|
|
1291
|
+
assertHidden(description);
|
|
1292
|
+
expect(description).toHaveAttribute("id");
|
|
1293
|
+
expect(dialog).toHaveAttribute("aria-describedby", description.id);
|
|
1294
|
+
if (options.textContent)
|
|
1295
|
+
expect(description).toHaveTextContent(options.textContent);
|
|
1296
|
+
for (let attributeName in options.attributes) {
|
|
1297
|
+
expect(description).toHaveAttribute(attributeName, options.attributes[attributeName]);
|
|
1298
|
+
}
|
|
1299
|
+
break;
|
|
1300
|
+
case DialogState.Visible:
|
|
1301
|
+
if (description === null)
|
|
1302
|
+
return expect(description).not.toBe(null);
|
|
1303
|
+
if (dialog === null)
|
|
1304
|
+
return expect(dialog).not.toBe(null);
|
|
1305
|
+
assertVisible(description);
|
|
1306
|
+
expect(description).toHaveAttribute("id");
|
|
1307
|
+
expect(dialog).toHaveAttribute("aria-describedby", description.id);
|
|
1308
|
+
if (options.textContent)
|
|
1309
|
+
expect(description).toHaveTextContent(options.textContent);
|
|
1310
|
+
for (let attributeName in options.attributes) {
|
|
1311
|
+
expect(description).toHaveAttribute(attributeName, options.attributes[attributeName]);
|
|
1312
|
+
}
|
|
1313
|
+
break;
|
|
1314
|
+
case DialogState.InvisibleUnmounted:
|
|
1315
|
+
expect(description).toBe(null);
|
|
1316
|
+
break;
|
|
1317
|
+
default:
|
|
1318
|
+
assertNever(options.state);
|
|
1319
|
+
}
|
|
1320
|
+
}
|
|
1321
|
+
catch (err) {
|
|
1322
|
+
if (err instanceof Error)
|
|
1323
|
+
Error.captureStackTrace(err, assertDialogDescription);
|
|
1324
|
+
throw err;
|
|
1325
|
+
}
|
|
1326
|
+
}
|
|
1327
|
+
// ---
|
|
1328
|
+
export function getRadioGroup() {
|
|
1329
|
+
return document.querySelector('[role="radiogroup"]');
|
|
1330
|
+
}
|
|
1331
|
+
export function getRadioGroupLabel() {
|
|
1332
|
+
return document.querySelector('label,[id^="headlessui-label-"]');
|
|
1333
|
+
}
|
|
1334
|
+
export function getRadioGroupOptions() {
|
|
1335
|
+
return Array.from(document.querySelectorAll('[id^="headlessui-radiogroup-option-"]'));
|
|
1336
|
+
}
|
|
1337
|
+
// ---
|
|
1338
|
+
export function assertRadioGroupLabel(options, label = getRadioGroupLabel(), radioGroup = getRadioGroup()) {
|
|
1339
|
+
try {
|
|
1340
|
+
if (label === null)
|
|
1341
|
+
return expect(label).not.toBe(null);
|
|
1342
|
+
if (radioGroup === null)
|
|
1343
|
+
return expect(radioGroup).not.toBe(null);
|
|
1344
|
+
expect(label).toHaveAttribute("id");
|
|
1345
|
+
expect(radioGroup).toHaveAttribute("aria-labelledby", label.id);
|
|
1346
|
+
if (options.textContent)
|
|
1347
|
+
expect(label).toHaveTextContent(options.textContent);
|
|
1348
|
+
for (let attributeName in options.attributes) {
|
|
1349
|
+
expect(label).toHaveAttribute(attributeName, options.attributes[attributeName]);
|
|
1350
|
+
}
|
|
1351
|
+
}
|
|
1352
|
+
catch (err) {
|
|
1353
|
+
if (err instanceof Error)
|
|
1354
|
+
Error.captureStackTrace(err, assertRadioGroupLabel);
|
|
1355
|
+
throw err;
|
|
1356
|
+
}
|
|
1357
|
+
}
|
|
1358
|
+
// ---
|
|
1359
|
+
export function getTabList() {
|
|
1360
|
+
return document.querySelector('[role="tablist"]');
|
|
1361
|
+
}
|
|
1362
|
+
export function getTabs() {
|
|
1363
|
+
return Array.from(document.querySelectorAll('[id^="headlessui-tabs-tab-"]'));
|
|
1364
|
+
}
|
|
1365
|
+
export function getPanels() {
|
|
1366
|
+
return Array.from(document.querySelectorAll('[id^="headlessui-tabs-panel-"]'));
|
|
1367
|
+
}
|
|
1368
|
+
// ---
|
|
1369
|
+
export function assertTabs({ active, orientation = "horizontal", tabContents = null, panelContents = null, }, list = getTabList(), tabs = getTabs(), panels = getPanels()) {
|
|
1370
|
+
try {
|
|
1371
|
+
if (list === null)
|
|
1372
|
+
return expect(list).not.toBe(null);
|
|
1373
|
+
expect(list).toHaveAttribute("role", "tablist");
|
|
1374
|
+
expect(list).toHaveAttribute("aria-orientation", orientation);
|
|
1375
|
+
let activeTab = Array.from(list.querySelectorAll('[id^="headlessui-tabs-tab-"]'))[active];
|
|
1376
|
+
let activePanel = panels.find((panel) => panel.id === activeTab.getAttribute("aria-controls"));
|
|
1377
|
+
for (let tab of tabs) {
|
|
1378
|
+
expect(tab).toHaveAttribute("id");
|
|
1379
|
+
expect(tab).toHaveAttribute("role", "tab");
|
|
1380
|
+
expect(tab).toHaveAttribute("type", "button");
|
|
1381
|
+
if (tab === activeTab) {
|
|
1382
|
+
expect(tab).toHaveAttribute("aria-selected", "true");
|
|
1383
|
+
expect(tab).toHaveAttribute("tabindex", "0");
|
|
1384
|
+
if (tabContents !== null) {
|
|
1385
|
+
expect(tab.textContent).toBe(tabContents);
|
|
1386
|
+
}
|
|
1387
|
+
}
|
|
1388
|
+
else {
|
|
1389
|
+
expect(tab).toHaveAttribute("aria-selected", "false");
|
|
1390
|
+
expect(tab).toHaveAttribute("tabindex", "-1");
|
|
1391
|
+
}
|
|
1392
|
+
if (tab.hasAttribute("aria-controls")) {
|
|
1393
|
+
let controlsId = tab.getAttribute("aria-controls");
|
|
1394
|
+
let panel = document.getElementById(controlsId);
|
|
1395
|
+
expect(panel).not.toBe(null);
|
|
1396
|
+
expect(panels).toContain(panel);
|
|
1397
|
+
expect(panel).toHaveAttribute("aria-labelledby", tab.id);
|
|
1398
|
+
}
|
|
1399
|
+
}
|
|
1400
|
+
for (let panel of panels) {
|
|
1401
|
+
expect(panel).toHaveAttribute("id");
|
|
1402
|
+
expect(panel).toHaveAttribute("role", "tabpanel");
|
|
1403
|
+
let controlledById = panel.getAttribute("aria-labelledby");
|
|
1404
|
+
let tab = document.getElementById(controlledById);
|
|
1405
|
+
expect(tabs).toContain(tab);
|
|
1406
|
+
expect(tab).toHaveAttribute("aria-controls", panel.id);
|
|
1407
|
+
if (panel === activePanel) {
|
|
1408
|
+
expect(panel).toHaveAttribute("tabindex", "0");
|
|
1409
|
+
if (tabContents !== null) {
|
|
1410
|
+
expect(panel.textContent).toBe(panelContents);
|
|
1411
|
+
}
|
|
1412
|
+
}
|
|
1413
|
+
else {
|
|
1414
|
+
expect(panel).toHaveAttribute("tabindex", "-1");
|
|
1415
|
+
}
|
|
1416
|
+
}
|
|
1417
|
+
}
|
|
1418
|
+
catch (err) {
|
|
1419
|
+
if (err instanceof Error)
|
|
1420
|
+
Error.captureStackTrace(err, assertTabs);
|
|
1421
|
+
throw err;
|
|
1422
|
+
}
|
|
1423
|
+
}
|
|
1424
|
+
// ---
|
|
1425
|
+
export function assertActiveElement(element) {
|
|
1426
|
+
try {
|
|
1427
|
+
if (element === null)
|
|
1428
|
+
return expect(element).not.toBe(null);
|
|
1429
|
+
try {
|
|
1430
|
+
// Jest has a weird bug:
|
|
1431
|
+
// "Cannot assign to read only property 'Symbol(impl)' of object '[object DOMImplementation]'"
|
|
1432
|
+
// when this assertion fails.
|
|
1433
|
+
// Therefore we will catch it when something goes wrong, and just look at the outerHTML string.
|
|
1434
|
+
expect(document.activeElement).toBe(element);
|
|
1435
|
+
}
|
|
1436
|
+
catch (err) {
|
|
1437
|
+
expect(document.activeElement?.outerHTML).toBe(element.outerHTML);
|
|
1438
|
+
}
|
|
1439
|
+
}
|
|
1440
|
+
catch (err) {
|
|
1441
|
+
if (err instanceof Error)
|
|
1442
|
+
Error.captureStackTrace(err, assertActiveElement);
|
|
1443
|
+
throw err;
|
|
1444
|
+
}
|
|
1445
|
+
}
|
|
1446
|
+
export function assertContainsActiveElement(element) {
|
|
1447
|
+
try {
|
|
1448
|
+
if (element === null)
|
|
1449
|
+
return expect(element).not.toBe(null);
|
|
1450
|
+
expect(element.contains(document.activeElement)).toBe(true);
|
|
1451
|
+
}
|
|
1452
|
+
catch (err) {
|
|
1453
|
+
if (err instanceof Error)
|
|
1454
|
+
Error.captureStackTrace(err, assertContainsActiveElement);
|
|
1455
|
+
throw err;
|
|
1456
|
+
}
|
|
1457
|
+
}
|
|
1458
|
+
// ---
|
|
1459
|
+
export function assertHidden(element) {
|
|
1460
|
+
try {
|
|
1461
|
+
if (element === null)
|
|
1462
|
+
return expect(element).not.toBe(null);
|
|
1463
|
+
expect(element).toHaveAttribute("hidden");
|
|
1464
|
+
expect(element).toHaveStyle({ display: "none" });
|
|
1465
|
+
}
|
|
1466
|
+
catch (err) {
|
|
1467
|
+
if (err instanceof Error)
|
|
1468
|
+
Error.captureStackTrace(err, assertHidden);
|
|
1469
|
+
throw err;
|
|
1470
|
+
}
|
|
1471
|
+
}
|
|
1472
|
+
export function assertVisible(element) {
|
|
1473
|
+
try {
|
|
1474
|
+
if (element === null)
|
|
1475
|
+
return expect(element).not.toBe(null);
|
|
1476
|
+
expect(element).not.toHaveAttribute("hidden");
|
|
1477
|
+
expect(element).not.toHaveStyle({ display: "none" });
|
|
1478
|
+
}
|
|
1479
|
+
catch (err) {
|
|
1480
|
+
if (err instanceof Error)
|
|
1481
|
+
Error.captureStackTrace(err, assertVisible);
|
|
1482
|
+
throw err;
|
|
1483
|
+
}
|
|
1484
|
+
}
|
|
1485
|
+
// ---
|
|
1486
|
+
export function assertFocusable(element) {
|
|
1487
|
+
try {
|
|
1488
|
+
if (element === null)
|
|
1489
|
+
return expect(element).not.toBe(null);
|
|
1490
|
+
expect(isFocusableElement(element, FocusableMode.Strict)).toBe(true);
|
|
1491
|
+
}
|
|
1492
|
+
catch (err) {
|
|
1493
|
+
if (err instanceof Error)
|
|
1494
|
+
Error.captureStackTrace(err, assertFocusable);
|
|
1495
|
+
throw err;
|
|
1496
|
+
}
|
|
1497
|
+
}
|
|
1498
|
+
export function assertNotFocusable(element) {
|
|
1499
|
+
try {
|
|
1500
|
+
if (element === null)
|
|
1501
|
+
return expect(element).not.toBe(null);
|
|
1502
|
+
expect(isFocusableElement(element, FocusableMode.Strict)).toBe(false);
|
|
1503
|
+
}
|
|
1504
|
+
catch (err) {
|
|
1505
|
+
if (err instanceof Error)
|
|
1506
|
+
Error.captureStackTrace(err, assertNotFocusable);
|
|
1507
|
+
throw err;
|
|
1508
|
+
}
|
|
1509
|
+
}
|
|
1510
|
+
export function assertInert(element) {
|
|
1511
|
+
try {
|
|
1512
|
+
if (element === null)
|
|
1513
|
+
return expect(element).not.toBe(null);
|
|
1514
|
+
expect(element).toHaveAttribute("aria-hidden", "true");
|
|
1515
|
+
expect(element).toHaveProperty("inert", true);
|
|
1516
|
+
}
|
|
1517
|
+
catch (err) {
|
|
1518
|
+
if (err instanceof Error)
|
|
1519
|
+
Error.captureStackTrace(err, assertInert);
|
|
1520
|
+
throw err;
|
|
1521
|
+
}
|
|
1522
|
+
}
|
|
1523
|
+
export function assertNotInert(element) {
|
|
1524
|
+
try {
|
|
1525
|
+
if (element === null)
|
|
1526
|
+
return expect(element).not.toBe(null);
|
|
1527
|
+
// NOTE: We can't test that the element doesn't have `aria-hidden`, because this can still be
|
|
1528
|
+
// the case even if they are not inert.
|
|
1529
|
+
expect(element.inert).toBeUndefined();
|
|
1530
|
+
}
|
|
1531
|
+
catch (err) {
|
|
1532
|
+
if (err instanceof Error)
|
|
1533
|
+
Error.captureStackTrace(err, assertNotInert);
|
|
1534
|
+
throw err;
|
|
1535
|
+
}
|
|
1536
|
+
}
|
|
1537
|
+
// ---
|
|
1538
|
+
export function assertDisabledish(element) {
|
|
1539
|
+
try {
|
|
1540
|
+
if (element === null)
|
|
1541
|
+
return expect(element).not.toBe(null);
|
|
1542
|
+
let actuallyDisabled = element.getAttribute("disabled");
|
|
1543
|
+
if (actuallyDisabled === "true" || actuallyDisabled === "") {
|
|
1544
|
+
return;
|
|
1545
|
+
}
|
|
1546
|
+
let ariaDisabled = element.getAttribute("aria-disabled");
|
|
1547
|
+
if (ariaDisabled === "true" || ariaDisabled === "") {
|
|
1548
|
+
return;
|
|
1549
|
+
}
|
|
1550
|
+
throw new Error(`Expected element to be disabledish, but it wasn't.`);
|
|
1551
|
+
}
|
|
1552
|
+
catch (err) {
|
|
1553
|
+
if (err instanceof Error)
|
|
1554
|
+
Error.captureStackTrace(err, assertDisabledish);
|
|
1555
|
+
throw err;
|
|
1556
|
+
}
|
|
1557
|
+
}
|
|
1558
|
+
// ---
|
|
1559
|
+
export function getByText(text) {
|
|
1560
|
+
let walker = document.createTreeWalker(document.body, NodeFilter.SHOW_ELEMENT, {
|
|
1561
|
+
acceptNode(node) {
|
|
1562
|
+
if (node.children.length > 0)
|
|
1563
|
+
return NodeFilter.FILTER_SKIP;
|
|
1564
|
+
return NodeFilter.FILTER_ACCEPT;
|
|
1565
|
+
},
|
|
1566
|
+
});
|
|
1567
|
+
while (walker.nextNode()) {
|
|
1568
|
+
if (walker.currentNode.textContent === text)
|
|
1569
|
+
return walker.currentNode;
|
|
1570
|
+
}
|
|
1571
|
+
return null;
|
|
1572
|
+
}
|