enzymejs / enzyme

JavaScript Testing utilities for React
https://enzymejs.github.io/enzyme/
MIT License
19.95k stars 2.01k forks source link

Shallow rendering is rednering the html of 3rd party components #1706

Closed StefanoSega closed 6 years ago

StefanoSega commented 6 years ago

Describe the bug When shallow rendering a component that uses the 3rd party components react-placeholder and material-ui-pickers/DateTimePicker the DateTimePicker is fully rendered in its whole final html.

To Reproduce Steps to reproduce the behavior:

  1. Create a component that wrap a DateTimePicker into a ReactPlaceholder; this is my code:
    
    import React, { PureComponent } from 'react';
    import PropTypes from 'prop-types';
    import ReactPlaceholder from 'react-placeholder';
    import DateFnsUtils from 'material-ui-pickers/utils/date-fns-utils';
    import MuiPickersUtilsProvider from 'material-ui-pickers/utils/MuiPickersUtilsProvider';
    import DateTimePicker from 'material-ui-pickers/DateTimePicker';
    import { MuiThemeProvider, createMuiTheme, Icon, InputAdornment } from 'material-ui';
    import { COLORS_CONFIG, DATETIME_CONFIG } from '../../config';

export default class DatetimePicker extends PureComponent { static propTypes = { id: PropTypes.string.isRequired, name: PropTypes.string, title: PropTypes.string.isRequired, placeholder: PropTypes.string, value: PropTypes.oneOfType([ PropTypes.instanceOf(Date), PropTypes.string, ]), onChange: PropTypes.func.isRequired, ready: PropTypes.bool, required: PropTypes.bool, // eslint-disable-next-line react/no-unused-prop-types forceValidationError: PropTypes.string, shouldDisableDate: PropTypes.func, }

static defaultProps = { name: null, placeholder: null, value: '', ready: true, required: false, forceValidationError: null, shouldDisableDate: null, }

constructor(props) { super(props);

this.bindActions();

}

componentDidUpdate() { if (this.props.forceValidationError) { // eslint-disable-next-line no-console console.error(DateTime field ${this.props.id} error: ${this.props.forceValidationError}); } }

shouldDisableDate(date) { if (this.props.shouldDisableDate) { return this.props.shouldDisableDate(date); }

return false;

}

bindActions() { this.shouldDisableDate = this.shouldDisableDate.bind(this); }

render() { const hasValue = !!this.props.value; const name = this.props.name || this.props.id;

const materialTheme = createMuiTheme({
  palette: {
    primary: {
      main: COLORS_CONFIG.green,
    },
    secondary: {
      main: COLORS_CONFIG.orange,
    },
  },
  overrides: {
    MuiFormControl: {
      root: {
        width: '100%',
      },
    },
    MuiInput: {
      input: {
        color: hasValue ? COLORS_CONFIG.black : COLORS_CONFIG.gray,
      },
    },
  },
});

return (
  <ReactPlaceholder
    type="text"
    rows={1}
    ready={this.props.ready}
    showLoadingAnimation
    className="datetime-picker-placeholder"
  >
    <MuiThemeProvider theme={materialTheme}>
      <MuiPickersUtilsProvider utils={DateFnsUtils}>
        <div
          className="datetime-picker"
          data-md-field
        >
          <DateTimePicker
            value={this.props.value || null}
            onChange={this.props.onChange}
            emptyLabel={this.props.placeholder}
            clearable={!this.props.required}
            shouldDisableDate={this.shouldDisableDate}
            format={DATETIME_CONFIG.formats.shortDateTime}
            InputProps={{
              endAdornment: (
                <InputAdornment position="end">
                  <Icon className="datetime-picker-icon">event</Icon>
                </InputAdornment>
              ),
            }}
          />
          <label htmlFor={this.props.id} className="datetime-picker-label">{this.props.title}</label>
        </div>
      </MuiPickersUtilsProvider>
    </MuiThemeProvider>
    <input
      type="hidden"
      id={this.props.id}
      name={name}
      value={this.props.value || ''}
    />
  </ReactPlaceholder>
);

} }

2. Include it in a test rendering it with the `shallow()` method:

import React from 'react'; import { shallow, mount } from 'enzyme'; import DatetimePicker from '../../../../react/components/materialDesign/datetimePicker';

let wrapper; const onChange = jest.fn(); const idPar = 'test';

describe('DatetimePicker', () => { describe('When the field is required and no value is specified', () => { beforeAll(() => { // global.Date = jest.fn(() => mockedDate); wrapper = shallow(<DatetimePicker id={idPar} title="Date" onChange={onChange} required />); });

test('it should set the value to a falsy value', () => {
  console.log(wrapper.html());
  expect(wrapper.find(`#${idPar}`).props().value).toBeFalsy();
});

}); });

3. `wrapper.html()` returns the whole html:

and indeed with the wrapper I cannot target the DateTimePicker component.

**Expected behavior**
It should not render the whole html, but just a DateTimePicker component with the properties like iti s inside my component.

**Desktop (please complete the following information):**
 - OS: MacOS Sierra
 - Browser -
 - Version 
    "enzyme": "^3.3.0",
    "enzyme-adapter-react-16": "^1.1.1",
    "enzyme-to-json": "^3.3.4",
ljharb commented 6 years ago

.html() isn't shallow - it uses cheerio to perform a full render, all the way down.

You might want wrapper.debug()?

StefanoSega commented 6 years ago

Yes!

with wrapper.debug() I can see how it's structured:

nsole.log tests/react/components/materialDesign/datetimePicker.test.jsx:28
      <ReactPlaceholder type="text" rows={1} ready={true} showLoadingAnimation={true} className="datetime-picker-placeholder" delay={0} color="#CD
CDCD">
        <MuiThemeProvider theme={{...}}>
          <MuiPickersUtilsProvider utils={[Function: DateFnsUtils]} locale={[undefined]} moment={[undefined]}>
            <div className="datetime-picker" data-md-field={true}>
              <WithStyles(WithUtils(DateTimePickerWrapper)) value={{...}} onChange={[Function: mockConstructor]} emptyLabel={{...}} clearable={fal
se} shouldDisableDate={[Function: bound shouldDisableDate]} format="DD/MM/YYYY hh:mm A" InputProps={{...}} />
              <label htmlFor="test" className="datetime-picker-label">
                Date
              </label>
            </div>
          </MuiPickersUtilsProvider>
        </MuiThemeProvider>
        <input type="hidden" id="test" name="test" value="" />
      </ReactPlaceholder>

... so the DateTimePicker component is in the end rendered as a WithStyles component.

Thanks, there's no issue so.