Skip to content

Commit

Permalink
Merge pull request #6385 from Sage/FE-6072_esc-that-popover-container
Browse files Browse the repository at this point in the history
fix(popover-container): ensure that component is closed when Esc key is pressed - FE-6072
  • Loading branch information
DipperTheDan authored Nov 9, 2023
2 parents 17c74f1 + d9062f6 commit b5a6393
Show file tree
Hide file tree
Showing 4 changed files with 248 additions and 16 deletions.
61 changes: 51 additions & 10 deletions cypress/components/popover-container/popover-container.cy.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import {
} from "../../locators/popover-container/index";
import { selectListText, selectText } from "../../locators/select/index";
import CypressMountWithProviders from "../../support/component-helper/cypress-mount";
import { keyCode } from "../../support/helper";
import { keyCode, pressESCKey } from "../../support/helper";
import { CHARACTERS } from "../../support/component-helper/constants";

const testData = [CHARACTERS.DIACRITICS, CHARACTERS.SPECIALCHARACTERS];
Expand Down Expand Up @@ -280,13 +280,42 @@ context("Test for Popover Container component", () => {
}
);

it("should close Popover Container using escape keyboard key", () => {
CypressMountWithProviders(
<PopoverContainer title="Cypress is awesome">Contents</PopoverContainer>
);

popoverSettingsIcon().click();
pressESCKey();
popoverContainerContent().should("not.exist");
});

it("should not close Popover Container when an option is selected from Select component inside", () => {
CypressMountWithProviders(<PopoverContainerWithSelect />);
popoverSettingsIcon().click();
selectText().click();
selectListText("green").click();
popoverContainerContent().should("be.visible");
});

it("should not close Popover Container when the escape key is pressed and the Select List is open", () => {
CypressMountWithProviders(<PopoverContainerWithSelect />);
popoverSettingsIcon().click();
selectText().click();
selectListText("red").trigger("keydown", keyCode("downarrow"));
pressESCKey();
selectListText("red").should("not.be.visible");
popoverContainerContent().should("be.visible");
});

it("should close Popover Container when the escape key is pressed with focus on the Select component", () => {
CypressMountWithProviders(<PopoverContainerWithSelect />);
popoverSettingsIcon().click();
selectText().click();
pressESCKey();
pressESCKey();
popoverContainerContent().should("not.exist");
});
});

