Skip to content
331 changes: 330 additions & 1 deletion packages/main/cypress/specs/Toolbar.cy.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,335 @@ describe("Toolbar general interaction", () => {
.should("have.been.calledOnce");
});

it("Should navigate items with arrows and Tab/Shift+Tab within toolbar", () => {
cy.mount(
<>
<Toolbar>
<ToolbarButton text="First"></ToolbarButton>
<ToolbarSelect>
<ToolbarSelectOption>One</ToolbarSelectOption>
<ToolbarSelectOption>Two</ToolbarSelectOption>
</ToolbarSelect>
<ToolbarButton text="Last"></ToolbarButton>
</Toolbar>
<input data-testid="after" />
</>
);

cy.get("ui5-toolbar-button")
.first()
.shadow()
.find("ui5-button")
.realClick()
.should("be.focused");

// Arrow navigation from First to Select
cy.realPress("ArrowRight");
cy.get("ui5-toolbar-select")
.shadow()
.find("ui5-select")
.should("be.focused");

// Arrow navigation from Select should not get trapped
cy.realPress("ArrowRight");
cy.get("ui5-toolbar-button")
.last()
.shadow()
.find("ui5-button")
.should("be.focused");

cy.realPress("ArrowLeft");
cy.get("ui5-toolbar-select")
.shadow()
.find("ui5-select")
.should("be.focused");

// Tab from Select should navigate to Last button (not exit)
cy.get("ui5-toolbar-select")
.shadow()
.find("ui5-select")
.realPress("Tab");
cy.get("ui5-toolbar-button")
.last()
.shadow()
.find("ui5-button")
.should("be.focused");

// Tab from Last button should exit to after input
cy.get("ui5-toolbar-button")
.last()
.shadow()
.find("ui5-button")
.realPress("Tab");
cy.get("[data-testid='after']")
.should("be.focused");

// Shift+Tab from after input should go back to Last button
cy.get("[data-testid='after']")
.realPress(["Shift", "Tab"]);
cy.get("ui5-toolbar-button")
.last()
.shadow()
.find("ui5-button")
.should("be.focused");

// Shift+Tab from Last should navigate to Select
cy.get("ui5-toolbar-button")
.last()
.shadow()
.find("ui5-button")
.realPress(["Shift", "Tab"]);
cy.get("ui5-toolbar-select")
.shadow()
.find("ui5-select")
.should("be.focused");
});

it("Should focus first item on entry and restore last focused on re-entry", () => {
cy.mount(
<>
<input data-testid="before" />
<Toolbar>
<ToolbarButton text="First"></ToolbarButton>
<ToolbarSelect>
<ToolbarSelectOption>One</ToolbarSelectOption>
<ToolbarSelectOption>Two</ToolbarSelectOption>
</ToolbarSelect>
<ToolbarButton text="Last"></ToolbarButton>
</Toolbar>
<input data-testid="after" />
</>
);

// Tab into toolbar should focus first item
cy.get("[data-testid='before']")
.realClick();

cy.get("[data-testid='before']")
.realPress("Tab");
cy.get("ui5-toolbar-button")
.first()
.shadow()
.find("ui5-button")
.should("be.focused");

// Navigate to Select
cy.realPress("ArrowRight");
cy.get("ui5-toolbar-select")
.shadow()
.find("ui5-select")
.should("be.focused");

// Tab out of toolbar to 'after' input
cy.get("ui5-toolbar-select")
.shadow()
.find("ui5-select")
.realPress("Tab");
cy.get("ui5-toolbar-button")
.last()
.shadow()
.find("ui5-button")
.should("be.focused");

cy.get("ui5-toolbar-button")
.last()
.shadow()
.find("ui5-button")
.realPress("Tab");
cy.get("[data-testid='after']")
.should("be.focused");

// Shift+Tab back into toolbar should restore last focused item (Select)
cy.get("[data-testid='after']")
.realPress(["Shift", "Tab"]);
cy.get("ui5-toolbar-button")
.last()
.shadow()
.find("ui5-button")
.should("be.focused");

cy.get("ui5-toolbar-button")
.last()
.shadow()
.find("ui5-button")
.realPress(["Shift", "Tab"]);
cy.get("ui5-toolbar-select")
.shadow()
.find("ui5-select")
.should("be.focused");
});

it("Should navigate toolbar-item wrapped controls with arrows", () => {
cy.mount(
<Toolbar>
<ToolbarItem><Button>First</Button></ToolbarItem>
<ToolbarItem><Button>Second</Button></ToolbarItem>
<ToolbarItem><Button>Third</Button></ToolbarItem>
</Toolbar>
);

cy.get("ui5-button")
.first()
.shadow()
.find("button")
.realClick()
.should("be.focused");

cy.realPress("ArrowRight");
cy.get("ui5-button")
.eq(1)
.should("be.focused");

cy.realPress("ArrowRight");
cy.get("ui5-button")
.eq(2)
.should("be.focused");
});

