Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(menu-full-screen, modal): ensure the call to action element is focused on close #6986

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion src/components/dialog-full-screen/components.test-pw.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,14 @@ const nestedDialogTitle = "Nested Dialog";

export const DialogFullScreenComponent = ({
children = "This is an example",
open = true,
...props
}: Partial<DialogFullScreenProps>) => {
const [isOpen, setIsOpen] = useState(true);
const [isOpen, setIsOpen] = useState(open);
const ref = useRef<HTMLButtonElement | null>(null);
return (
<>
<Button onClick={() => setIsOpen(true)}>Open Dialog Full Screen</Button>
<DialogFullScreen
focusFirstElement={ref}
open={isOpen}
Expand Down
76 changes: 76 additions & 0 deletions src/components/dialog-full-screen/dialog-full-screen.pw.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -303,6 +303,82 @@ test.describe("render DialogFullScreen component and check properties", () => {
);
});

test("when Dialog Full Screen is opened and then closed, the call to action element should be focused", async ({
mount,
page,
}) => {
await mount(<DialogFullScreenComponent open={false} />);

const button = page
.getByRole("button")
.filter({ hasText: "Open Dialog Full Screen" });
const dialogFullScreen = page.getByRole("dialog");
await expect(button).not.toBeFocused();
await expect(dialogFullScreen).not.toBeVisible();
Parsium marked this conversation as resolved.
Show resolved Hide resolved

await button.click();
await expect(dialogFullScreen).toBeVisible();
const closeButton = page.getByLabel("Close");
await closeButton.click();
await expect(button).toBeFocused();
await expect(dialogFullScreen).not.toBeVisible();
});

test("when Dialog Full Screen is open on render, then closed, opened and then closed again, the call to action element should be focused", async ({
mount,
page,
}) => {
await mount(<DialogFullScreenComponent />);

const dialogFullScreen = page.getByRole("dialog");
await expect(dialogFullScreen).toBeVisible();
const closeButton = page.getByLabel("Close");
await closeButton.click();

const button = page
.getByRole("button")
.filter({ hasText: "Open Dialog Full Screen" });
await expect(button).not.toBeFocused();
await expect(dialogFullScreen).not.toBeVisible();

await button.click();
await expect(dialogFullScreen).toBeVisible();
await closeButton.click();
await expect(button).toBeFocused();
});

test("when nested Dialog's are opened/closed their respective call to action elements should be focused correctly", async ({
mount,
page,
}) => {
await mount(<NestedDialog />);

const firstButton = page
.getByRole("button")
.filter({ hasText: "Open Main Dialog" });
const firstDialog = page.getByRole("dialog").first();
await expect(firstButton).not.toBeFocused();
await expect(firstDialog).not.toBeVisible();

await firstButton.click();
await expect(firstDialog).toBeVisible();
const secondButton = page
.getByRole("button")
.filter({ hasText: "Open Nested Dialog" });
await expect(secondButton).not.toBeFocused();
await secondButton.click();
const secondDialog = page.getByRole("dialog").last();
await expect(secondDialog).toBeVisible();

const secondCloseButton = page.getByLabel("Close").last();
await secondCloseButton.click();
await expect(secondButton).toBeFocused();

const firstCloseButton = page.getByLabel("Close").first();
await firstCloseButton.click();
await expect(firstButton).toBeFocused();
});

test("should render component with autofocus disabled", async ({
mount,
page,
Expand Down
37 changes: 35 additions & 2 deletions src/components/dialog/components.test-pw.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -210,8 +210,12 @@ export const DialogComponentFocusableSelectors = (
);
};

export const DefaultStory = () => {
const [isOpen, setIsOpen] = useState(defaultOpenState);
export const DefaultStory = ({
open = defaultOpenState,
}: {
open?: boolean;
}) => {
const [isOpen, setIsOpen] = useState(open);
return (
<>
<Button onClick={() => setIsOpen(true)}>Open Dialog</Button>
Expand Down Expand Up @@ -254,6 +258,35 @@ export const DefaultStory = () => {
);
};

export const DefaultNestedStory = () => {
const [isFirstDialogOpen, setIsFirstDialogOpen] = useState(false);
const [isNestedDialogOpen, setIsNestedDialogOpen] = useState(false);

return (
<>
<Button onClick={() => setIsFirstDialogOpen(true)}>
Open First Dialog
</Button>
<Dialog
open={isFirstDialogOpen}
onCancel={() => setIsFirstDialogOpen(false)}
title="First Dialog"
>
<Button onClick={() => setIsNestedDialogOpen(true)}>
Open Nested Dialog
</Button>
<Dialog
open={isNestedDialogOpen}
onCancel={() => setIsNestedDialogOpen(false)}
title="Nested Dialog"
>
<Textbox label="Nested Dialog Textbox" />
</Dialog>
</Dialog>
</>
);
};

export const Editable = () => {
const [isOpen, setIsOpen] = useState(defaultOpenState);
const [isDisabled, setIsDisabled] = useState(true);
Expand Down
73 changes: 73 additions & 0 deletions src/components/dialog/dialog.pw.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
DialogWithAutoFocusSelect,
DialogComponentFocusableSelectors,
DefaultStory,
DefaultNestedStory,
Editable,
WithHelp,
LoadingContent,
Expand Down Expand Up @@ -249,6 +250,78 @@ test.describe("Testing Dialog component properties", () => {
).toBeFocused();
});

test("when Dialog is opened and then closed, the call to action element should be focused", async ({
mount,
page,
}) => {
await mount(<DefaultStory />);

const button = page.getByRole("button").filter({ hasText: "Open Dialog" });
const dialog = page.getByRole("dialog");
await expect(button).not.toBeFocused();
await expect(dialog).not.toBeVisible();

await button.click();
await expect(dialog).toBeVisible();
const closeButton = page.getByLabel("Close");
await closeButton.click();
await expect(button).toBeFocused();
await expect(dialog).not.toBeVisible();
});

test("when Dialog is open on render, then closed, opened and then closed again, the call to action element should be focused", async ({
mount,
page,
}) => {
await mount(<DefaultStory open />);

const dialog = page.getByRole("dialog");
await expect(dialog).toBeVisible();
const closeButton = page.getByLabel("Close");
await closeButton.click();

const button = page.getByRole("button").filter({ hasText: "Open Dialog" });
await expect(button).not.toBeFocused();
await expect(dialog).not.toBeVisible();

await button.click();
await expect(dialog).toBeVisible();
await closeButton.click();
await expect(button).toBeFocused();
});

test("when nested Dialog's are opened/closed their respective call to action elements should be focused correctly", async ({
mount,
page,
}) => {
await mount(<DefaultNestedStory />);

const firstButton = page
.getByRole("button")
.filter({ hasText: "Open First Dialog" });
const firstDialog = page.getByRole("dialog").first();
await expect(firstButton).not.toBeFocused();
await expect(firstDialog).not.toBeVisible();

await firstButton.click();
await expect(firstDialog).toBeVisible();
const secondButton = page
.getByRole("button")
.filter({ hasText: "Open Nested Dialog" });
await expect(secondButton).not.toBeFocused();
await secondButton.click();
const secondDialog = page.getByRole("dialog").last();
await expect(secondDialog).toBeVisible();

const secondCloseButton = page.getByLabel("Close").last();
await secondCloseButton.click();
await expect(secondButton).toBeFocused();

const firstCloseButton = page.getByLabel("Close").first();
await firstCloseButton.click();
await expect(firstButton).toBeFocused();
});

test("when disableAutoFocus prop is passed, the first focusable element should not be focused", async ({
mount,
page,
Expand Down
24 changes: 24 additions & 0 deletions src/components/menu/component.test-pw.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -304,6 +304,30 @@ export const MenuComponentFullScreen = (
);
};

export const MenuComponentFullScreenSimple = ({
open = true,
}: {
open?: boolean;
}) => {
const [menuOpen, setMenuOpen] = useState(open);

return (
<Menu menuType="light">
<MenuItem key="menu-item" onClick={() => setMenuOpen(true)}>
Menu
</MenuItem>
<MenuFullscreen
key="menu"
isOpen={menuOpen}
onClose={() => setMenuOpen(false)}
>
<MenuItem href="#">Menu Item One</MenuItem>
<MenuItem href="#">Menu Item Two</MenuItem>
</MenuFullscreen>
</Menu>
);
};

export const MenuComponentFullScreenWithLongSubmenuText = (
props: Partial<MenuFullscreenProps>
) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ export const MenuFullscreen = ({
closeModal,
modalRef: menuRef,
topModalOverride,
focusCallToActionElement: document.activeElement as HTMLElement,
});

return (
Expand Down
41 changes: 41 additions & 0 deletions src/components/menu/menu.pw.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ import {
MenuComponentSearch,
MenuWithChildrenUpdating,
MenuComponentFullScreen,
MenuComponentFullScreenSimple,
MenuFullScreenBackgroundScrollTest,
MenuComponentItems,
MenuFullScreenWithSearchButton,
Expand Down Expand Up @@ -1154,6 +1155,46 @@ test.describe("Prop tests for Menu component", () => {
}
);

test("when a Menu Fullscreen is opened and then closed, the call to action element should be focused", async ({
mount,
page,
}) => {
await mount(<MenuComponentFullScreenSimple open={false} />);

await page.setViewportSize({ width: 1200, height: 800 });
const item = page.getByRole("button").filter({ hasText: "Menu" });
await item.click();
const fullscreen = getComponent(page, "menu-fullscreen");
await waitForAnimationEnd(fullscreen);
const closeButton = page.getByLabel("Close");
await closeButton.click();
await expect(item).toBeFocused();
});

test("when Menu Fullscreen is open on render, then closed, opened and then closed again, the call to action element should be focused", async ({
mount,
page,
}) => {
await mount(<MenuComponentFullScreenSimple />);

await page.setViewportSize({ width: 1200, height: 800 });
const fullscreen = getComponent(page, "menu-fullscreen");
await waitForAnimationEnd(fullscreen);
await expect(fullscreen).toBeVisible();
const closeButton = page.getByLabel("Close");
await closeButton.click();

const item = page.getByRole("button").filter({ hasText: "Menu" });
await expect(item).not.toBeFocused();
await expect(fullscreen).not.toBeVisible();

await item.click();
await waitForAnimationEnd(fullscreen);
await expect(fullscreen).toBeVisible();
await closeButton.click();
await expect(item).toBeFocused();
});

// TODO: Skipped due to flaky focus behaviour. To review in FE-6428
test.skip(`should verify that inner Menu without link is NOT available with tabbing in Fullscreen Menu`, async ({
mount,
Expand Down
1 change: 1 addition & 0 deletions src/components/modal/modal.component.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ const Modal = ({
modalRef: ref,
setTriggerRefocusFlag,
topModalOverride,
focusCallToActionElement: document.activeElement as HTMLElement,
});

let background;
Expand Down
37 changes: 34 additions & 3 deletions src/components/sidebar/components.test-pw.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,15 @@ import Toast from "../toast";
import Textbox from "../textbox";
import Dialog from "../dialog";

export const Default = (args: Partial<SidebarProps>) => {
const [isOpen, setIsOpen] = useState(true);
export const Default = ({ open = true }: { open?: boolean }) => {
const [isOpen, setIsOpen] = useState(open);
const onCancel = () => {
setIsOpen(false);
};
return (
<>
<Button onClick={() => setIsOpen(true)}>Open sidebar</Button>
<Sidebar {...args} aria-label="sidebar" open={isOpen} onCancel={onCancel}>
<Sidebar aria-label="sidebar" open={isOpen} onCancel={onCancel}>
<Box mb={2}>
<Button buttonType="primary">Test</Button>
<Button buttonType="secondary" ml={2}>
Expand All @@ -29,6 +29,37 @@ export const Default = (args: Partial<SidebarProps>) => {
);
};

export const DefaultNested = () => {
const [isFirstSidebarOpen, setIsFirstSidebarOpen] = useState(false);
const [isNestedSidebarOpen, setIsNestedSidebarOpen] = useState(false);
return (
<>
<Button onClick={() => setIsFirstSidebarOpen(true)}>
Open First Sidebar
</Button>
<Sidebar
open={isFirstSidebarOpen}
onCancel={() => setIsFirstSidebarOpen(false)}
>
<Button onClick={() => setIsNestedSidebarOpen(true)}>
Open Nested Sidebar
</Button>
<Sidebar
open={isNestedSidebarOpen}
onCancel={() => setIsNestedSidebarOpen(false)}
>
<Box mb={2}>
<Button buttonType="primary">Test</Button>
<Button buttonType="secondary" ml={2}>
Last
</Button>
</Box>
</Sidebar>
</Sidebar>
</>
);
};

export const SidebarComponent = (props: Partial<SidebarProps>) => {
return (
<>
Expand Down
Loading
Loading