Skip to content

Commit

Permalink
fix(MenuItem): tooltip should on all item and not only on title (#1914)
Browse files Browse the repository at this point in the history
Co-authored-by: Tal Koren <[email protected]>
Co-authored-by: Tal Koren <[email protected]>
  • Loading branch information
3 people authored Jan 29, 2024
1 parent f75c73d commit 5151bde
Show file tree
Hide file tree
Showing 9 changed files with 130 additions and 19 deletions.
1 change: 1 addition & 0 deletions src/components/Menu/Menu/Menu.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
.menu {
margin: unset;
padding: unset;
position: relative;
}

.menu:focus {
Expand Down
14 changes: 14 additions & 0 deletions src/components/Menu/Menu/__tests__/__snapshots__/menu.jest.js.snap
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,13 @@ exports[`Snapshots renders correctly with children 1`] = `
>
item 1
</div>
<div
aria-hidden={true}
className="hiddenTitle"
tabIndex={-1}
>
item 1
</div>
<div
style={
Expand All @@ -64,6 +71,13 @@ exports[`Snapshots renders correctly with children 1`] = `
>
item 2
</div>
<div
aria-hidden={true}
className="hiddenTitle"
tabIndex={-1}
>
item 2
</div>
<div
style={
Expand Down
40 changes: 30 additions & 10 deletions src/components/Menu/Menu/__tests__/menu-interactions.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,41 +19,57 @@ const TWO_DEPTHS_MENU_TEXTS = {
TOP_MENU_NON_SUB_MENU_ITEM: "Another item"
};

const HIDDEN_ELEMENT_SELECTOR = "[aria-hidden]";

const showSubSubMenusOnHover = async canvas => {
const menuElement = getMenuElement(canvas);

const topMenuItem = getByText(menuElement, TWO_DEPTHS_MENU_TEXTS.TOP_MENU_SUB_MENU_ITEM);
const topMenuItem = getByText(menuElement, TWO_DEPTHS_MENU_TEXTS.TOP_MENU_SUB_MENU_ITEM, {
ignore: HIDDEN_ELEMENT_SELECTOR
});
await userEvent.hover(topMenuItem);

const innerMenuItem = getByText(menuElement, TWO_DEPTHS_MENU_TEXTS.SUB_MENU_ITEM);
const innerMenuItem = getByText(menuElement, TWO_DEPTHS_MENU_TEXTS.SUB_MENU_ITEM, {
ignore: HIDDEN_ELEMENT_SELECTOR
});
await userEvent.hover(innerMenuItem);

// validate showing sub sub item
const optionToSelect = await waitForElementVisible(() =>
within(menuElement).findByText(TWO_DEPTHS_MENU_TEXTS.SUB_SUB_MENU_ITEM)
within(menuElement).findByText(TWO_DEPTHS_MENU_TEXTS.SUB_SUB_MENU_ITEM, { ignore: HIDDEN_ELEMENT_SELECTOR })
);
await clickElement(optionToSelect);
expect(document.activeElement).toHaveTextContent(TWO_DEPTHS_MENU_TEXTS.SUB_SUB_MENU_ITEM);

//close the sub-menus on hovering the top-level menu
await userEvent.hover(getByText(menuElement, TWO_DEPTHS_MENU_TEXTS.TOP_MENU_NON_SUB_MENU_ITEM));
expect(canvas.queryByText(TWO_DEPTHS_MENU_TEXTS.SUB_MENU_ITEM)).not.toBeInTheDocument();
expect(canvas.queryByText(TWO_DEPTHS_MENU_TEXTS.SUB_SUB_MENU_ITEM)).not.toBeInTheDocument();
await userEvent.hover(
getByText(menuElement, TWO_DEPTHS_MENU_TEXTS.TOP_MENU_NON_SUB_MENU_ITEM, { ignore: HIDDEN_ELEMENT_SELECTOR })
);
expect(
canvas.queryByText(TWO_DEPTHS_MENU_TEXTS.SUB_MENU_ITEM, { ignore: HIDDEN_ELEMENT_SELECTOR })
).not.toBeInTheDocument();
expect(
canvas.queryByText(TWO_DEPTHS_MENU_TEXTS.SUB_SUB_MENU_ITEM, { ignore: HIDDEN_ELEMENT_SELECTOR })
).not.toBeInTheDocument();
};

const showSubSubMenusWithKeyboard = async canvas => {
const menuElement = getMenuElement(canvas);

//set the initial focus, to make the keyboard events work
const topMenuItem = getByText(menuElement, TWO_DEPTHS_MENU_TEXTS.TOP_MENU_SUB_MENU_ITEM);
const topMenuItem = getByText(menuElement, TWO_DEPTHS_MENU_TEXTS.TOP_MENU_SUB_MENU_ITEM, {
ignore: HIDDEN_ELEMENT_SELECTOR
});
await userEvent.click(topMenuItem);

//open sub menu
await pressNavigationKey(NavigationCommand.DOWN_ARROW);
await pressNavigationKey(NavigationCommand.DOWN_ARROW);
await pressNavigationKey(NavigationCommand.RIGHT_ARROW);
await waitForElementVisible(() =>
within(menuElement).findByText(new RegExp(`^${TWO_DEPTHS_MENU_TEXTS.SUB_MENU_ITEM}$`))
within(menuElement).findByText(new RegExp(`^${TWO_DEPTHS_MENU_TEXTS.SUB_MENU_ITEM}$`), {
ignore: HIDDEN_ELEMENT_SELECTOR
})
);
expectActiveElementToHavePartialText(TWO_DEPTHS_MENU_TEXTS.SUB_MENU_ITEM);

Expand All @@ -62,13 +78,17 @@ const showSubSubMenusWithKeyboard = async canvas => {
await pressNavigationKey(NavigationCommand.DOWN_ARROW);
await pressNavigationKey(NavigationCommand.RIGHT_ARROW);
await waitForElementVisible(() =>
within(menuElement).findByText(new RegExp(`^${TWO_DEPTHS_MENU_TEXTS.SUB_SUB_MENU_ITEM}$`))
within(menuElement).findByText(new RegExp(`^${TWO_DEPTHS_MENU_TEXTS.SUB_SUB_MENU_ITEM}$`), {
ignore: HIDDEN_ELEMENT_SELECTOR
})
);
expectActiveElementToHavePartialText(TWO_DEPTHS_MENU_TEXTS.SUB_SUB_MENU_ITEM);

//close sub-sub-menu - using left arrow
await pressNavigationKey(NavigationCommand.LEFT_ARROW);
expect(canvas.queryByText(menuElement, TWO_DEPTHS_MENU_TEXTS.SUB_SUB_MENU_ITEM)).not.toBeInTheDocument();
expect(
canvas.queryByText(menuElement, TWO_DEPTHS_MENU_TEXTS.SUB_SUB_MENU_ITEM, { ignore: HIDDEN_ELEMENT_SELECTOR })
).not.toBeInTheDocument();
expectActiveElementToHavePartialText(TWO_DEPTHS_MENU_TEXTS.SUB_MENU_ITEM);

//close sub-menu - using escape
Expand Down
7 changes: 7 additions & 0 deletions src/components/Menu/MenuItem/MenuItem.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -103,3 +103,10 @@
flex-grow: 1;
padding-block: 1px;
}

.hiddenTitle {
width: 100%;
position: absolute;
left: 0;
opacity: 0;
}
6 changes: 4 additions & 2 deletions src/components/Menu/MenuItem/MenuItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -334,13 +334,15 @@ const MenuItem: VibeComponent<MenuItemProps> & {
content={shouldShowTooltip ? finalTooltipContent : null}
position={tooltipPosition}
showDelay={tooltipShowDelay}
// Tooltip should be on a whole MenuItem, but it's a breaking change - should be fixed in the next major and then this can be removed
moveBy={icon && tooltipPosition === Tooltip.positions.LEFT ? { main: 30 } : undefined}
{...tooltipProps}
>
<div ref={titleRef} className={styles.title}>
{title}
</div>
{/* Tooltip should be on a whole MenuItem, but it's a breaking change (tooltip adds span) - should be fixed in the next major and then this div be removed */}
<div className={styles.hiddenTitle} aria-hidden tabIndex={-1}>
{title}
</div>
</Tooltip>
{label && <Label kind={Label.kinds.LINE} text={label} />}
{renderSubMenuIconIfNeeded()}
Expand Down
4 changes: 2 additions & 2 deletions src/components/Menu/MenuItem/__tests__/MenuItem.jest.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ describe("<MenuItem />", () => {
});

it("should be able to render menu item text", async () => {
const { getByText } = render(<MenuItem title={title} />);
const menuItemElement = getByText(title);
const { getAllByText } = render(<MenuItem title={title} />);
const menuItemElement = getAllByText(title);
await waitFor(() => expect(menuItemElement).toBeTruthy());
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,13 @@ exports[`Snapshots renders correctly when disabled 1`] = `
>
my item
</div>
<div
aria-hidden={true}
className="hiddenTitle"
tabIndex={-1}
>
my item
</div>
<div
style={
Expand Down Expand Up @@ -42,6 +49,13 @@ exports[`Snapshots renders correctly when selected 1`] = `
>
my item
</div>
<div
aria-hidden={true}
className="hiddenTitle"
tabIndex={-1}
>
my item
</div>
<div
style={
Expand Down Expand Up @@ -70,6 +84,13 @@ exports[`Snapshots renders correctly with a label 1`] = `
>
my item
</div>
<div
aria-hidden={true}
className="hiddenTitle"
tabIndex={-1}
>
my item
</div>
<span
className=""
data-testid="label"
Expand Down Expand Up @@ -112,6 +133,13 @@ exports[`Snapshots renders correctly with custom class name 1`] = `
className="title"
>
</div>
<div
aria-hidden={true}
className="hiddenTitle"
tabIndex={-1}
>
</div>
<div
Expand Down Expand Up @@ -140,6 +168,13 @@ exports[`Snapshots renders correctly with empty props 1`] = `
className="title"
>
</div>
<div
aria-hidden={true}
className="hiddenTitle"
tabIndex={-1}
>
</div>
<div
Expand Down Expand Up @@ -191,6 +226,13 @@ exports[`Snapshots renders correctly with title and SVG icon 1`] = `
>
my item
</div>
<div
aria-hidden={true}
className="hiddenTitle"
tabIndex={-1}
>
my item
</div>
<div
style={
Expand Down Expand Up @@ -231,6 +273,13 @@ exports[`Snapshots renders correctly with title and font icon 1`] = `
>
my item
</div>
<div
aria-hidden={true}
className="hiddenTitle"
tabIndex={-1}
>
my item
</div>
<div
style={
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,13 @@ exports[`SplitButtonMenu renders correctly 1`] = `
>
Test 1
</div>
<div
aria-hidden={true}
className="hiddenTitle"
tabIndex={-1}
>
Test 1
</div>
<div
style={
Expand All @@ -52,6 +59,13 @@ exports[`SplitButtonMenu renders correctly 1`] = `
>
Test 2
</div>
<div
aria-hidden={true}
className="hiddenTitle"
tabIndex={-1}
>
Test 2
</div>
<div
style={
Expand Down
14 changes: 9 additions & 5 deletions src/tests/interactions-utils.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { fireEvent, queries, userEvent, within } from "@storybook/testing-library";
import { BoundFunctions, Screen, waitFor } from "@testing-library/react";
import { BoundFunctions, Screen, SelectorMatcherOptions, waitFor } from "@testing-library/react";
import { NavigationCommand as NavigationCommandType } from "./constants";
import { expect } from "@storybook/jest";

Expand Down Expand Up @@ -145,8 +145,8 @@ export const getFirstByClassName = (className: string) => {
return document.getElementsByClassName(className)[0];
};

export const getByRole = (rootElement: HTMLElement | BoundFunctions<typeof queries>, role: string) => {
return getWithin(rootElement).getByRole(role);
export const getByRole = (rootElement: HTMLElement | BoundFunctions<typeof queries>, role: string, options = {}) => {
return getWithin(rootElement).getByRole(role, options);
};

export const getAllByRole = (rootElement: HTMLElement | BoundFunctions<typeof queries>, role: string) => {
Expand All @@ -161,8 +161,12 @@ export const getAllByLabelText = (rootElement: HTMLElement, text: string) => {
return getWithin(rootElement).getAllByLabelText(text);
};

export const getByText = (rootElement: HTMLElement | BoundFunctions<typeof queries>, text: string) => {
return getWithin(rootElement).getByText(text);
export const getByText = (
rootElement: HTMLElement | BoundFunctions<typeof queries>,
text: string,
options: SelectorMatcherOptions = {}
) => {
return getWithin(rootElement).getByText(text, options);
};

export const getAllByText = (rootElement: HTMLElement | BoundFunctions<typeof queries>, text: string) => {
Expand Down

0 comments on commit 5151bde

Please sign in to comment.