@sapudex/web-components 2.40.0 → 2.42.0
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/config/wdio.conf.cjs +18 -2
- package/cypress/specs/Dropdown.cy.tsx +90 -0
- package/cypress/specs/MessageStrip.cy.tsx +77 -0
- package/cypress/specs/PasswordField.cy.tsx +37 -0
- package/cypress/specs/Search.cy.tsx +244 -138
- package/cypress/specs/SelectBox.cy.tsx +184 -5
- package/cypress/support/component-imports.instrumented.ts +2 -0
- package/cypress/support/component-imports.src.ts +2 -0
- package/cypress.config.ts +10 -0
- package/dist/Dropdown.d.ts +5 -0
- package/dist/Dropdown.js +56 -0
- package/dist/Dropdown.js.map +1 -1
- package/dist/List.d.ts +1 -6
- package/dist/List.js +2 -10
- package/dist/List.js.map +1 -1
- package/dist/MastheadCountrySelector.d.ts +7 -0
- package/dist/MastheadCountrySelector.js +3 -0
- package/dist/MastheadCountrySelector.js.map +1 -1
- package/dist/MessageStrip.d.ts +25 -0
- package/dist/MessageStrip.js +16 -4
- package/dist/MessageStrip.js.map +1 -1
- package/dist/PasswordField.d.ts +10 -0
- package/dist/PasswordField.js +17 -2
- package/dist/PasswordField.js.map +1 -1
- package/dist/PasswordFieldTemplate.js +1 -1
- package/dist/PasswordFieldTemplate.js.map +1 -1
- package/dist/Search.d.ts +1 -1
- package/dist/Search.js +13 -9
- package/dist/Search.js.map +1 -1
- package/dist/SearchTemplate.js +3 -3
- package/dist/SearchTemplate.js.map +1 -1
- package/dist/SearchableListBase.d.ts +1 -6
- package/dist/SearchableListBase.js.map +1 -1
- package/dist/SearchableListBaseTemplate.js +1 -1
- package/dist/SearchableListBaseTemplate.js.map +1 -1
- package/dist/SelectBase.d.ts +7 -1
- package/dist/SelectBase.js +114 -17
- package/dist/SelectBase.js.map +1 -1
- package/dist/TextField.d.ts +1 -1
- package/dist/TextField.js +2 -2
- package/dist/TextField.js.map +1 -1
- package/dist/TextFieldTemplate.js +1 -1
- package/dist/TextFieldTemplate.js.map +1 -1
- package/dist/assets/{InputSuggestions-DAElWNvY.js → InputSuggestions-DVRXb_CJ.js} +1 -1
- package/dist/assets/{ListItemStandardExpandableTextTemplate-B2fGcGW7.js → ListItemStandardExpandableTextTemplate-DhmGlKzk.js} +1 -1
- package/dist/assets/{bundle.esm-DPVxY8xv.js → bundle.esm-CjF11YUZ.js} +233 -233
- package/dist/assets/messagebundle_en-BZqDcZHN.js +1 -0
- package/dist/assets/messagebundle_hu-CBV3ISbF.js +1 -0
- package/dist/assets/messagebundle_tr-DOeCmUdx.js +1 -0
- package/dist/assets/messagebundle_uk-GmdhsMsP.js +1 -0
- package/dist/assets/messagebundle_zh_CN-CHwH3F3-.js +1 -0
- package/dist/assets/messagebundle_zh_TW-FO0BCkub.js +1 -0
- package/dist/css/themes/MastheadCountrySelector.css +1 -1
- package/dist/css/themes/MastheadMenuItem.css +1 -1
- package/dist/css/themes/MastheadProfileMenu.css +1 -1
- package/dist/css/themes/StatusMessage.css +1 -1
- package/dist/css/themes/TextField.css +1 -1
- package/dist/css/themes/Toast.css +1 -1
- package/dist/css/themes/Wizard.css +1 -1
- package/dist/css/themes/WizardTab.css +1 -1
- package/dist/custom-elements-internal.json +114 -146
- package/dist/custom-elements.json +114 -114
- package/dist/features/SearchSuggestionsPopover.d.ts +6 -9
- package/dist/features/SearchSuggestionsPopover.js +47 -163
- package/dist/features/SearchSuggestionsPopover.js.map +1 -1
- package/dist/generated/assets/i18n/messagebundle_en.json +1 -1
- package/dist/generated/assets/i18n/messagebundle_hu.json +1 -1
- package/dist/generated/assets/i18n/messagebundle_tr.json +1 -1
- package/dist/generated/assets/i18n/messagebundle_uk.json +1 -1
- package/dist/generated/assets/i18n/messagebundle_zh_CN.json +1 -1
- package/dist/generated/assets/i18n/messagebundle_zh_TW.json +1 -1
- package/dist/generated/i18n/i18n-defaults.d.ts +3 -2
- package/dist/generated/i18n/i18n-defaults.js +3 -2
- package/dist/generated/i18n/i18n-defaults.js.map +1 -1
- package/dist/generated/templates/MastheadCountrySelectorTemplate.lit.js +6 -5
- package/dist/generated/templates/MastheadCountrySelectorTemplate.lit.js.map +1 -1
- package/dist/generated/templates/MastheadMobileMenuTemplate.lit.js +1 -1
- package/dist/generated/templates/MastheadMobileMenuTemplate.lit.js.map +1 -1
- package/dist/generated/themes/MastheadCountrySelector.css.d.ts +1 -1
- package/dist/generated/themes/MastheadCountrySelector.css.js +1 -1
- package/dist/generated/themes/MastheadCountrySelector.css.js.map +1 -1
- package/dist/generated/themes/MastheadMenuItem.css.d.ts +1 -1
- package/dist/generated/themes/MastheadMenuItem.css.js +1 -1
- package/dist/generated/themes/MastheadMenuItem.css.js.map +1 -1
- package/dist/generated/themes/MastheadProfileMenu.css.d.ts +1 -1
- package/dist/generated/themes/MastheadProfileMenu.css.js +1 -1
- package/dist/generated/themes/MastheadProfileMenu.css.js.map +1 -1
- package/dist/generated/themes/StatusMessage.css.d.ts +1 -1
- package/dist/generated/themes/StatusMessage.css.js +1 -1
- package/dist/generated/themes/StatusMessage.css.js.map +1 -1
- package/dist/generated/themes/TextField.css.d.ts +1 -1
- package/dist/generated/themes/TextField.css.js +1 -1
- package/dist/generated/themes/TextField.css.js.map +1 -1
- package/dist/generated/themes/Toast.css.d.ts +1 -1
- package/dist/generated/themes/Toast.css.js +1 -1
- package/dist/generated/themes/Toast.css.js.map +1 -1
- package/dist/generated/themes/Wizard.css.d.ts +1 -1
- package/dist/generated/themes/Wizard.css.js +1 -1
- package/dist/generated/themes/Wizard.css.js.map +1 -1
- package/dist/generated/themes/WizardTab.css.d.ts +1 -1
- package/dist/generated/themes/WizardTab.css.js +1 -1
- package/dist/generated/themes/WizardTab.css.js.map +1 -1
- package/dist/messagebundle.properties +2 -1
- package/dist/messagebundle_en.properties +2 -1
- package/dist/messagebundle_hu.properties +125 -0
- package/dist/messagebundle_tr.properties +126 -0
- package/dist/messagebundle_uk.properties +128 -0
- package/dist/messagebundle_zh_CN.properties +126 -0
- package/dist/messagebundle_zh_TW.properties +126 -0
- package/dist/utils/sanitizeString.js +1 -4
- package/dist/utils/sanitizeString.js.map +1 -1
- package/dist/vscode.html-custom-data.json +30 -25
- package/dist/web-types.json +61 -41
- package/package.json +2 -2
- package/src/Dropdown.ts +62 -0
- package/src/List.ts +3 -18
- package/src/MastheadCountrySelector.hbs +2 -0
- package/src/MastheadCountrySelector.ts +9 -0
- package/src/MastheadMobileMenu.hbs +1 -0
- package/src/MessageStrip.ts +37 -4
- package/src/PasswordField.ts +24 -0
- package/src/PasswordFieldTemplate.tsx +2 -2
- package/src/Search.ts +13 -9
- package/src/SearchTemplate.tsx +0 -3
- package/src/SearchableListBase.ts +1 -6
- package/src/SearchableListBaseTemplate.tsx +0 -1
- package/src/SelectBase.ts +114 -19
- package/src/TextField.ts +1 -1
- package/src/TextFieldTemplate.tsx +1 -1
- package/src/features/SearchSuggestionsPopover.ts +55 -177
- package/src/generated/i18n/i18n-defaults.ts +2 -2
- package/src/generated/templates/MastheadCountrySelectorTemplate.lit.ts +6 -5
- package/src/generated/templates/MastheadMobileMenuTemplate.lit.ts +1 -1
- package/src/generated/themes/MastheadCountrySelector.css.ts +1 -1
- package/src/generated/themes/MastheadMenuItem.css.ts +1 -1
- package/src/generated/themes/MastheadProfileMenu.css.ts +1 -1
- package/src/generated/themes/StatusMessage.css.ts +1 -1
- package/src/generated/themes/TextField.css.ts +1 -1
- package/src/generated/themes/Toast.css.ts +1 -1
- package/src/generated/themes/Wizard.css.ts +1 -1
- package/src/generated/themes/WizardTab.css.ts +1 -1
- package/src/i18n/messagebundle.properties +2 -1
- package/src/i18n/messagebundle_en.properties +2 -1
- package/src/i18n/messagebundle_hu.properties +125 -0
- package/src/i18n/messagebundle_tr.properties +126 -0
- package/src/i18n/messagebundle_uk.properties +128 -0
- package/src/i18n/messagebundle_zh_CN.properties +126 -0
- package/src/i18n/messagebundle_zh_TW.properties +126 -0
- package/src/themes/MastheadCountrySelector.css +5 -2
- package/src/themes/MastheadMenuItem.css +12 -4
- package/src/themes/MastheadProfileMenu.css +0 -5
- package/src/themes/StatusMessage.css +4 -4
- package/src/themes/TextField.css +3 -0
- package/src/themes/Toast.css +14 -2
- package/src/themes/Wizard.css +7 -0
- package/src/themes/WizardTab.css +23 -0
- package/src/utils/sanitizeString.ts +1 -4
- package/dist/assets/messagebundle_en-BxjFAY2S.js +0 -1
- package/dist/assets/messagebundle_hu-BEMbu-EE.js +0 -1
- package/dist/assets/messagebundle_tr-9FvYSV7d.js +0 -1
- package/dist/assets/messagebundle_uk-BgTRxken.js +0 -1
- package/dist/assets/messagebundle_zh_CN-BsCxQ-NE.js +0 -1
- package/dist/assets/messagebundle_zh_TW-Li9m-Dgo.js +0 -1
|
@@ -15,26 +15,40 @@ const SUGGESTION_LIST_RECOMMENDED_ONLY = JSON.stringify({
|
|
|
15
15
|
const SEARCH_SHADOW = "[udex-search]";
|
|
16
16
|
const SEARCH_INPUT = "#search-input";
|
|
17
17
|
const SUGGESTIONS_LIST = ".udex-search-suggestions__list";
|
|
18
|
+
const ITEM_BUTTON = "[ui5-button]";
|
|
19
|
+
const SUBMIT_BUTTON = "#submit-button";
|
|
18
20
|
|
|
19
21
|
const focusInputThroughShadow = () => {
|
|
20
22
|
cy.get(SEARCH_SHADOW).shadow().find(SEARCH_INPUT).focus();
|
|
21
23
|
};
|
|
22
24
|
|
|
23
|
-
//
|
|
24
|
-
//
|
|
25
|
-
// (
|
|
26
|
-
// pseudo-class
|
|
27
|
-
const
|
|
28
|
-
cy.get(SEARCH_SHADOW)
|
|
29
|
-
|
|
30
|
-
|
|
25
|
+
// Items are focused via item.focus() inside the popover (light-DOM children of
|
|
26
|
+
// ui5-popover that lives inside the search shadow root). Cypress's
|
|
27
|
+
// should("be.focused") fails on such cross-shadow chains, so use the native
|
|
28
|
+
// :focus pseudo-class which the browser applies to the actually-focused node.
|
|
29
|
+
const assertNthFlatItemFocused = (flatIndex: number) => {
|
|
30
|
+
cy.get(SEARCH_SHADOW)
|
|
31
|
+
.shadow()
|
|
32
|
+
.find(`${SUGGESTIONS_LIST} ${ITEM_BUTTON}`)
|
|
33
|
+
.eq(flatIndex)
|
|
34
|
+
.should($el => {
|
|
35
|
+
expect($el[0].matches(":focus")).to.be.true;
|
|
36
|
+
});
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const assertNthFlatItemAriaSelected = (flatIndex: number) => {
|
|
40
|
+
cy.get(SEARCH_SHADOW)
|
|
41
|
+
.shadow()
|
|
42
|
+
.find(`${SUGGESTIONS_LIST} ${ITEM_BUTTON}`)
|
|
43
|
+
.eq(flatIndex)
|
|
44
|
+
.should("have.attr", "aria-selected", "true");
|
|
31
45
|
};
|
|
32
46
|
|
|
33
47
|
// suggestion-list must be set dynamically after mount.
|
|
34
48
|
// UI5Element._suppressInvalidation is true until after the first render, so
|
|
35
49
|
// attributes set by the JSX renderer never reach onInvalidation and the lists
|
|
36
50
|
// are never populated. Setting the attribute after mount (when
|
|
37
|
-
// _suppressInvalidation is false) allows onInvalidation
|
|
51
|
+
// _suppressInvalidation is false) allows onInvalidation -> setSuggestedRecommendedLists
|
|
38
52
|
// to run and populate suggestedList / recommendedList.
|
|
39
53
|
const setSuggestionList = (list: string) => {
|
|
40
54
|
cy.get(SEARCH_SHADOW).invoke("attr", "suggestion-list", list);
|
|
@@ -51,18 +65,31 @@ const renderAlwaysOpen = () => (
|
|
|
51
65
|
</div>
|
|
52
66
|
);
|
|
53
67
|
|
|
54
|
-
const
|
|
68
|
+
const renderAlwaysOpenWithSubmit = () => (
|
|
55
69
|
<div>
|
|
56
70
|
<UDExSearch
|
|
57
71
|
id="search"
|
|
58
72
|
show-suggested-popover
|
|
59
73
|
open-suggestion-popup
|
|
74
|
+
show-submit-button
|
|
60
75
|
/>
|
|
61
76
|
<a id="next-focus" href="#">Next focus</a>
|
|
62
77
|
</div>
|
|
63
78
|
);
|
|
64
79
|
|
|
65
|
-
|
|
80
|
+
const renderAlwaysOpenWithPrev = () => (
|
|
81
|
+
<div>
|
|
82
|
+
<a id="prev-focus" href="#">Previous focus</a>
|
|
83
|
+
<UDExSearch
|
|
84
|
+
id="search"
|
|
85
|
+
show-suggested-popover
|
|
86
|
+
open-suggestion-popup
|
|
87
|
+
/>
|
|
88
|
+
<a id="next-focus" href="#">Next focus</a>
|
|
89
|
+
</div>
|
|
90
|
+
);
|
|
91
|
+
|
|
92
|
+
describe("UDExSearch — keyboard focus management (rewritten)", () => {
|
|
66
93
|
describe("Always-open popover with two lists (suggested + recommended)", () => {
|
|
67
94
|
beforeEach(() => {
|
|
68
95
|
cy.mount(renderAlwaysOpen());
|
|
@@ -70,211 +97,290 @@ describe("UDExSearch — keyboard focus management with openSuggestionPopup", ()
|
|
|
70
97
|
// Wait for the popover to be opened by onAfterRendering -> handleOpenSuggestionPopover.
|
|
71
98
|
cy.get(SEARCH_SHADOW).shadow().find(SEARCH_INPUT).should("have.attr", "aria-expanded", "true");
|
|
72
99
|
cy.get(SEARCH_SHADOW).shadow().find(SUGGESTIONS_LIST).should("have.length", 2);
|
|
100
|
+
// 2 + 2 = 4 items across both lists.
|
|
101
|
+
cy.get(SEARCH_SHADOW).shadow().find(`${SUGGESTIONS_LIST} ${ITEM_BUTTON}`).should("have.length", 4);
|
|
73
102
|
});
|
|
74
103
|
|
|
75
|
-
it("
|
|
104
|
+
it("ArrowDown from the input opens the dropdown and focuses the first item", () => {
|
|
76
105
|
focusInputThroughShadow();
|
|
77
|
-
cy.
|
|
106
|
+
cy.realPress("ArrowDown");
|
|
78
107
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
108
|
+
assertNthFlatItemFocused(0);
|
|
109
|
+
assertNthFlatItemAriaSelected(0);
|
|
110
|
+
});
|
|
82
111
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
112
|
+
it("ArrowDown navigates a flat list across both suggested and recommended items", () => {
|
|
113
|
+
focusInputThroughShadow();
|
|
114
|
+
// Arrow down four times moves through index 0, 1, 2, 3.
|
|
115
|
+
cy.realPress("ArrowDown"); // -> first item of suggested
|
|
116
|
+
assertNthFlatItemFocused(0);
|
|
86
117
|
|
|
87
|
-
//
|
|
88
|
-
|
|
118
|
+
cy.realPress("ArrowDown"); // -> second item of suggested
|
|
119
|
+
assertNthFlatItemFocused(1);
|
|
120
|
+
|
|
121
|
+
cy.realPress("ArrowDown"); // -> first item of recommended
|
|
122
|
+
assertNthFlatItemFocused(2);
|
|
123
|
+
|
|
124
|
+
cy.realPress("ArrowDown"); // -> second item of recommended (last)
|
|
125
|
+
assertNthFlatItemFocused(3);
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
it("ArrowDown on the last item wraps to the first item (circular)", () => {
|
|
129
|
+
focusInputThroughShadow();
|
|
130
|
+
cy.realPress("ArrowDown");
|
|
131
|
+
cy.realPress("ArrowDown");
|
|
132
|
+
cy.realPress("ArrowDown");
|
|
133
|
+
cy.realPress("ArrowDown");
|
|
134
|
+
assertNthFlatItemFocused(3);
|
|
135
|
+
|
|
136
|
+
// Wrap forward: last -> first.
|
|
137
|
+
cy.realPress("ArrowDown");
|
|
138
|
+
assertNthFlatItemFocused(0);
|
|
139
|
+
assertNthFlatItemAriaSelected(0);
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
it("ArrowUp on the first item wraps to the last item (circular)", () => {
|
|
143
|
+
focusInputThroughShadow();
|
|
144
|
+
// Initial state: selectedItemIndex = -1. ArrowUp wraps to last.
|
|
145
|
+
cy.realPress("ArrowDown");
|
|
146
|
+
assertNthFlatItemFocused(0);
|
|
147
|
+
|
|
148
|
+
// From first item, ArrowUp wraps to last item.
|
|
149
|
+
cy.realPress("ArrowUp");
|
|
150
|
+
assertNthFlatItemFocused(3);
|
|
151
|
+
assertNthFlatItemAriaSelected(3);
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
it("ArrowUp navigates backward across both lists as one flat sequence", () => {
|
|
155
|
+
focusInputThroughShadow();
|
|
156
|
+
// Move to last item.
|
|
157
|
+
cy.realPress("ArrowDown");
|
|
158
|
+
cy.realPress("ArrowDown");
|
|
159
|
+
cy.realPress("ArrowDown");
|
|
160
|
+
cy.realPress("ArrowDown");
|
|
161
|
+
assertNthFlatItemFocused(3);
|
|
162
|
+
|
|
163
|
+
// Step backward: last -> 2 -> 1 -> 0.
|
|
164
|
+
cy.realPress("ArrowUp");
|
|
165
|
+
assertNthFlatItemFocused(2);
|
|
166
|
+
cy.realPress("ArrowUp");
|
|
167
|
+
assertNthFlatItemFocused(1);
|
|
168
|
+
cy.realPress("ArrowUp");
|
|
169
|
+
assertNthFlatItemFocused(0);
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
it("Escape from a focused dropdown item returns focus to the input", () => {
|
|
173
|
+
focusInputThroughShadow();
|
|
174
|
+
cy.realPress("ArrowDown");
|
|
175
|
+
assertNthFlatItemFocused(0);
|
|
176
|
+
|
|
177
|
+
cy.realPress("Escape");
|
|
178
|
+
// With openSuggestionPopup=true the popover re-opens immediately after close,
|
|
179
|
+
// so we only verify that focus returned to the input.
|
|
89
180
|
cy.get(SEARCH_SHADOW).shadow().find(SEARCH_INPUT).should("be.focused");
|
|
90
|
-
|
|
181
|
+
});
|
|
91
182
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
cy.
|
|
95
|
-
|
|
183
|
+
it("aria-selected is removed from items after the dropdown closes", () => {
|
|
184
|
+
focusInputThroughShadow();
|
|
185
|
+
cy.realPress("ArrowDown");
|
|
186
|
+
assertNthFlatItemAriaSelected(0);
|
|
187
|
+
|
|
188
|
+
cy.realPress("Escape");
|
|
189
|
+
|
|
190
|
+
// After close, no item should retain aria-selected.
|
|
191
|
+
cy.get(SEARCH_SHADOW)
|
|
192
|
+
.shadow()
|
|
193
|
+
.find(`${SUGGESTIONS_LIST} ${ITEM_BUTTON}[aria-selected="true"]`)
|
|
194
|
+
.should("have.length", 0);
|
|
96
195
|
});
|
|
196
|
+
});
|
|
97
197
|
|
|
98
|
-
|
|
99
|
-
|
|
198
|
+
describe("Tab / Shift+Tab no-trap behaviour from the input", () => {
|
|
199
|
+
it("Tab from the input while dropdown is open closes the dropdown and proceeds to the next focusable", () => {
|
|
200
|
+
// Use a search WITHOUT openSuggestionPopup so handleTabKey actually closes.
|
|
100
201
|
cy.mount(
|
|
101
202
|
<div>
|
|
102
|
-
<a id="prev-focus" href="#">Previous focus</a>
|
|
103
203
|
<UDExSearch
|
|
104
204
|
id="search"
|
|
105
205
|
show-suggested-popover
|
|
106
|
-
|
|
206
|
+
value="SAP hana"
|
|
107
207
|
/>
|
|
208
|
+
<a id="next-focus" href="#">Next focus</a>
|
|
108
209
|
</div>,
|
|
109
210
|
);
|
|
110
211
|
setSuggestionList(SUGGESTION_LIST_BOTH);
|
|
212
|
+
// Open the popover by focusing the input and triggering ArrowDown,
|
|
213
|
+
// which calls handleDownKey -> onOpenPopover.
|
|
214
|
+
focusInputThroughShadow();
|
|
215
|
+
cy.realPress("ArrowDown");
|
|
111
216
|
cy.get(SEARCH_SHADOW).shadow().find(SEARCH_INPUT).should("have.attr", "aria-expanded", "true");
|
|
112
|
-
cy.get(SEARCH_SHADOW).shadow().find(SUGGESTIONS_LIST).should("have.length", 2);
|
|
113
217
|
|
|
218
|
+
// Move focus back to the input (the popover stays open).
|
|
114
219
|
focusInputThroughShadow();
|
|
115
220
|
cy.get(SEARCH_SHADOW).shadow().find(SEARCH_INPUT).should("be.focused");
|
|
116
221
|
|
|
117
|
-
//
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
//
|
|
122
|
-
cy.realPress(["Shift", "Tab"]);
|
|
123
|
-
assertListFocused(0);
|
|
124
|
-
|
|
125
|
-
// Shift+Tab 3: lists[0] -> input (escape flag is set; popover stays open)
|
|
126
|
-
cy.realPress(["Shift", "Tab"]);
|
|
127
|
-
cy.get(SEARCH_SHADOW).shadow().find(SEARCH_INPUT).should("be.focused");
|
|
128
|
-
cy.get(SEARCH_SHADOW).shadow().find(SEARCH_INPUT).should("have.attr", "aria-expanded", "true");
|
|
129
|
-
|
|
130
|
-
// Shift+Tab 4: input + flag -> focus leaves the search component backward
|
|
131
|
-
cy.realPress(["Shift", "Tab"]);
|
|
132
|
-
cy.get("#prev-focus").should("be.focused");
|
|
222
|
+
// Tab from input: handleTabKey calls onClosePopover (no preventDefault),
|
|
223
|
+
// so default Tab moves focus to the next focusable in the document.
|
|
224
|
+
cy.realPress("Tab");
|
|
225
|
+
cy.get(SEARCH_SHADOW).shadow().find(SEARCH_INPUT).should("have.attr", "aria-expanded", "false");
|
|
226
|
+
// Default Tab proceeded — the input is no longer the focused element.
|
|
133
227
|
cy.get(SEARCH_SHADOW).shadow().find(SEARCH_INPUT).should("not.be.focused");
|
|
134
228
|
});
|
|
135
229
|
|
|
136
|
-
it("Shift+Tab from
|
|
230
|
+
it("Shift+Tab from the input while dropdown is open closes the dropdown and proceeds backward", () => {
|
|
137
231
|
cy.mount(
|
|
138
232
|
<div>
|
|
233
|
+
<a id="prev-focus" href="#">Previous focus</a>
|
|
139
234
|
<UDExSearch
|
|
140
235
|
id="search"
|
|
141
236
|
show-suggested-popover
|
|
142
|
-
|
|
143
|
-
value="SAP"
|
|
237
|
+
value="SAP hana"
|
|
144
238
|
/>
|
|
145
|
-
<a id="next-focus" href="#">Next focus</a>
|
|
146
239
|
</div>,
|
|
147
240
|
);
|
|
148
241
|
setSuggestionList(SUGGESTION_LIST_BOTH);
|
|
242
|
+
focusInputThroughShadow();
|
|
243
|
+
cy.realPress("ArrowDown");
|
|
149
244
|
cy.get(SEARCH_SHADOW).shadow().find(SEARCH_INPUT).should("have.attr", "aria-expanded", "true");
|
|
150
|
-
cy.get(SEARCH_SHADOW).shadow().find(SUGGESTIONS_LIST).should("have.length", 2);
|
|
151
245
|
|
|
246
|
+
// Return focus to the input.
|
|
152
247
|
focusInputThroughShadow();
|
|
153
248
|
cy.get(SEARCH_SHADOW).shadow().find(SEARCH_INPUT).should("be.focused");
|
|
154
249
|
|
|
155
|
-
//
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
cy.
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
250
|
+
// Shift+Tab from input: handleTabKey closes (no preventDefault), default
|
|
251
|
+
// Shift+Tab proceeds backward to the previous focusable.
|
|
252
|
+
cy.realPress(["Shift", "Tab"]);
|
|
253
|
+
cy.get(SEARCH_SHADOW).shadow().find(SEARCH_INPUT).should("have.attr", "aria-expanded", "false");
|
|
254
|
+
cy.get("#prev-focus").should("be.focused");
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
it("with openSuggestionPopup=true, Tab from the input does not close the popover (no preventDefault)", () => {
|
|
258
|
+
cy.mount(renderAlwaysOpen());
|
|
259
|
+
setSuggestionList(SUGGESTION_LIST_BOTH);
|
|
260
|
+
cy.get(SEARCH_SHADOW).shadow().find(SEARCH_INPUT).should("have.attr", "aria-expanded", "true");
|
|
162
261
|
|
|
163
|
-
|
|
262
|
+
focusInputThroughShadow();
|
|
164
263
|
cy.realPress("Tab");
|
|
165
|
-
cy.get(SEARCH_SHADOW).shadow().find(SEARCH_INPUT).should("not.be.focused");
|
|
166
264
|
|
|
167
|
-
//
|
|
168
|
-
|
|
169
|
-
cy.get(SEARCH_SHADOW).shadow().find(SEARCH_INPUT).should("
|
|
265
|
+
// handleTabKey returned early because openSuggestionPopup is true.
|
|
266
|
+
// The popover remains open (or is re-opened by handleOpenSuggestionPopover on next render).
|
|
267
|
+
cy.get(SEARCH_SHADOW).shadow().find(SEARCH_INPUT).should("have.attr", "aria-expanded", "true");
|
|
170
268
|
});
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
describe("Tab / Shift+Tab from a focused dropdown item", () => {
|
|
272
|
+
it("Tab from a focused item closes the dropdown and moves focus to #submit-button when present", () => {
|
|
273
|
+
cy.mount(renderAlwaysOpenWithSubmit());
|
|
274
|
+
setSuggestionList(SUGGESTION_LIST_BOTH);
|
|
275
|
+
cy.get(SEARCH_SHADOW).shadow().find(SEARCH_INPUT).should("have.attr", "aria-expanded", "true");
|
|
276
|
+
cy.get(SEARCH_SHADOW).shadow().find(`${SUGGESTIONS_LIST} ${ITEM_BUTTON}`).should("have.length", 4);
|
|
277
|
+
cy.get(SEARCH_SHADOW).shadow().find(SUBMIT_BUTTON).should("exist");
|
|
171
278
|
|
|
172
|
-
it("Down arrow from a focused list moves focus into individual item buttons; Up arrow reverses it", () => {
|
|
173
279
|
focusInputThroughShadow();
|
|
174
|
-
cy.
|
|
280
|
+
cy.realPress("ArrowDown");
|
|
281
|
+
assertNthFlatItemFocused(0);
|
|
175
282
|
|
|
176
|
-
// Tab into lists[0].
|
|
177
283
|
cy.realPress("Tab");
|
|
178
|
-
assertListFocused(0);
|
|
179
284
|
|
|
180
|
-
//
|
|
181
|
-
cy.
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
.eq(0)
|
|
186
|
-
.find("[ui5-button]")
|
|
187
|
-
.eq(0)
|
|
188
|
-
.should("have.attr", "aria-selected", "true");
|
|
285
|
+
// Focus moved to submit button (programmatic focus by onKeyTabList).
|
|
286
|
+
cy.get(SEARCH_SHADOW).shadow().find(SUBMIT_BUTTON).should($el => {
|
|
287
|
+
expect($el[0].matches(":focus")).to.be.true;
|
|
288
|
+
});
|
|
289
|
+
});
|
|
189
290
|
|
|
190
|
-
|
|
191
|
-
cy.
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
.find("[ui5-button]")
|
|
197
|
-
.eq(1)
|
|
198
|
-
.should("have.attr", "aria-selected", "true");
|
|
291
|
+
it("Tab from a focused item closes the dropdown and falls back to the input when there is no submit button", () => {
|
|
292
|
+
cy.mount(renderAlwaysOpen());
|
|
293
|
+
setSuggestionList(SUGGESTION_LIST_BOTH);
|
|
294
|
+
cy.get(SEARCH_SHADOW).shadow().find(SEARCH_INPUT).should("have.attr", "aria-expanded", "true");
|
|
295
|
+
cy.get(SEARCH_SHADOW).shadow().find(`${SUGGESTIONS_LIST} ${ITEM_BUTTON}`).should("have.length", 4);
|
|
296
|
+
cy.get(SEARCH_SHADOW).shadow().find(SUBMIT_BUTTON).should("not.exist");
|
|
199
297
|
|
|
200
|
-
|
|
298
|
+
focusInputThroughShadow();
|
|
201
299
|
cy.realPress("ArrowDown");
|
|
202
|
-
|
|
300
|
+
assertNthFlatItemFocused(0);
|
|
301
|
+
|
|
302
|
+
cy.realPress("Tab");
|
|
303
|
+
|
|
304
|
+
cy.get(SEARCH_SHADOW).shadow().find(SEARCH_INPUT).should("be.focused");
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
it("Shift+Tab from a focused item closes the dropdown and moves focus to the input", () => {
|
|
308
|
+
cy.mount(renderAlwaysOpenWithSubmit());
|
|
309
|
+
setSuggestionList(SUGGESTION_LIST_BOTH);
|
|
310
|
+
cy.get(SEARCH_SHADOW).shadow().find(SEARCH_INPUT).should("have.attr", "aria-expanded", "true");
|
|
311
|
+
cy.get(SEARCH_SHADOW).shadow().find(`${SUGGESTIONS_LIST} ${ITEM_BUTTON}`).should("have.length", 4);
|
|
203
312
|
|
|
204
|
-
|
|
313
|
+
focusInputThroughShadow();
|
|
205
314
|
cy.realPress("ArrowDown");
|
|
206
|
-
|
|
207
|
-
.shadow()
|
|
208
|
-
.find(SUGGESTIONS_LIST)
|
|
209
|
-
.eq(1)
|
|
210
|
-
.find("[ui5-button]")
|
|
211
|
-
.eq(0)
|
|
212
|
-
.should("have.attr", "aria-selected", "true");
|
|
315
|
+
assertNthFlatItemFocused(0);
|
|
213
316
|
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
317
|
+
cy.realPress(["Shift", "Tab"]);
|
|
318
|
+
|
|
319
|
+
cy.get(SEARCH_SHADOW).shadow().find(SEARCH_INPUT).should("be.focused");
|
|
217
320
|
});
|
|
218
321
|
});
|
|
219
322
|
|
|
220
323
|
describe("Always-open popover with a single list (recommended only)", () => {
|
|
221
324
|
beforeEach(() => {
|
|
222
|
-
cy.mount(
|
|
325
|
+
cy.mount(renderAlwaysOpen());
|
|
223
326
|
setSuggestionList(SUGGESTION_LIST_RECOMMENDED_ONLY);
|
|
224
327
|
cy.get(SEARCH_SHADOW).shadow().find(SEARCH_INPUT).should("have.attr", "aria-expanded", "true");
|
|
225
328
|
cy.get(SEARCH_SHADOW).shadow().find(SUGGESTIONS_LIST).should("have.length", 1);
|
|
329
|
+
cy.get(SEARCH_SHADOW).shadow().find(`${SUGGESTIONS_LIST} ${ITEM_BUTTON}`).should("have.length", 2);
|
|
226
330
|
});
|
|
227
331
|
|
|
228
|
-
it("
|
|
332
|
+
it("ArrowDown moves through the single list, ArrowDown on last item wraps to first", () => {
|
|
229
333
|
focusInputThroughShadow();
|
|
230
|
-
cy.get(SEARCH_SHADOW).shadow().find(SEARCH_INPUT).should("be.focused");
|
|
231
334
|
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
assertListFocused(0);
|
|
335
|
+
cy.realPress("ArrowDown");
|
|
336
|
+
assertNthFlatItemFocused(0);
|
|
235
337
|
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
cy.get(SEARCH_SHADOW).shadow().find(SEARCH_INPUT).should("be.focused");
|
|
239
|
-
cy.get(SEARCH_SHADOW).shadow().find(SEARCH_INPUT).should("have.attr", "aria-expanded", "true");
|
|
338
|
+
cy.realPress("ArrowDown");
|
|
339
|
+
assertNthFlatItemFocused(1);
|
|
240
340
|
|
|
241
|
-
//
|
|
242
|
-
cy.realPress("
|
|
243
|
-
|
|
244
|
-
cy.get(SEARCH_SHADOW).shadow().find(SEARCH_INPUT).should("not.be.focused");
|
|
341
|
+
// Last item -> wrap to first.
|
|
342
|
+
cy.realPress("ArrowDown");
|
|
343
|
+
assertNthFlatItemFocused(0);
|
|
245
344
|
});
|
|
246
345
|
|
|
247
|
-
it("
|
|
248
|
-
cy.mount(
|
|
249
|
-
<div>
|
|
250
|
-
<a id="prev-focus" href="#">Previous focus</a>
|
|
251
|
-
<UDExSearch
|
|
252
|
-
id="search"
|
|
253
|
-
show-suggested-popover
|
|
254
|
-
open-suggestion-popup
|
|
255
|
-
/>
|
|
256
|
-
</div>,
|
|
257
|
-
);
|
|
258
|
-
setSuggestionList(SUGGESTION_LIST_RECOMMENDED_ONLY);
|
|
259
|
-
cy.get(SEARCH_SHADOW).shadow().find(SEARCH_INPUT).should("have.attr", "aria-expanded", "true");
|
|
260
|
-
cy.get(SEARCH_SHADOW).shadow().find(SUGGESTIONS_LIST).should("have.length", 1);
|
|
261
|
-
|
|
346
|
+
it("ArrowUp from the first item wraps to the last item", () => {
|
|
262
347
|
focusInputThroughShadow();
|
|
263
|
-
cy.
|
|
348
|
+
cy.realPress("ArrowDown");
|
|
349
|
+
assertNthFlatItemFocused(0);
|
|
264
350
|
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
351
|
+
cy.realPress("ArrowUp");
|
|
352
|
+
assertNthFlatItemFocused(1);
|
|
353
|
+
});
|
|
268
354
|
|
|
269
|
-
|
|
270
|
-
|
|
355
|
+
it("Tab from a focused item closes the dropdown and returns focus to the input (no submit button)", () => {
|
|
356
|
+
focusInputThroughShadow();
|
|
357
|
+
cy.realPress("ArrowDown");
|
|
358
|
+
assertNthFlatItemFocused(0);
|
|
359
|
+
|
|
360
|
+
cy.realPress("Tab");
|
|
361
|
+
// With openSuggestionPopup=true the popover re-opens immediately after close,
|
|
362
|
+
// so we only verify that focus returned to the input.
|
|
271
363
|
cy.get(SEARCH_SHADOW).shadow().find(SEARCH_INPUT).should("be.focused");
|
|
364
|
+
});
|
|
365
|
+
});
|
|
366
|
+
|
|
367
|
+
describe("Suggestion list divs are no longer Tab-reachable", () => {
|
|
368
|
+
it("the list divs do not carry tabindex=0 and are skipped in the Tab order", () => {
|
|
369
|
+
cy.mount(renderAlwaysOpenWithPrev());
|
|
370
|
+
setSuggestionList(SUGGESTION_LIST_BOTH);
|
|
272
371
|
cy.get(SEARCH_SHADOW).shadow().find(SEARCH_INPUT).should("have.attr", "aria-expanded", "true");
|
|
273
372
|
|
|
274
|
-
//
|
|
275
|
-
cy.
|
|
276
|
-
|
|
277
|
-
|
|
373
|
+
// Confirm the lists themselves are not exposed in the Tab order.
|
|
374
|
+
cy.get(SEARCH_SHADOW)
|
|
375
|
+
.shadow()
|
|
376
|
+
.find(SUGGESTIONS_LIST)
|
|
377
|
+
.each($el => {
|
|
378
|
+
// Either no tabindex attribute at all, or not a positive/zero value.
|
|
379
|
+
const tabindex = $el.attr("tabindex");
|
|
380
|
+
if (tabindex !== undefined) {
|
|
381
|
+
expect(Number(tabindex)).to.be.lessThan(0);
|
|
382
|
+
}
|
|
383
|
+
});
|
|
278
384
|
});
|
|
279
385
|
});
|
|
280
386
|
});
|