Repository: mui/material-ui (TypeScript)
Date: 2020-05-07 00:10:48+00:00
Task type: Bug Fix

Issue:
Popper remains visible when open is false and keepMounted is set
As title says, `open` prop is set to `false` and `keepMounted` to `true`, this makes popper to be always visible.

- [x] The issue is present in the latest release.
- [x] I have searched the [issues](https://github.com/mui-org/material-ui/issues) of this repository and believe that this is not a duplicate.

## Current Behavior 😯

When `open=false` and `keepMounted=true`, popper remains visible.

## Expected Behavior 🤔

Popper should become hidden via css ex. `display: none;` 

## Steps to Reproduce 🕹

https://codesandbox.io/s/material-demo-ff2ct?file=/demo.js

## Your Environment 🌎

| Tech        | Version |
| ----------- | ------- |
| Material-UI | v4.9.13  |
| React | v16.13.1  |
| popper.js | v1.16.1  |



Issue discussion:

Solution patch:
diff --git a/packages/material-ui/src/Popper/Popper.js b/packages/material-ui/src/Popper/Popper.js
--- a/packages/material-ui/src/Popper/Popper.js
+++ b/packages/material-ui/src/Popper/Popper.js
@@ -215,6 +215,7 @@ const Popper = React.forwardRef(function Popper(props, ref) {
           // Fix Popper.js display issue
           top: 0,
           left: 0,
+          display: !open && keepMounted && !transition ? 'none' : null,
           ...style,
         }}
       >


Test patch:
diff --git a/packages/material-ui/src/Popper/Popper.test.js b/packages/material-ui/src/Popper/Popper.test.js
--- a/packages/material-ui/src/Popper/Popper.test.js
+++ b/packages/material-ui/src/Popper/Popper.test.js
@@ -5,7 +5,7 @@ import PropTypes from 'prop-types';
 import { createMount } from '@material-ui/core/test-utils';
 import { ThemeProvider, createMuiTheme } from '@material-ui/core/styles';
 import describeConformance from '@material-ui/core/test-utils/describeConformance';
-import { createClientRender } from 'test/utils/createClientRender';
+import { createClientRender, fireEvent } from 'test/utils/createClientRender';
 import consoleErrorMock from 'test/utils/consoleErrorMock';
 import PopperJs from 'popper.js';
 import Grow from '../Grow';
@@ -28,10 +28,6 @@ describe('<Popper />', () => {
     });
   });
 
