Repository: mui/material-ui (TypeScript)
Date: 2018-12-04 12:07:06+00:00
Task type: Bug Fix

Issue:
[Dialog] Setting paper classname removes other classes
When providing custom `PaperProps` with a `className` to a `Modal`, the other classes are overridden, causing the modal to be displayed incorrectly.

- [x] This is not a v0.x issue. <!-- (v0.x is no longer maintained) -->
- [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.

## Expected Behavior 🤔
In the following example, I would expect the modal to be full screen.

## Current Behavior 😯
It is not full screen.

## Steps to Reproduce 🕹
See this snippet:
https://codesandbox.io/s/vj39w8j20l

When you comment out line 53 of `/pages/index.tsx`, you'll see the difference.

## Your Environment 🌎
<!---
    Include as many relevant details about the environment with which you experienced the bug.
    If you encounter issues with typescript please include version and tsconfig.
-->

| Tech         | Version |
|--------------|---------|
| Material-UI  | v3.6.1  |
| React        |     16.6.3    |
| TypeScript   |    3.1.6     |




Issue discussion:

Solution patch:
diff --git a/packages/material-ui/src/Dialog/Dialog.js b/packages/material-ui/src/Dialog/Dialog.js
--- a/packages/material-ui/src/Dialog/Dialog.js
+++ b/packages/material-ui/src/Dialog/Dialog.js
@@ -10,6 +10,7 @@ import { capitalize } from '../utils/helpers';
 import Modal from '../Modal';
 import Fade from '../Fade';
 import { duration } from '../styles/transitions';
+import chainPropTypes from '../utils/chainPropTypes';
 import Paper from '../Paper';
 
 export const styles = theme => ({
@@ -296,8 +297,21 @@ Dialog.propTypes = {
   open: PropTypes.bool.isRequired,
   /**
    * Properties applied to the [`Paper`](/api/paper/) element.
+   * If you want to add a class to the `Paper` component use
+   * `classes.paper` in the `Dialog` props instead.
    */
-  PaperProps: PropTypes.object,
+  PaperProps: chainPropTypes(PropTypes.object, props => {
+    const { PaperProps = {} } = props;
+    if ('className' in PaperProps) {
+      return new Error(
+        '`className` overrides all `Dialog` specific styles in `Paper`. If you wanted to add ' +
+          'styles to the `Paper` component use `classes.paper` in the `Dialog` props ' +
+          `instead.${process.env.NODE_ENV === 'test' ? Date.now() : ''}`,
+      );
+    }
+
+    return null;
+  }),
   /**
    * Determine the container for scrolling the dialog.
    */
diff --git a/pages/api/dialog.md b/pages/api/dialog.md
--- a/pages/api/dialog.md
+++ b/pages/api/dialog.md
@@ -35,7 +35,7 @@ Dialogs are overlaid modal paper based components with a backdrop.
 | <span class="prop-name">onExited</span> | <span class="prop-type">func</span> |   | Callback fired when the dialog has exited. |
 | <span class="prop-name">onExiting</span> | <span class="prop-type">func</span> |   | Callback fired when the dialog is exiting. |
 | <span class="prop-name required">open *</span> | <span class="prop-type">bool</span> |   | If `true`, the Dialog is open. |
-| <span class="prop-name">PaperProps</span> | <span class="prop-type">object</span> |   | Properties applied to the [`Paper`](/api/paper/) element. |
+| <span class="prop-name">PaperProps</span> | <span class="prop-type">object</span> |   | Properties applied to the [`Paper`](/api/paper/) element. If you want to add a class to the `Paper` component use `classes.paper` in the `Dialog` props instead. |
 | <span class="prop-name">scroll</span> | <span class="prop-type">enum:&nbsp;'body'&nbsp;&#124;<br>&nbsp;'paper'<br></span> | <span class="prop-default">'paper'</span> | Determine the container for scrolling the dialog. |
 | <span class="prop-name">TransitionComponent</span> | <span class="prop-type">union:&nbsp;string&nbsp;&#124;<br>&nbsp;func&nbsp;&#124;<br>&nbsp;object<br></span> | <span class="prop-default">Fade</span> | Transition component. |
 | <span class="prop-name">transitionDuration</span> | <span class="prop-type">union:&nbsp;number&nbsp;&#124;<br>&nbsp;{ enter?: number, exit?: number }<br></span> | <span class="prop-default">{ enter: duration.enteringScreen, exit: duration.leavingScreen }</span> | The duration for the transition, in milliseconds. You may specify a single timeout for all transitions, or individually with an object. |


Test patch:
diff --git a/packages/material-ui/src/Dialog/Dialog.test.js b/packages/material-ui/src/Dialog/Dialog.test.js
--- a/packages/material-ui/src/Dialog/Dialog.test.js
+++ b/packages/material-ui/src/Dialog/Dialog.test.js
@@ -1,13 +1,15 @@
 import React from 'react';
 import { assert } from 'chai';
 import { spy } from 'sinon';
-import { createShallow, getClasses } from '@material-ui/core/test-utils';
+import consoleErrorMock from 'test/utils/consoleErrorMock';
+import { createMount, createShallow, getClasses } from '@material-ui/core/test-utils';
 import Paper from '../Paper';
 import Fade from '../Fade';
 import Modal from '../Modal';
 import Dialog from './Dialog';
 
 describe('<Dialog />', () => {
+  let mount;
   let shallow;
   let classes;
   const defaultProps = {
@@ -15,10 +17,15 @@ describe('<Dialog />', () => {
   };
 
   before(() => {
+    mount = createMount();
     shallow = createShallow({ dive: true });
     classes = getClasses(<Dialog {...defaultProps}>foo</Dialog>);
   });
 
+  after(() => {
+    mount.cleanUp();
+  });
+
   it('should render a Modal', () => {
     const wrapper = shallow(<Dialog {...defaultProps}>foo</Dialog>);
     assert.strictEqual(wrapper.type(), Modal);
@@ -234,4 +241,32 @@ describe('<Dialog />', () => {
       assert.strictEqual(wrapper.find(Paper).hasClass(classes.paperFullScreen), false);
     });
   });
+
+  describe('prop: PaperProps.className', () => {
+    before(() => {
+      consoleErrorMock.spy();
+    });
+
+    after(() => {
+      consoleErrorMock.reset();
+    });
+
+    it('warns on className usage', () => {
+      const wrapper = mount(
+        <Dialog open PaperProps={{ className: 'custom-paper-class' }}>
+          foo
+        </Dialog>,
+      );
+      const paperWrapper = wrapper.find('div.custom-paper-class');
+
+      assert.strictEqual(paperWrapper.exists(), true);
+      assert.strictEqual(paperWrapper.hasClass(classes.paper), false);
+      assert.strictEqual(consoleErrorMock.callCount(), 1);
+      assert.include(
+        consoleErrorMock.args()[0][0],
+        '`className` overrides all `Dialog` specific styles in `Paper`. If you wanted to add ' +
+          'styles to the `Paper` component use `classes.paper` in the `Dialog` props instead.',
+      );
+    });
+  });
 });