Skip to content
Open
171 changes: 169 additions & 2 deletions packages/fiori/cypress/specs/Search.cy.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import Button from "@ui5/webcomponents/dist/Button.js";
import ButtonDesign from "@ui5/webcomponents/dist/types/ButtonDesign.js";
import Avatar from "@ui5/webcomponents/dist/Avatar.js";
import AvatarSize from "@ui5/webcomponents/dist/types/AvatarSize.js";
import type ResponsivePopover from "@ui5/webcomponents/dist/ResponsivePopover.js";
import { SEARCH_ITEM_SHOW_MORE_COUNT, SEARCH_ITEM_SHOW_MORE_NO_COUNT } from "../../src/generated/i18n/i18n-defaults.js";

describe("Properties", () => {
Expand Down Expand Up @@ -791,7 +792,7 @@ describe("Events", () => {

cy.get("[ui5-search]")
.then(search => {
search.get(0).addEventListener("ui5-close", cy.stub().as("closed"));
search.get(0).addEventListener("ui5-close", cy.spy().as("closed"));
});

cy.get("[ui5-search]")
Expand All @@ -818,7 +819,7 @@ describe("Events", () => {

cy.get("[ui5-search]")
.then(search => {
search.get(0).addEventListener("ui5-close", cy.stub().as("closed"));
search.get(0).addEventListener("ui5-close", cy.spy().as("closed"));
});

cy.get("[ui5-search]")
Expand Down Expand Up @@ -1075,6 +1076,172 @@ describe("Events", () => {
cy.get("ui5-search-item").eq(0)
.should("not.have.attr", "selected");
});

it("should reset suggestions highlight on pressing 'clear' button", () => {
cy.mount(
<Search showClearIcon>
<SearchItem text="Item 1" />
</Search>
);

cy.get("[ui5-search]").as("search");

cy.get("@search")
.shadow()
.find("input")
.as("input");

cy.get("@input")
.realClick();

cy.get("@search")
.should("be.focused");

cy.get("@input")
.realPress("I");

cy.get("@search")
.should("have.value", "Item 1");

cy.get("[ui5-search-item]").eq(0)
.should("have.attr", "highlight-text", "I");

cy.get("@search")
.shadow()
.find("[ui5-icon][name='decline']")
.realClick();

cy.get("@search")
.should("have.value", "");

cy.get("@search")
.should("not.have.attr", "open");

cy.get("@search")
.invoke("prop", "open", true);

cy.get("ui5-search-item").eq(0)
.should("have.attr", "highlight-text", "");
});

it("should close the popover on search if no suggestion is selected", () => {
cy.mount(
<Search showClearIcon>
<SearchItem text="Item 1" />
</Search>
);

cy.get("[ui5-search]").as("search");

cy.get("@search")
.shadow()
.find("input")
.as("input");

cy.get("@input")
.realClick();

cy.get("@search")
.should("be.focused");

cy.get("@input")
.realPress("P"); // no matching suggestion

cy.get("@search")
.should("have.value", "P");

cy.get("@search")
.shadow()
.find("[ui5-icon][name='search']")
.realClick();

cy.get("@search")
.should("not.have.attr", "open");
});

it("should close the popover on 'search' if suggestion is selected", () => {
cy.mount(
<Search showClearIcon>
<SearchItem text="Item 1" />
</Search>
);

cy.get("[ui5-search]").as("search");

cy.get("@search")
.shadow()
.find("input")
.as("input");

cy.get("@input")
.realClick();

cy.get("@search")
.should("be.focused");

cy.get("@input")
.realPress("I"); // no matching suggestion

cy.get("@search")
.should("have.value", "Item 1");

cy.get("@search")
.shadow()
.find("[ui5-icon][name='search']")
.realClick();

cy.get("@search")
.should("not.have.attr", "open");
});

it("should open picker by default when 'open' property is set to true", () => {
cy.mount(
<Search open>
<SearchItem text="Item 1" />
</Search>
);

cy.get("[ui5-search]")
.shadow()
.find<ResponsivePopover>("[ui5-responsive-popover]")
.ui5ResponsivePopoverOpened();
});

it("should not open picker if text is deleted and there are no items", () => {
const handleInput = (e: any) => {
if (e.target.value) {
const item = document.createElement("ui5-search-item");
item.setAttribute("text", e.target.value);
e.target.appendChild(item);
} else {
e.target.innerHTML = "";
}
};

cy.mount(<Search showClearIcon noTypeahead onInput={handleInput}></Search>);

cy.get("[ui5-search]").as("search");

cy.get("@search")
.shadow()
.find("input")
.as("input");

cy.get("@input")
.realClick();

cy.get("@input")
.realPress("I");

cy.get("@input")
.realPress("Backspace");

cy.get("@search")
.should("have.value", "");

cy.get("@search")
.should("not.have.attr", "open");
});
});

describe("Accessibility", () => {
Expand Down
41 changes: 34 additions & 7 deletions packages/fiori/src/Search.ts
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,12 @@ class Search extends SearchField {
*/
_proposedItem?: ISearchSuggestionItem;

/**
* This property is used during rendering to indicate that the user has started typing in the input
* @private
*/
_isTyping: boolean;

@i18n("@ui5/webcomponents-fiori")
static i18nBundle: I18nBundle;

Expand All @@ -222,26 +228,32 @@ class Search extends SearchField {
// The typed in value.
this._typedInValue = "";
this._valueBeforeOpen = this.getAttribute("value") || "";
this._isTyping = false;
}

onBeforeRendering() {
super.onBeforeRendering();

if (this.collapsed && !isPhone()) {
this.open = false;
return;
}

const innerInput = this.nativeInput;
const autoCompletedChars = innerInput && (innerInput.selectionEnd! - innerInput.selectionStart!);

this.open = this.open || (this._popoupHasAnyContent() && this._isTyping && innerInput!.value.length > 0);

// If there is already a selection the autocomplete has already been performed
if (this._shouldAutocomplete && !autoCompletedChars) {
const item = this._getFirstMatchingItem(this.value);
this._proposedItem = item;

if (!isPhone()) {
this.open = this._popoupHasAnyContent();
}

if (item) {
this._handleTypeAhead(item);
this._selectMatchingItem(item);
} else {
this._deselectItems();
}
}

Expand Down Expand Up @@ -311,8 +323,6 @@ class Search extends SearchField {
this._innerValue = originalValue;
this._performTextSelection = true;
this.value = originalValue;

this._shouldAutocomplete = false;
}

_startsWithMatchingItems(str: string): Array<ISearchSuggestionItem> {
Expand Down Expand Up @@ -383,6 +393,7 @@ class Search extends SearchField {

innerInput.setSelectionRange(this.value.length, this.value.length);
this.open = false;
this._isTyping = false;
}

_onMobileInputKeydown(e: KeyboardEvent) {
Expand All @@ -401,6 +412,7 @@ class Search extends SearchField {
_handleEscape() {
this.value = this._typedInValue || this.value;
this._innerValue = this.value;
this._isTyping = false;
}

_handleInput(e: InputEvent) {
Expand All @@ -411,7 +423,17 @@ class Search extends SearchField {
return;
}

this.open = ((e.currentTarget as HTMLInputElement).value.length > 0) && this._popoupHasAnyContent();
this._isTyping = true;
this.open = this.value.length > 0;
}

_handleClear(): void {
super._handleClear();

this._typedInValue = "";
this._innerValue = "";
this._shouldAutocomplete = false;
this.open = false;
}

_popoupHasAnyContent() {
Expand Down Expand Up @@ -466,7 +488,10 @@ class Search extends SearchField {
this.value = item.text;
this._innerValue = this.value;
this._typedInValue = this.value;
this._shouldAutocomplete = false;
this._performTextSelection = true;
this.open = false;
this._isTyping = false;
this.focus();
}

Expand Down Expand Up @@ -502,6 +527,7 @@ class Search extends SearchField {
}

this.open = false;
this._isTyping = false;
}

_handleBeforeClose(e: CustomEvent<PopupBeforeCloseEventDetail>) {
Expand All @@ -518,6 +544,7 @@ class Search extends SearchField {

_handleClose() {
this.open = false;
this._isTyping = false;
this.fireDecoratorEvent("close");
}

Expand Down
39 changes: 22 additions & 17 deletions packages/fiori/test/pages/Search.html
Original file line number Diff line number Diff line change
Expand Up @@ -403,24 +403,29 @@

const searchLazy = document.getElementById('search-lazy');
searchLazy.addEventListener('ui5-input', (e) => {
// clear search items
searchLazy.innerHTML = '';

searchLazy.getSlottedNodes("items").forEach(item => {
item.remove();
});

// simulate lazy loading
setTimeout(() => {
const lazyData = [
{ name: 'Red Apple', category: 'Fruit' },
{ name: 'Apple', category: 'Fruit' },
{ name: 'Banana', category: 'Fruit' },
{ name: 'Orange', category: 'Fruit' },
{ name: 'Grapes', category: 'Fruit' },
];
createItems(searchLazy, lazyData);
}, 100);
// clear search items
searchLazy.innerHTML = '';

searchLazy.getSlottedNodes("items").forEach(item => {
item.remove();
});
}, 100)


if(e.target.value.length){
// simulate lazy loading
setTimeout(() => {
const lazyData = [
{ name: `Red Apple ${Math.random()}`, category: 'Fruit' },
{ name: `Apple ${Math.random()}`, category: 'Fruit' },
{ name: `Banana ${Math.random()}`, category: 'Fruit' },
{ name: `Orange ${Math.random()}`, category: 'Fruit' },
{ name: `Grapes ${Math.random()}`, category: 'Fruit' },
];
createItems(searchLazy, lazyData);
}, 300);
}
});
</script>
</body>
Expand Down
Loading