-  after(() => {
-    mount.cleanUp();
-  });
-
   describeConformance(<Popper {...defaultProps} />, () => ({
     classes: {},
     inheritComponent: 'div',
@@ -42,12 +38,13 @@ describe('<Popper />', () => {
       // https://github.com/facebook/react/issues/11565
       'reactTestRenderer',
     ],
+    after: () => mount.cleanUp(),
   }));
 
   describe('prop: placement', () => {
     it('should have top placement', () => {
       const renderSpy = spy();
-      mount(
+      render(
         <ThemeProvider theme={rtlTheme}>
           <Popper {...defaultProps} placement="top">
             {({ placement }) => {
@@ -85,7 +82,7 @@ describe('<Popper />', () => {
     ].forEach((test) => {
       it(`should flip ${test.in} when direction=rtl is used`, () => {
         const renderSpy = spy();
-        mount(
+        render(
           <ThemeProvider theme={rtlTheme}>
             <Popper {...defaultProps} placement={test.in}>
               {({ placement }) => {
@@ -125,20 +122,19 @@ describe('<Popper />', () => {
 
   describe('prop: open', () => {
     it('should open without any issue', () => {
-      const wrapper = mount(<Popper {...defaultProps} open={false} />);
-      expect(wrapper.find('[role="tooltip"]').exists()).to.equal(false);
-      wrapper.setProps({ open: true });
-      wrapper.update();
-      expect(wrapper.find('[role="tooltip"]').exists()).to.equal(true);
-      expect(wrapper.find('[role="tooltip"]').text()).to.equal('Hello World');
+      const { queryByRole, getByRole, setProps } = render(
+        <Popper {...defaultProps} open={false} />,
+      );
+      expect(queryByRole('tooltip')).to.equal(null);
+      setProps({ open: true });
+      expect(getByRole('tooltip')).to.have.text('Hello World');
     });
 
     it('should close without any issue', () => {
-      const wrapper = mount(<Popper {...defaultProps} />);
-      expect(wrapper.find('[role="tooltip"]').exists()).to.equal(true);
-      expect(wrapper.find('[role="tooltip"]').text()).to.equal('Hello World');
-      wrapper.setProps({ open: false });
-      expect(wrapper.find('[role="tooltip"]').length).to.equal(0);
+      const { queryByRole, getByRole, setProps } = render(<Popper {...defaultProps} />);
+      expect(getByRole('tooltip')).to.have.text('Hello World');
+      setProps({ open: false });
+      expect(queryByRole('tooltip')).to.equal(null);
     });
   });
 
@@ -152,11 +148,18 @@ describe('<Popper />', () => {
           done();
         },
       };
-      mount(<Popper {...defaultProps} popperOptions={popperOptions} placement="top" open />);
+      render(<Popper {...defaultProps} popperOptions={popperOptions} placement="top" open />);
     });
   });
 
   describe('prop: keepMounted', () => {
+    it('should keep the children mounted in the DOM', () => {
+      render(<Popper {...defaultProps} keepMounted open={false} />);
+      const tooltip = document.querySelector('[role="tooltip"]');
+      expect(tooltip).to.have.text('Hello World');
+      expect(tooltip.style.display).to.equal('none');
+    });
+
     describe('by default', () => {
       // Test case for https://github.com/mui-org/material-ui/issues/15180
       it('should remove the transition children in the DOM when closed whilst transition status is entering', () => {
@@ -191,31 +194,29 @@ describe('<Popper />', () => {
           }
         }
 
-        const wrapper = mount(<OpenClose />);
-        expect(wrapper.contains(children)).to.equal(false);
-        wrapper.find('button').simulate('click');
-        expect(wrapper.contains(children)).to.equal(false);
+        const { getByRole } = render(<OpenClose />);
+        expect(document.querySelector('p')).to.equal(null);
+        fireEvent.click(getByRole('button'));
+        expect(document.querySelector('p')).to.equal(null);
       });
     });
   });
 
   describe('prop: transition', () => {
     let clock;
-    let looseMount;
+    const looseRender = createClientRender({ strict: false });
 
     before(() => {
       clock = useFakeTimers();
       // StrictModeViolation: uses Grow
-      looseMount = createMount({ strict: false });
     });
 
     after(() => {
       clock.restore();
-      looseMount.cleanUp();
     });
 
     it('should work', () => {
-      const wrapper = looseMount(
+      const { queryByRole, getByRole, setProps } = looseRender(
         <Popper {...defaultProps} transition>
           {({ TransitionProps }) => (
             <Grow {...TransitionProps}>
@@ -224,12 +225,10 @@ describe('<Popper />', () => {
           )}
         </Popper>,
       );
-      expect(wrapper.find('[role="tooltip"]').exists()).to.equal(true);
-      expect(wrapper.find('[role="tooltip"]').text()).to.equal('Hello World');
-      wrapper.setProps({ anchorEl: null, open: false });
+      expect(getByRole('tooltip')).to.have.text('Hello World');
+      setProps({ anchorEl: null, open: false });
       clock.tick(0);
-      wrapper.update();
-      expect(wrapper.find('[role="tooltip"]').exists()).to.equal(false);
+      expect(queryByRole('tooltip')).to.equal(null);
     });
   });
 
@@ -237,9 +236,9 @@ describe('<Popper />', () => {
     it('should return a ref', () => {
       const ref1 = React.createRef();
       const ref2 = React.createRef();
-      const wrapper = mount(<Popper {...defaultProps} popperRef={ref1} />);
+      const { setProps } = render(<Popper {...defaultProps} popperRef={ref1} />);
       expect(ref1.current instanceof PopperJs).to.equal(true);
-      wrapper.setProps({
+      setProps({
         popperRef: ref2,
       });
       expect(ref1.current).to.equal(null);
@@ -250,9 +249,11 @@ describe('<Popper />', () => {
   describe('prop: disablePortal', () => {
     it('should work', () => {
       const popperRef = React.createRef();
-      const wrapper = mount(<Popper {...defaultProps} disablePortal popperRef={popperRef} />);
+      const { getByRole } = render(
+        <Popper {...defaultProps} disablePortal popperRef={popperRef} />,
+      );
       // renders
-      expect(wrapper.find('[role="tooltip"]').exists()).to.equal(true);
+      expect(getByRole('tooltip')).to.not.equal(null);
       // correctly sets modifiers
       expect(popperRef.current.options.modifiers.preventOverflow.boundariesElement).to.equal(
         'scrollParent',
@@ -261,9 +262,9 @@ describe('<Popper />', () => {
 
     it('sets preventOverflow to window when disablePortal is false', () => {
       const popperRef = React.createRef();
-      const wrapper = mount(<Popper {...defaultProps} popperRef={popperRef} />);
+      const { getByRole } = render(<Popper {...defaultProps} popperRef={popperRef} />);
       // renders
-      expect(wrapper.find('[role="tooltip"]').exists()).to.equal(true);
+      expect(getByRole('tooltip')).to.not.equal(null);
       // correctly sets modifiers
       expect(popperRef.current.options.modifiers.preventOverflow.boundariesElement).to.equal(
         'window',