@sapudex/web-components 2.36.0 → 2.38.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/cypress/specs/List.cy.tsx +38 -0
- package/cypress/specs/Pagination.cy.tsx +52 -0
- package/cypress/specs/PasswordField.cy.tsx +36 -0
- package/cypress/specs/Search.cy.tsx +265 -0
- package/cypress/specs/SelectBox.cy.tsx +19 -1
- package/cypress/specs/TabNavigation.cy.tsx +314 -0
- package/cypress/specs/Table.cy.tsx +170 -0
- package/cypress/support/component-imports.instrumented.ts +2 -0
- package/cypress/support/component-imports.src.ts +2 -0
- package/dist/CountrySelector.d.ts +2 -0
- package/dist/CountrySelector.js +12 -3
- package/dist/CountrySelector.js.map +1 -1
- package/dist/CountrySelectorTemplate.d.ts +2 -0
- package/dist/CountrySelectorTemplate.js +8 -0
- package/dist/CountrySelectorTemplate.js.map +1 -0
- package/dist/FilterMenu.d.ts +3 -3
- package/dist/FilterMenu.js +8 -9
- package/dist/FilterMenu.js.map +1 -1
- package/dist/FilterMenuTemplate.d.ts +2 -0
- package/dist/FilterMenuTemplate.js +27 -0
- package/dist/FilterMenuTemplate.js.map +1 -0
- package/dist/List.d.ts +1 -0
- package/dist/List.js +4 -0
- package/dist/List.js.map +1 -1
- package/dist/Pagination.js +1 -0
- package/dist/Pagination.js.map +1 -1
- package/dist/PasswordField.d.ts +16 -0
- package/dist/PasswordField.js +40 -0
- 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 -0
- package/dist/Search.js +6 -0
- package/dist/Search.js.map +1 -1
- package/dist/SearchableListBaseTemplate.d.ts +2 -0
- package/dist/SearchableListBaseTemplate.js +32 -0
- package/dist/SearchableListBaseTemplate.js.map +1 -0
- package/dist/SecondaryNavigation.d.ts +2 -0
- package/dist/SecondaryNavigation.js +20 -0
- package/dist/SecondaryNavigation.js.map +1 -1
- package/dist/SelectBase.d.ts +7 -6
- package/dist/SelectBase.js +2 -2
- package/dist/SelectBase.js.map +1 -1
- package/dist/SelectBox.d.ts +2 -0
- package/dist/SelectBox.js +12 -3
- package/dist/SelectBox.js.map +1 -1
- package/dist/SelectBoxTemplate.d.ts +2 -0
- package/dist/SelectBoxTemplate.js +27 -0
- package/dist/SelectBoxTemplate.js.map +1 -0
- package/dist/Tab.d.ts +9 -0
- package/dist/Tab.js +6 -0
- package/dist/Tab.js.map +1 -1
- package/dist/TabNavigation.js +32 -12
- package/dist/TabNavigation.js.map +1 -1
- package/dist/TableGroupRowTemplate.js +1 -1
- package/dist/TableGroupRowTemplate.js.map +1 -1
- package/dist/TableTemplate.js +1 -1
- package/dist/TableTemplate.js.map +1 -1
- package/dist/TagTemplate.js +2 -4
- package/dist/TagTemplate.js.map +1 -1
- package/dist/TextField.js +1 -0
- package/dist/TextField.js.map +1 -1
- package/dist/Wizard.d.ts +2 -2
- package/dist/Wizard.js +7 -7
- package/dist/Wizard.js.map +1 -1
- package/dist/assets/{InputSuggestions-BIR_sdOv.js → InputSuggestions-BQp20KJM.js} +1 -1
- package/dist/assets/{ListItemStandardExpandableTextTemplate-5xOuY0en.js → ListItemStandardExpandableTextTemplate-D0w0u6f7.js} +1 -1
- package/dist/assets/{bundle.esm-C8GwJvJr.js → bundle.esm-DIFG6k5I.js} +240 -240
- package/dist/assets/messagebundle_bg-4ainC3nj.js +1 -0
- package/dist/assets/messagebundle_cs-CFtmLavu.js +1 -0
- package/dist/assets/messagebundle_en-BxjFAY2S.js +1 -0
- package/dist/css/themes/MastheadMenu.css +1 -1
- package/dist/css/themes/MastheadMobileMenu.css +1 -1
- package/dist/css/themes/MediaPlayer.css +1 -1
- package/dist/css/themes/RatingBar.css +1 -1
- package/dist/css/themes/RatingIndicator.css +1 -1
- package/dist/css/themes/SecondaryNavigationSubItem.css +1 -1
- package/dist/css/themes/Slider.css +1 -1
- package/dist/css/themes/TabNavigation.css +1 -1
- package/dist/css/themes/TableGroupRow.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 +65 -0
- package/dist/custom-elements.json +49 -0
- package/dist/features/SearchSuggestionsPopover.d.ts +4 -0
- package/dist/features/SearchSuggestionsPopover.js +56 -3
- package/dist/features/SearchSuggestionsPopover.js.map +1 -1
- package/dist/generated/assets/i18n/messagebundle_bg.json +1 -1
- package/dist/generated/assets/i18n/messagebundle_cs.json +1 -1
- package/dist/generated/assets/i18n/messagebundle_en.json +1 -1
- package/dist/generated/i18n/i18n-defaults.d.ts +2 -3
- package/dist/generated/i18n/i18n-defaults.js +2 -3
- package/dist/generated/i18n/i18n-defaults.js.map +1 -1
- package/dist/generated/templates/ListTemplate.lit.js +1 -1
- package/dist/generated/templates/ListTemplate.lit.js.map +1 -1
- package/dist/generated/templates/TabHeaderTemplate.lit.js +4 -4
- package/dist/generated/templates/TabHeaderTemplate.lit.js.map +1 -1
- package/dist/generated/templates/TabInOverflowTemplate.lit.js +2 -2
- package/dist/generated/templates/TabInOverflowTemplate.lit.js.map +1 -1
- package/dist/generated/themes/MastheadMenu.css.d.ts +1 -1
- package/dist/generated/themes/MastheadMenu.css.js +1 -1
- package/dist/generated/themes/MastheadMenu.css.js.map +1 -1
- package/dist/generated/themes/MastheadMobileMenu.css.d.ts +1 -1
- package/dist/generated/themes/MastheadMobileMenu.css.js +1 -1
- package/dist/generated/themes/MastheadMobileMenu.css.js.map +1 -1
- package/dist/generated/themes/MediaPlayer.css.d.ts +1 -1
- package/dist/generated/themes/MediaPlayer.css.js +1 -1
- package/dist/generated/themes/MediaPlayer.css.js.map +1 -1
- package/dist/generated/themes/RatingBar.css.d.ts +1 -1
- package/dist/generated/themes/RatingBar.css.js +1 -1
- package/dist/generated/themes/RatingBar.css.js.map +1 -1
- package/dist/generated/themes/RatingIndicator.css.d.ts +1 -1
- package/dist/generated/themes/RatingIndicator.css.js +1 -1
- package/dist/generated/themes/RatingIndicator.css.js.map +1 -1
- package/dist/generated/themes/SecondaryNavigationSubItem.css.d.ts +1 -1
- package/dist/generated/themes/SecondaryNavigationSubItem.css.js +1 -1
- package/dist/generated/themes/SecondaryNavigationSubItem.css.js.map +1 -1
- package/dist/generated/themes/Slider.css.d.ts +1 -1
- package/dist/generated/themes/Slider.css.js +1 -1
- package/dist/generated/themes/Slider.css.js.map +1 -1
- package/dist/generated/themes/TabNavigation.css.d.ts +1 -1
- package/dist/generated/themes/TabNavigation.css.js +1 -1
- package/dist/generated/themes/TabNavigation.css.js.map +1 -1
- package/dist/generated/themes/TableGroupRow.css.d.ts +1 -1
- package/dist/generated/themes/TableGroupRow.css.js +1 -1
- package/dist/generated/themes/TableGroupRow.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 +1 -6
- package/dist/messagebundle_bg.properties +129 -2
- package/dist/messagebundle_cs.properties +126 -0
- package/dist/messagebundle_en.properties +3 -1
- package/dist/vscode.html-custom-data.json +6 -1
- package/dist/web-types.json +42 -4
- package/package.json +1 -1
- package/src/CountrySelector.ts +14 -3
- package/src/CountrySelectorTemplate.tsx +12 -0
- package/src/FilterMenu.ts +9 -10
- package/src/FilterMenuTemplate.tsx +104 -0
- package/src/List.hbs +1 -1
- package/src/List.ts +5 -0
- package/src/Pagination.ts +1 -0
- package/src/PasswordField.ts +55 -0
- package/src/PasswordFieldTemplate.tsx +3 -0
- package/src/Search.ts +7 -0
- package/src/SearchableListBaseTemplate.tsx +157 -0
- package/src/SecondaryNavigation.ts +20 -0
- package/src/SelectBase.ts +7 -6
- package/src/SelectBox.ts +14 -3
- package/src/SelectBoxTemplate.tsx +165 -0
- package/src/Tab.ts +14 -0
- package/src/TabHeader.hbs +6 -2
- package/src/TabInOverflow.hbs +2 -0
- package/src/TabNavigation.ts +34 -12
- package/src/TableGroupRowTemplate.tsx +26 -17
- package/src/TableTemplate.tsx +1 -1
- package/src/TagTemplate.tsx +2 -4
- package/src/TextField.ts +1 -0
- package/src/Wizard.ts +15 -14
- package/src/features/SearchSuggestionsPopover.ts +59 -3
- package/src/generated/i18n/i18n-defaults.ts +2 -2
- package/src/generated/templates/ListTemplate.lit.ts +1 -1
- package/src/generated/templates/TabHeaderTemplate.lit.ts +4 -4
- package/src/generated/templates/TabInOverflowTemplate.lit.ts +2 -2
- package/src/generated/themes/MastheadMenu.css.ts +1 -1
- package/src/generated/themes/MastheadMobileMenu.css.ts +1 -1
- package/src/generated/themes/MediaPlayer.css.ts +1 -1
- package/src/generated/themes/RatingBar.css.ts +1 -1
- package/src/generated/themes/RatingIndicator.css.ts +1 -1
- package/src/generated/themes/SecondaryNavigationSubItem.css.ts +1 -1
- package/src/generated/themes/Slider.css.ts +1 -1
- package/src/generated/themes/TabNavigation.css.ts +1 -1
- package/src/generated/themes/TableGroupRow.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 +1 -6
- package/src/i18n/messagebundle_bg.properties +129 -2
- package/src/i18n/messagebundle_cs.properties +126 -0
- package/src/i18n/messagebundle_en.properties +3 -1
- package/src/themes/MastheadMenu.css +4 -2
- package/src/themes/MastheadMobileMenu.css +4 -2
- package/src/themes/MediaPlayer.css +12 -0
- package/src/themes/RatingBar.css +14 -5
- package/src/themes/SecondaryNavigationSubItem.css +1 -0
- package/src/themes/TabNavigation.css +6 -0
- package/src/themes/TableGroupRow.css +5 -0
- package/src/themes/Wizard.css +19 -15
- package/src/themes/WizardTab.css +43 -28
- package/dist/assets/messagebundle_bg-4X593TQq.js +0 -1
- package/dist/assets/messagebundle_cs-Bt8XtMib.js +0 -1
- package/dist/assets/messagebundle_en-iNqRB1VZ.js +0 -1
- package/dist/generated/templates/CountrySelectorTemplate.lit.d.ts +0 -3
- package/dist/generated/templates/CountrySelectorTemplate.lit.js +0 -8
- package/dist/generated/templates/CountrySelectorTemplate.lit.js.map +0 -1
- package/dist/generated/templates/FilterMenuTemplate.lit.d.ts +0 -3
- package/dist/generated/templates/FilterMenuTemplate.lit.js +0 -13
- package/dist/generated/templates/FilterMenuTemplate.lit.js.map +0 -1
- package/dist/generated/templates/SearchableListBaseTemplate.lit.d.ts +0 -3
- package/dist/generated/templates/SearchableListBaseTemplate.lit.js +0 -19
- package/dist/generated/templates/SearchableListBaseTemplate.lit.js.map +0 -1
- package/dist/generated/templates/SelectBoxTemplate.lit.d.ts +0 -3
- package/dist/generated/templates/SelectBoxTemplate.lit.js +0 -20
- package/dist/generated/templates/SelectBoxTemplate.lit.js.map +0 -1
- package/src/CountrySelector.hbs +0 -6
- package/src/FilterMenu.hbs +0 -88
- package/src/SearchableListBase.hbs +0 -148
- package/src/SelectBox.hbs +0 -138
- package/src/generated/templates/CountrySelectorTemplate.lit.ts +0 -12
- package/src/generated/templates/FilterMenuTemplate.lit.ts +0 -17
- package/src/generated/templates/SearchableListBaseTemplate.lit.ts +0 -23
- package/src/generated/templates/SelectBoxTemplate.lit.ts +0 -24
|
@@ -168,4 +168,42 @@ describe("List component - collapsible group header", () => {
|
|
|
168
168
|
cy.get("@grouping-header").find('.udex-list__group-heading__icon').realClick();
|
|
169
169
|
cy.get("#group-header-collapsible").should("not.have.attr", "collapsed", "");
|
|
170
170
|
});
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
describe("List component - SingleContent accessible name", () => {
|
|
174
|
+
it("uses accessible-name as aria-label when only accessible-name is set", () => {
|
|
175
|
+
cy.mount(
|
|
176
|
+
<UDExList mode="SingleContent" accessible-name="My list">
|
|
177
|
+
<UDExListItem>Item 1</UDExListItem>
|
|
178
|
+
</UDExList>
|
|
179
|
+
);
|
|
180
|
+
cy.get("[udex-list]").shadow().find(".udex-list").should("have.attr", "aria-label", "My list");
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
it("uses header-text as aria-label when only header-text is set", () => {
|
|
184
|
+
cy.mount(
|
|
185
|
+
<UDExList mode="SingleContent" header-text="My header">
|
|
186
|
+
<UDExListItem>Item 1</UDExListItem>
|
|
187
|
+
</UDExList>
|
|
188
|
+
);
|
|
189
|
+
cy.get("[udex-list]").shadow().find(".udex-list").should("have.attr", "aria-label", "My header");
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
it("prioritizes accessible-name over header-text when both are set", () => {
|
|
193
|
+
cy.mount(
|
|
194
|
+
<UDExList mode="SingleContent" accessible-name="My accessible name" header-text="My header">
|
|
195
|
+
<UDExListItem>Item 1</UDExListItem>
|
|
196
|
+
</UDExList>
|
|
197
|
+
);
|
|
198
|
+
cy.get("[udex-list]").shadow().find(".udex-list").should("have.attr", "aria-label", "My accessible name");
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
it("does not have aria-label when neither accessible-name nor header-text is set", () => {
|
|
202
|
+
cy.mount(
|
|
203
|
+
<UDExList mode="SingleContent">
|
|
204
|
+
<UDExListItem>Item 1</UDExListItem>
|
|
205
|
+
</UDExList>
|
|
206
|
+
);
|
|
207
|
+
cy.get("[udex-list]").shadow().find(".udex-list").should("not.have.attr", "aria-label");
|
|
208
|
+
});
|
|
171
209
|
});
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { UDExPagination } from "../support/component-imports.js";
|
|
2
|
+
|
|
3
|
+
describe("UDExPagination", () => {
|
|
4
|
+
describe("Focus management — _pageFocusedManually flag reset", () => {
|
|
5
|
+
it("does not call focusActivePage on programmatic currentPageIndex change after a user click", () => {
|
|
6
|
+
// Mount pagination with enough pages to navigate
|
|
7
|
+
cy.mount(
|
|
8
|
+
<div>
|
|
9
|
+
<button id="external-focus-target">External</button>
|
|
10
|
+
<UDExPagination id="pagination" pages={5} currentPageIndex={0} />
|
|
11
|
+
</div>,
|
|
12
|
+
);
|
|
13
|
+
|
|
14
|
+
// Wait for the component to render fully
|
|
15
|
+
cy.get("[udex-pagination]")
|
|
16
|
+
.shadow()
|
|
17
|
+
.find(".udex-pagination__page[data-index='0']")
|
|
18
|
+
.should("exist");
|
|
19
|
+
|
|
20
|
+
// Click on page 3 (index 2) — this sets _pageFocusedManually = true,
|
|
21
|
+
// triggers re-render, focusActivePage() runs and resets the flag
|
|
22
|
+
cy.get("[udex-pagination]")
|
|
23
|
+
.shadow()
|
|
24
|
+
.find(".udex-pagination__page[data-index='2']")
|
|
25
|
+
.click();
|
|
26
|
+
|
|
27
|
+
// Verify the click was registered (page 3 is now active)
|
|
28
|
+
cy.get("[udex-pagination]")
|
|
29
|
+
.shadow()
|
|
30
|
+
.find(".udex-pagination__page[data-index='2']")
|
|
31
|
+
.should("have.attr", "active");
|
|
32
|
+
|
|
33
|
+
// Move focus to an external element so we can detect if focus moves back
|
|
34
|
+
cy.get("#external-focus-target").focus();
|
|
35
|
+
cy.get("#external-focus-target").should("have.focus");
|
|
36
|
+
|
|
37
|
+
// Programmatically change currentPageIndex — this should NOT trigger focusActivePage
|
|
38
|
+
// because _pageFocusedManually was already consumed (reset to false) on the prior render
|
|
39
|
+
cy.get("[udex-pagination]").then(($el) => {
|
|
40
|
+
($el[0] as any).currentPageIndex = 4;
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
// Allow time for re-render to complete
|
|
44
|
+
// eslint-disable-next-line cypress/no-unnecessary-waiting
|
|
45
|
+
cy.wait(100);
|
|
46
|
+
|
|
47
|
+
// Assert that focus did NOT move to the pagination page button.
|
|
48
|
+
// Focus should remain on the external button.
|
|
49
|
+
cy.get("#external-focus-target").should("have.focus");
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
});
|
|
@@ -89,3 +89,39 @@ describe("General interaction", () => {
|
|
|
89
89
|
});
|
|
90
90
|
});
|
|
91
91
|
|
|
92
|
+
describe("Events", () => {
|
|
93
|
+
it("fires input event on typing", () => {
|
|
94
|
+
const inputHandler = cy.stub().as("inputHandler");
|
|
95
|
+
cy.mount(<UDExPasswordField onInput={inputHandler}></UDExPasswordField>);
|
|
96
|
+
cy.get("[udex-password-field]").shadow().find("[udex-text-field]").shadow().find("input").type("a");
|
|
97
|
+
cy.get("@inputHandler").should("have.been.called");
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it("fires change event on Enter", () => {
|
|
101
|
+
const changeHandler = cy.stub().as("changeHandler");
|
|
102
|
+
cy.mount(<UDExPasswordField onChange={changeHandler}></UDExPasswordField>);
|
|
103
|
+
cy.get("[udex-password-field]").shadow().find("[udex-text-field]").shadow().find("input").type("test{enter}");
|
|
104
|
+
cy.get("@changeHandler").should("have.been.called");
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it("fires blur event on focus out", () => {
|
|
108
|
+
const blurHandler = cy.stub().as("blurHandler");
|
|
109
|
+
cy.mount(<UDExPasswordField onBlur={blurHandler}></UDExPasswordField>);
|
|
110
|
+
cy.get("[udex-password-field]").shadow().find("[udex-text-field]").shadow().find("input").focus().blur();
|
|
111
|
+
cy.get("@blurHandler").should("have.been.called");
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
it("fires click-icon event when show/hide button is clicked", () => {
|
|
115
|
+
const clickIconHandler = cy.stub().as("clickIconHandler");
|
|
116
|
+
cy.mount(<UDExPasswordField onClickIcon={clickIconHandler} show-button-enabled></UDExPasswordField>);
|
|
117
|
+
cy.get("[udex-password-field]").shadow().find("[udex-text-field]").shadow().find("[udex-control-button]").click();
|
|
118
|
+
cy.get("@clickIconHandler").should("have.been.called");
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
it("updates value property on input", () => {
|
|
122
|
+
cy.mount(<UDExPasswordField></UDExPasswordField>);
|
|
123
|
+
cy.get("[udex-password-field]").shadow().find("[udex-text-field]").shadow().find("input").type("secret");
|
|
124
|
+
cy.get("[udex-password-field]").should("have.prop", "value", "secret");
|
|
125
|
+
});
|
|
126
|
+
});
|
|
127
|
+
|
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
import { UDExSearch } from "../support/component-imports.js";
|
|
2
|
+
// The SearchSuggestionsPopover feature must be imported so the popover is enabled.
|
|
3
|
+
import "../../src/features/SearchSuggestionsPopover.js";
|
|
4
|
+
|
|
5
|
+
const SUGGESTION_LIST_BOTH = JSON.stringify({
|
|
6
|
+
suggested: ["SAP hana", "SAP hana Enterprise Cloud"],
|
|
7
|
+
recommended: ["What is SAP hana", "SAP hana in Community"],
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
const SUGGESTION_LIST_RECOMMENDED_ONLY = JSON.stringify({
|
|
11
|
+
suggested: [],
|
|
12
|
+
recommended: ["What is SAP hana", "SAP hana in Community"],
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
const SEARCH_SHADOW = "[udex-search]";
|
|
16
|
+
const SEARCH_INPUT = "#search-input";
|
|
17
|
+
const SUGGESTIONS_LIST = ".udex-search-suggestions__list";
|
|
18
|
+
|
|
19
|
+
const focusInputThroughShadow = () => {
|
|
20
|
+
cy.get(SEARCH_SHADOW).shadow().find(SEARCH_INPUT).focus();
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
// suggestion-list must be set dynamically after mount.
|
|
24
|
+
// UI5Element._suppressInvalidation is true until after the first render, so
|
|
25
|
+
// attributes set by the JSX renderer never reach onInvalidation and the lists
|
|
26
|
+
// are never populated. Setting the attribute after mount (when
|
|
27
|
+
// _suppressInvalidation is false) allows onInvalidation → setSuggestedRecommendedLists
|
|
28
|
+
// to run and populate suggestedList / recommendedList.
|
|
29
|
+
const setSuggestionList = (list: string) => {
|
|
30
|
+
cy.get(SEARCH_SHADOW).invoke("attr", "suggestion-list", list);
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
const renderAlwaysOpen = () => (
|
|
34
|
+
<div>
|
|
35
|
+
<UDExSearch
|
|
36
|
+
id="search"
|
|
37
|
+
show-suggested-popover
|
|
38
|
+
open-suggestion-popup
|
|
39
|
+
/>
|
|
40
|
+
<a id="next-focus" href="#">Next focus</a>
|
|
41
|
+
</div>
|
|
42
|
+
);
|
|
43
|
+
|
|
44
|
+
const renderAlwaysOpenSingleList = () => (
|
|
45
|
+
<div>
|
|
46
|
+
<UDExSearch
|
|
47
|
+
id="search"
|
|
48
|
+
show-suggested-popover
|
|
49
|
+
open-suggestion-popup
|
|
50
|
+
/>
|
|
51
|
+
<a id="next-focus" href="#">Next focus</a>
|
|
52
|
+
</div>
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
describe("UDExSearch — keyboard focus management with openSuggestionPopup", () => {
|
|
56
|
+
describe("Always-open popover with two lists (suggested + recommended)", () => {
|
|
57
|
+
beforeEach(() => {
|
|
58
|
+
cy.mount(renderAlwaysOpen());
|
|
59
|
+
setSuggestionList(SUGGESTION_LIST_BOTH);
|
|
60
|
+
// Wait for the popover to be opened by onAfterRendering -> handleOpenSuggestionPopover.
|
|
61
|
+
cy.get(SEARCH_SHADOW).shadow().find(SEARCH_INPUT).should("have.attr", "aria-expanded", "true");
|
|
62
|
+
cy.get(SEARCH_SHADOW).shadow().find(SUGGESTIONS_LIST).should("have.length", 2);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it("Tab traverses input -> lists[0] -> lists[1] -> input -> exits the component", () => {
|
|
66
|
+
focusInputThroughShadow();
|
|
67
|
+
cy.get(SEARCH_SHADOW).shadow().find(SEARCH_INPUT).should("be.focused");
|
|
68
|
+
|
|
69
|
+
// Tab 1: input -> lists[0]
|
|
70
|
+
cy.realPress("Tab");
|
|
71
|
+
cy.get(SEARCH_SHADOW).shadow().find(SUGGESTIONS_LIST).eq(0).should("be.focused");
|
|
72
|
+
|
|
73
|
+
// Tab 2: lists[0] -> lists[1]
|
|
74
|
+
cy.realPress("Tab");
|
|
75
|
+
cy.get(SEARCH_SHADOW).shadow().find(SUGGESTIONS_LIST).eq(1).should("be.focused");
|
|
76
|
+
|
|
77
|
+
// Tab 3: lists[1] -> input (escape flag is set; popover stays open)
|
|
78
|
+
cy.realPress("Tab");
|
|
79
|
+
cy.get(SEARCH_SHADOW).shadow().find(SEARCH_INPUT).should("be.focused");
|
|
80
|
+
cy.get(SEARCH_SHADOW).shadow().find(SEARCH_INPUT).should("have.attr", "aria-expanded", "true");
|
|
81
|
+
|
|
82
|
+
// Tab 4: input + flag -> focus leaves the search component entirely
|
|
83
|
+
cy.realPress("Tab");
|
|
84
|
+
cy.get("#next-focus").should("be.focused");
|
|
85
|
+
cy.get(SEARCH_SHADOW).shadow().find(SEARCH_INPUT).should("not.be.focused");
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it("Shift+Tab traverses input -> lists[1] -> lists[0] -> input -> exits the component backward", () => {
|
|
89
|
+
// Place a focusable element BEFORE the search so Shift+Tab past the input has somewhere to go.
|
|
90
|
+
cy.mount(
|
|
91
|
+
<div>
|
|
92
|
+
<a id="prev-focus" href="#">Previous focus</a>
|
|
93
|
+
<UDExSearch
|
|
94
|
+
id="search"
|
|
95
|
+
show-suggested-popover
|
|
96
|
+
open-suggestion-popup
|
|
97
|
+
/>
|
|
98
|
+
</div>,
|
|
99
|
+
);
|
|
100
|
+
setSuggestionList(SUGGESTION_LIST_BOTH);
|
|
101
|
+
cy.get(SEARCH_SHADOW).shadow().find(SEARCH_INPUT).should("have.attr", "aria-expanded", "true");
|
|
102
|
+
cy.get(SEARCH_SHADOW).shadow().find(SUGGESTIONS_LIST).should("have.length", 2);
|
|
103
|
+
|
|
104
|
+
focusInputThroughShadow();
|
|
105
|
+
|
|
106
|
+
// Shift+Tab 1: input -> lists[1] (last list)
|
|
107
|
+
cy.realPress(["Shift", "Tab"]);
|
|
108
|
+
cy.get(SEARCH_SHADOW).shadow().find(SUGGESTIONS_LIST).eq(1).should("be.focused");
|
|
109
|
+
|
|
110
|
+
// Shift+Tab 2: lists[1] -> lists[0]
|
|
111
|
+
cy.realPress(["Shift", "Tab"]);
|
|
112
|
+
cy.get(SEARCH_SHADOW).shadow().find(SUGGESTIONS_LIST).eq(0).should("be.focused");
|
|
113
|
+
|
|
114
|
+
// Shift+Tab 3: lists[0] -> input (escape flag is set; popover stays open)
|
|
115
|
+
cy.realPress(["Shift", "Tab"]);
|
|
116
|
+
cy.get(SEARCH_SHADOW).shadow().find(SEARCH_INPUT).should("be.focused");
|
|
117
|
+
cy.get(SEARCH_SHADOW).shadow().find(SEARCH_INPUT).should("have.attr", "aria-expanded", "true");
|
|
118
|
+
|
|
119
|
+
// Shift+Tab 4: input + flag -> focus leaves the search component backward
|
|
120
|
+
cy.realPress(["Shift", "Tab"]);
|
|
121
|
+
cy.get("#prev-focus").should("be.focused");
|
|
122
|
+
cy.get(SEARCH_SHADOW).shadow().find(SEARCH_INPUT).should("not.be.focused");
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
it("Shift+Tab from reset button while popup is open moves focus to input (not trapped)", () => {
|
|
126
|
+
cy.mount(
|
|
127
|
+
<div>
|
|
128
|
+
<UDExSearch
|
|
129
|
+
id="search"
|
|
130
|
+
show-suggested-popover
|
|
131
|
+
open-suggestion-popup
|
|
132
|
+
value="SAP"
|
|
133
|
+
/>
|
|
134
|
+
<a id="next-focus" href="#">Next focus</a>
|
|
135
|
+
</div>,
|
|
136
|
+
);
|
|
137
|
+
setSuggestionList(SUGGESTION_LIST_BOTH);
|
|
138
|
+
cy.get(SEARCH_SHADOW).shadow().find(SEARCH_INPUT).should("have.attr", "aria-expanded", "true");
|
|
139
|
+
cy.get(SEARCH_SHADOW).shadow().find(SUGGESTIONS_LIST).should("have.length", 2);
|
|
140
|
+
|
|
141
|
+
focusInputThroughShadow();
|
|
142
|
+
|
|
143
|
+
// Navigate through popover lists with Tab and return to input via escape flag.
|
|
144
|
+
cy.realPress("Tab");
|
|
145
|
+
cy.get(SEARCH_SHADOW).shadow().find(SUGGESTIONS_LIST).eq(0).should("be.focused");
|
|
146
|
+
cy.realPress("Tab");
|
|
147
|
+
cy.get(SEARCH_SHADOW).shadow().find(SUGGESTIONS_LIST).eq(1).should("be.focused");
|
|
148
|
+
cy.realPress("Tab");
|
|
149
|
+
cy.get(SEARCH_SHADOW).shadow().find(SEARCH_INPUT).should("be.focused");
|
|
150
|
+
|
|
151
|
+
// Tab again: escape flag is consumed, natural Tab moves to reset button.
|
|
152
|
+
cy.realPress("Tab");
|
|
153
|
+
cy.get(SEARCH_SHADOW).shadow().find(SEARCH_INPUT).should("not.be.focused");
|
|
154
|
+
|
|
155
|
+
// Shift+Tab from reset button must return to input — not get trapped.
|
|
156
|
+
cy.realPress(["Shift", "Tab"]);
|
|
157
|
+
cy.get(SEARCH_SHADOW).shadow().find(SEARCH_INPUT).should("be.focused");
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
it("Down arrow from a focused list moves focus into individual item buttons; Up arrow reverses it", () => {
|
|
161
|
+
focusInputThroughShadow();
|
|
162
|
+
|
|
163
|
+
// Tab into lists[0].
|
|
164
|
+
cy.realPress("Tab");
|
|
165
|
+
cy.get(SEARCH_SHADOW).shadow().find(SUGGESTIONS_LIST).eq(0).should("be.focused");
|
|
166
|
+
|
|
167
|
+
// ArrowDown: first item in lists[0] receives focus & aria-selected.
|
|
168
|
+
cy.realPress("ArrowDown");
|
|
169
|
+
cy.get(SEARCH_SHADOW)
|
|
170
|
+
.shadow()
|
|
171
|
+
.find(SUGGESTIONS_LIST)
|
|
172
|
+
.eq(0)
|
|
173
|
+
.find("[ui5-button]")
|
|
174
|
+
.eq(0)
|
|
175
|
+
.should("have.attr", "aria-selected", "true");
|
|
176
|
+
|
|
177
|
+
// ArrowDown: focus moves to the second item in lists[0].
|
|
178
|
+
cy.realPress("ArrowDown");
|
|
179
|
+
cy.get(SEARCH_SHADOW)
|
|
180
|
+
.shadow()
|
|
181
|
+
.find(SUGGESTIONS_LIST)
|
|
182
|
+
.eq(0)
|
|
183
|
+
.find("[ui5-button]")
|
|
184
|
+
.eq(1)
|
|
185
|
+
.should("have.attr", "aria-selected", "true");
|
|
186
|
+
|
|
187
|
+
// ArrowDown from the LAST item of lists[0] hops focus to lists[1] (selectedItemIndex resets to -1).
|
|
188
|
+
cy.realPress("ArrowDown");
|
|
189
|
+
cy.get(SEARCH_SHADOW).shadow().find(SUGGESTIONS_LIST).eq(1).should("be.focused");
|
|
190
|
+
|
|
191
|
+
// ArrowDown inside lists[1]: focuses first item there.
|
|
192
|
+
cy.realPress("ArrowDown");
|
|
193
|
+
cy.get(SEARCH_SHADOW)
|
|
194
|
+
.shadow()
|
|
195
|
+
.find(SUGGESTIONS_LIST)
|
|
196
|
+
.eq(1)
|
|
197
|
+
.find("[ui5-button]")
|
|
198
|
+
.eq(0)
|
|
199
|
+
.should("have.attr", "aria-selected", "true");
|
|
200
|
+
|
|
201
|
+
// ArrowUp from the FIRST item of lists[1] hops focus back to lists[0].
|
|
202
|
+
cy.realPress("ArrowUp");
|
|
203
|
+
cy.get(SEARCH_SHADOW).shadow().find(SUGGESTIONS_LIST).eq(0).should("be.focused");
|
|
204
|
+
});
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
describe("Always-open popover with a single list (recommended only)", () => {
|
|
208
|
+
beforeEach(() => {
|
|
209
|
+
cy.mount(renderAlwaysOpenSingleList());
|
|
210
|
+
setSuggestionList(SUGGESTION_LIST_RECOMMENDED_ONLY);
|
|
211
|
+
cy.get(SEARCH_SHADOW).shadow().find(SEARCH_INPUT).should("have.attr", "aria-expanded", "true");
|
|
212
|
+
cy.get(SEARCH_SHADOW).shadow().find(SUGGESTIONS_LIST).should("have.length", 1);
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
it("Tab traverses input -> lists[0] -> input -> exits the component", () => {
|
|
216
|
+
focusInputThroughShadow();
|
|
217
|
+
|
|
218
|
+
// Tab 1: input -> lists[0]
|
|
219
|
+
cy.realPress("Tab");
|
|
220
|
+
cy.get(SEARCH_SHADOW).shadow().find(SUGGESTIONS_LIST).eq(0).should("be.focused");
|
|
221
|
+
|
|
222
|
+
// Tab 2: lists[0] -> input (escape flag set; popover stays open)
|
|
223
|
+
cy.realPress("Tab");
|
|
224
|
+
cy.get(SEARCH_SHADOW).shadow().find(SEARCH_INPUT).should("be.focused");
|
|
225
|
+
cy.get(SEARCH_SHADOW).shadow().find(SEARCH_INPUT).should("have.attr", "aria-expanded", "true");
|
|
226
|
+
|
|
227
|
+
// Tab 3: input + flag -> exits component
|
|
228
|
+
cy.realPress("Tab");
|
|
229
|
+
cy.get("#next-focus").should("be.focused");
|
|
230
|
+
cy.get(SEARCH_SHADOW).shadow().find(SEARCH_INPUT).should("not.be.focused");
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
it("Shift+Tab traverses input -> lists[0] -> input -> exits the component backward", () => {
|
|
234
|
+
cy.mount(
|
|
235
|
+
<div>
|
|
236
|
+
<a id="prev-focus" href="#">Previous focus</a>
|
|
237
|
+
<UDExSearch
|
|
238
|
+
id="search"
|
|
239
|
+
show-suggested-popover
|
|
240
|
+
open-suggestion-popup
|
|
241
|
+
/>
|
|
242
|
+
</div>,
|
|
243
|
+
);
|
|
244
|
+
setSuggestionList(SUGGESTION_LIST_RECOMMENDED_ONLY);
|
|
245
|
+
cy.get(SEARCH_SHADOW).shadow().find(SEARCH_INPUT).should("have.attr", "aria-expanded", "true");
|
|
246
|
+
cy.get(SEARCH_SHADOW).shadow().find(SUGGESTIONS_LIST).should("have.length", 1);
|
|
247
|
+
|
|
248
|
+
focusInputThroughShadow();
|
|
249
|
+
|
|
250
|
+
// Shift+Tab 1: input -> lists[0] (the only / last list)
|
|
251
|
+
cy.realPress(["Shift", "Tab"]);
|
|
252
|
+
cy.get(SEARCH_SHADOW).shadow().find(SUGGESTIONS_LIST).eq(0).should("be.focused");
|
|
253
|
+
|
|
254
|
+
// Shift+Tab 2: lists[0] -> input (escape flag set)
|
|
255
|
+
cy.realPress(["Shift", "Tab"]);
|
|
256
|
+
cy.get(SEARCH_SHADOW).shadow().find(SEARCH_INPUT).should("be.focused");
|
|
257
|
+
cy.get(SEARCH_SHADOW).shadow().find(SEARCH_INPUT).should("have.attr", "aria-expanded", "true");
|
|
258
|
+
|
|
259
|
+
// Shift+Tab 3: input + flag -> exits component backward
|
|
260
|
+
cy.realPress(["Shift", "Tab"]);
|
|
261
|
+
cy.get("#prev-focus").should("be.focused");
|
|
262
|
+
cy.get(SEARCH_SHADOW).shadow().find(SEARCH_INPUT).should("not.be.focused");
|
|
263
|
+
});
|
|
264
|
+
});
|
|
265
|
+
});
|
|
@@ -505,6 +505,24 @@ describe("SelectBox", () => {
|
|
|
505
505
|
.should("have.text", "Python");
|
|
506
506
|
});
|
|
507
507
|
|
|
508
|
+
it("MultiSelect mode - navigation label is cleared from input after Tab closes the dropdown", () => {
|
|
509
|
+
cy.mount(multiSelect);
|
|
510
|
+
cy.get("#select-box").shadow()
|
|
511
|
+
.find(".udex-text-field__search")
|
|
512
|
+
.click()
|
|
513
|
+
.realPress("ArrowDown");
|
|
514
|
+
|
|
515
|
+
cy.get("#select-box").shadow()
|
|
516
|
+
.find(".udex-text-field__single-label")
|
|
517
|
+
.should("have.text", "JavaScript");
|
|
518
|
+
|
|
519
|
+
cy.realPress("Tab");
|
|
520
|
+
|
|
521
|
+
cy.get("#select-box").shadow()
|
|
522
|
+
.find(".udex-text-field__single-label")
|
|
523
|
+
.should("not.exist");
|
|
524
|
+
});
|
|
525
|
+
|
|
508
526
|
it("MultiSelect mode - after clicking on the item dropdown is opened", () => {
|
|
509
527
|
cy.mount(multiSelect);
|
|
510
528
|
cy.get("#select-box").shadow().find(".udex-text-field__trigger").click();
|
|
@@ -1155,7 +1173,7 @@ describe("SelectBox", () => {
|
|
|
1155
1173
|
.find("udex-tag-group").shadow()
|
|
1156
1174
|
.find("udex-tag")
|
|
1157
1175
|
.first()
|
|
1158
|
-
.should("have.attr", "text", "5
|
|
1176
|
+
.should("have.attr", "text", "5 stars");
|
|
1159
1177
|
});
|
|
1160
1178
|
|
|
1161
1179
|
it("MultiSelect mode - no single-label shown after opening dropdown and clicking outside", () => {
|