describe("check events for Popover Container component", () => {
Expand Down Expand Up @@ -338,6 +367,18 @@ context("Test for Popover Container component", () => {
}
);

it("should call onClose callback when the escape is pressed", () => {
const callback: PopoverContainerProps["onClose"] = cy
.stub()
.as("onClose");
CypressMountWithProviders(
<PopoverContainerComponent onClose={callback} open />
);

pressESCKey();
cy.get("@onClose").should("have.been.calledOnce");
});

it("should call onClose callback when a click event is triggered outside the container", () => {
const callback: PopoverContainerProps["onClose"] = cy
.stub()
Expand All @@ -364,63 +405,63 @@ context("Test for Popover Container component", () => {
});

describe("Accessibility tests for Popover Container component", () => {
it("should pass accessibilty tests for Popover Container Default story", () => {
it("should pass accessibility tests for Popover Container Default story", () => {
CypressMountWithProviders(<stories.Default />);

popoverSettingsIcon().click();
cy.checkAccessibility();
});

it("should pass accessibilty tests for Popover Container Title story", () => {
it("should pass accessibility tests for Popover Container Title story", () => {
CypressMountWithProviders(<stories.Title />);

popoverSettingsIcon().click();
cy.checkAccessibility();
});

it("should pass accessibilty tests for Popover Container Position story", () => {
it("should pass accessibility tests for Popover Container Position story", () => {
CypressMountWithProviders(<stories.Position />);

popoverSettingsIcon().click();
cy.checkAccessibility();
});

it("should pass accessibilty tests for Popover Container CoverButton story", () => {
it("should pass accessibility tests for Popover Container CoverButton story", () => {
CypressMountWithProviders(<stories.CoverButton />);

popoverSettingsIcon().click();
cy.checkAccessibility();
});

it("should pass accessibilty tests for Popover Container RenderProps story", () => {
it("should pass accessibility tests for Popover Container RenderProps story", () => {
CypressMountWithProviders(<stories.RenderProps />);

popoverSettingsIcon().click();
cy.checkAccessibility();
});

it("should pass accessibilty tests for Popover Container Controlled story", () => {
it("should pass accessibility tests for Popover Container Controlled story", () => {
CypressMountWithProviders(<stories.Controlled />);

popoverSettingsIcon().click();
cy.checkAccessibility();
});

it("should pass accessibilty tests for Popover Container Complex story", () => {
it("should pass accessibility tests for Popover Container Complex story", () => {
CypressMountWithProviders(<stories.Complex />);

popoverSettingsIcon().click();
cy.checkAccessibility();
});

it("should pass accessibilty tests for Popover Container Filter story", () => {
it("should pass accessibility tests for Popover Container Filter story", () => {
CypressMountWithProviders(<stories.Filter />);

popoverSettingsIcon().click();
cy.checkAccessibility();
});

it("should pass accessibilty tests for Popover Container Filter story with filter button clicked", () => {
it("should pass accessibility tests for Popover Container Filter story with filter button clicked", () => {
CypressMountWithProviders(<stories.Filter />);

popoverSettingsIcon().click();
Expand Down
39 changes: 35 additions & 4 deletions src/components/popover-container/popover-container.component.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useEffect, useRef, useState } from "react";
import React, { useCallback, useEffect, useRef, useState } from "react";
import { PaddingProps } from "styled-system";
import { Transition, TransitionStatus } from "react-transition-group";
import { flip, offset } from "@floating-ui/dom";
Expand All @@ -16,6 +16,7 @@ import Popover from "../../__internal__/popover";
import createGuid from "../../__internal__/utils/helpers/guid";
import { filterStyledSystemPaddingProps } from "../../style/utils";
import useClickAwayListener from "../../hooks/__internal__/useClickAwayListener";
import Events from "../../__internal__/utils/helpers/events";

export interface RenderOpenProps {
tabIndex: number;
Expand Down Expand Up @@ -168,6 +169,38 @@ export const PopoverContainer = ({
setTimeout(() => closeButtonRef.current?.focus(), 0);
}, [isOpen]);

const closePopover = useCallback(
(ev: React.MouseEvent<HTMLElement> | React.KeyboardEvent<HTMLElement>) => {
if (!isControlled) setIsOpenInternal(!isOpen);
if (onClose) onClose(ev);
if (isOpen && openButtonRef.current) openButtonRef.current.focus();
},
[isControlled, isOpen, onClose]
);

const handleEscKey = useCallback(
(ev) => {
const eventIsFromSelectInput = Events.composedPath(ev).find((element) => {
return (
element instanceof HTMLElement &&
element.getAttribute("data-element") === "input" &&
element.getAttribute("aria-expanded") === "true"
);
});

if (!eventIsFromSelectInput && Events.isEscKey(ev)) closePopover(ev);
},
[closePopover]
);

useEffect(() => {
document.addEventListener("keydown", handleEscKey);

return () => {
document.removeEventListener("keydown", handleEscKey);
};
}, [handleEscKey]);

const handleOpenButtonClick = (
e: React.MouseEvent<HTMLElement> | React.KeyboardEvent<HTMLElement>
) => {
Expand All @@ -182,9 +215,7 @@ export const PopoverContainer = ({
const handleCloseButtonClick = (
e: React.MouseEvent<HTMLElement> | React.KeyboardEvent<HTMLElement>
) => {
if (!isControlled) setIsOpenInternal(!isOpen);
if (onClose) onClose(e);
if (isOpen && openButtonRef.current) openButtonRef.current.focus();
closePopover(e);
};

const renderOpenComponentProps = {
Expand Down
143 changes: 143 additions & 0 deletions src/components/popover-container/popover-container.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import {
} from "../../__spec_helper__/test-utils";
import Icon from "../icon";
import guid from "../../__internal__/utils/helpers/guid";
import { Select, Option } from "../select";

jest.mock("../../__internal__/utils/helpers/guid");
(guid as jest.MockedFunction<typeof guid>).mockImplementation(() => "guid-123");
Expand Down Expand Up @@ -314,6 +315,28 @@ describe("PopoverContainer", () => {
expect(wrapper.find(PopoverContainerContentStyle).exists()).toBe(true);
});

it("should close popover if escape key is pressed", () => {
wrapper = render();

act(() => {
wrapper.find(PopoverContainerOpenIcon).props().onClick();
});

wrapper.update();
expect(wrapper.find(PopoverContainerOpenIcon).props().tabIndex).toBe(-1);
expect(wrapper.find(PopoverContainerContentStyle).exists()).toBe(true);

document.dispatchEvent(
new KeyboardEvent("keydown", {
key: "Escape",
bubbles: true,
})
);

wrapper.update();
expect(wrapper.find(PopoverContainerContentStyle).exists()).toBe(false);
});

describe("and custom component is provided as an opening button", () => {
interface MyOpenButtonProps extends RenderOpenProps {
children: React.ReactNode;
Expand Down Expand Up @@ -785,6 +808,126 @@ describe("open state when click event triggered", () => {
expect(onCloseFn).not.toHaveBeenCalled();
});

it("should close the container and call onClose when controlled and escape key is pressed", () => {
const onCloseFn = jest.fn();
const MockWrapper = () => {
const [open, setOpen] = React.useState(true);

return (
<PopoverContainer
title="PopoverContainerSettings"
open={open}
onClose={(e) => {
setOpen(false);
onCloseFn(e);
}}
/>
);
};
const wrapper = mount(<MockWrapper />);

document.dispatchEvent(
new KeyboardEvent("keydown", {
key: "Escape",
bubbles: true,
})
);
expect(wrapper.update().find(PopoverContainerContentStyle).exists()).toBe(
false
);
expect(onCloseFn).toHaveBeenCalled();
});

it("should close the container when escape key is pressed inside of a Select component", () => {
const onCloseFn = jest.fn();
const MockWrapper = () => {
const [open, setOpen] = React.useState(true);

return (
<>
<PopoverContainer
title="PopoverContainerSettings"
open={open}
onClose={(e) => {
setOpen(false);
onCloseFn(e);
}}
>
<Select name="simple" id="simple" label="color" labelInline>
<Option text="Amber" value="1" />
<Option text="Black" value="2" />
</Select>
</PopoverContainer>
</>
);
};
const wrapper = mount(<MockWrapper />);
expect(wrapper.update().find(PopoverContainer).prop("open")).toBe(true);

const selectInput = document.querySelector(
'[data-element="input"][aria-expanded="false"]'
);

selectInput?.dispatchEvent(
new KeyboardEvent("keydown", {
key: "Escape",
bubbles: true,
})
);

expect(wrapper.update().find(PopoverContainerContentStyle).exists()).toBe(
false
);
expect(onCloseFn).toHaveBeenCalled();
});

it("should not close the container when escape key is pressed inside of the SelectList", () => {
const onCloseFn = jest.fn();
const MockWrapper = () => {
const [open, setOpen] = React.useState(true);

return (
<>
<PopoverContainer
title="PopoverContainerSettings"
open={open}
onClose={(e) => {
setOpen(false);
onCloseFn(e);
}}
>
<Select name="simple" id="simple" label="color" labelInline>
<Option text="Amber" value="1" />
<Option text="Black" value="2" />
</Select>
</PopoverContainer>
</>
);
};
const wrapper = mount(<MockWrapper />);
expect(wrapper.update().find(PopoverContainer).prop("open")).toBe(true);

const selectText = wrapper.find('input[type="text"]').first();

selectText.simulate("click");

const expandedSelectInput = document.querySelector(
'[data-element="input"][aria-expanded="true"]'
);

expandedSelectInput?.dispatchEvent(
new KeyboardEvent("keydown", {
key: "Escape",
bubbles: true,
})
);

expect(wrapper.update().find(PopoverContainerContentStyle).exists()).toBe(
true
);
expect(onCloseFn).not.toHaveBeenCalled();
});

it("should render with the expected border radius styling", () => {
assertStyleMatch(
{
Expand Down
Loading

0 comments on commit b5a6393

Please sign in to comment.