it("Should skip non-focusable toolbar-item content during arrow navigation", () => {
cy.mount(
<Toolbar>
<ToolbarButton text="First"></ToolbarButton>
<ToolbarItem><span>Not focusable</span></ToolbarItem>
<ToolbarButton text="Last"></ToolbarButton>
</Toolbar>
);

cy.get("ui5-toolbar-button")
.first()
.shadow()
.find("ui5-button")
.realClick()
.should("be.focused");

cy.realPress("ArrowRight");
cy.get("ui5-toolbar-button")
.last()
.should("be.focused");
});

it("Should swap left and right arrow behavior in RTL", () => {
cy.document().then(doc => {
doc.body.setAttribute("dir", "rtl");
});

cy.mount(
<Toolbar>
<ToolbarButton text="First"></ToolbarButton>
<ToolbarButton text="Second"></ToolbarButton>
<ToolbarButton text="Third"></ToolbarButton>
</Toolbar>
);

cy.get("ui5-toolbar-button")
.eq(1)
.shadow()
.find("ui5-button")
.realClick()
.should("be.focused");

cy.realPress("ArrowLeft");
cy.get("ui5-toolbar-button")
.eq(2)
.shadow()
.find("ui5-button")
.should("be.focused");

cy.realPress("ArrowRight");
cy.get("ui5-toolbar-button")
.eq(1)
.shadow()
.find("ui5-button")
.should("be.focused");

cy.document().then(doc => {
doc.body.removeAttribute("dir");
});
});

it("Should preserve Home/End behavior for text inputs inside toolbar-item", () => {
cy.mount(
<Toolbar>
<ToolbarButton text="First"></ToolbarButton>
<ToolbarItem>
<input data-testid="editor" defaultValue="abcdef" />
</ToolbarItem>
<ToolbarButton text="Last"></ToolbarButton>
</Toolbar>
);

cy.get("[data-testid='editor']")
.realClick()
.then($input => {
const input = $input[0] as HTMLInputElement;
input.setSelectionRange(3, 3);
});

cy.realPress("Home");
cy.get("[data-testid='editor']")
.should("be.focused")
.then($input => {
expect(($input[0] as HTMLInputElement).selectionStart).to.equal(0);
});

cy.get("[data-testid='editor']")
.then($input => {
const input = $input[0] as HTMLInputElement;
input.setSelectionRange(2, 2);
});

cy.realPress("End");
cy.get("[data-testid='editor']")
.should("be.focused")
.then($input => {
expect(($input[0] as HTMLInputElement).selectionStart).to.equal(6);
});
});

it("Should not suppress input arrow/home/end behavior inside overflow popover", () => {
cy.viewport(220, 600);

cy.mount(
<Toolbar>
<ToolbarButton text="First"></ToolbarButton>
<ToolbarItem overflow-priority="AlwaysOverflow">
<input data-testid="overflow-editor" defaultValue="abcdef" />
</ToolbarItem>
<ToolbarButton text="Last"></ToolbarButton>
</Toolbar>
);

cy.get("[ui5-toolbar]")
.shadow()
.find(".ui5-tb-overflow-btn")
.realClick();

cy.get("[ui5-toolbar]")
.shadow()
.find(".ui5-overflow-popover")
.should("have.prop", "open", true);

cy.get("[data-testid='overflow-editor']")
.realClick()
.then($input => {
const input = $input[0] as HTMLInputElement;
input.setSelectionRange(2, 2);
});

cy.realPress("ArrowLeft");
cy.get("[data-testid='overflow-editor']")
.should("be.focused")
.then($input => {
expect(($input[0] as HTMLInputElement).selectionStart).to.equal(1);
});

cy.realPress("Home");
cy.get("[data-testid='overflow-editor']")
.then($input => {
expect(($input[0] as HTMLInputElement).selectionStart).to.equal(0);
});
});

it("Should move button with alwaysOverflow priority to overflow popover", () => {

cy.mount(
Expand Down Expand Up @@ -741,7 +1070,7 @@ describe("ToolbarButton", () => {
const toolbar = $toolbar[0] as Toolbar;
const addButton = document.getElementById("add-btn") as ToolbarButton;

expect(toolbar.itemsToOverflow.includes(addButton)).to.be.true;
expect(toolbar.itemsToOverflow.some(item => item._id === addButton._id)).to.be.true;

const initialOverflowCount = toolbar.itemsToOverflow.length;
const initialItemsWidth = toolbar.itemsWidth;
Expand Down
Loading
Loading