rjsf-team / react-jsonschema-form

A React component for building Web forms from JSON Schema.
https://rjsf-team.github.io/react-jsonschema-form/
Apache License 2.0
14.27k stars 2.18k forks source link

bug in Mocha-run tests after upgrade to 0.41.2 #427

Closed spacebaboon closed 7 years ago

spacebaboon commented 7 years ago

Prerequisites

Description

The following error is thrown in unit tests of a custom component wrapping RJSF after upgrading from 0.41.1 to 0.41.2:

Warning: React.createElement: type should not be null, undefined, boolean, or number. It should be a string (for DOM elements) or a ReactClass (for composite components). Check the render method of 'Form'.

Invariant Violation: Element type is invalid: expected a string (for built-in components) or a class/function (for composite components) but got: undefined. Check the render method of 'Form'.
    at invariant (tp-client/node_modules/react-dom/node_modules/fbjs/lib/invariant.js:38:15)
    at instantiateReactComponent (tp-client/node_modules/react-dom/lib/instantiateReactComponent.js:68:134)
    at instantiateChild (tp-client/node_modules/react-dom/lib/ReactChildReconciler.js:44:28)
    at tp-client/node_modules/react-dom/lib/ReactChildReconciler.js:71:16
    at traverseAllChildrenImpl (tp-client/node_modules/react-dom/lib/traverseAllChildren.js:77:5)
    at traverseAllChildrenImpl (tp-client/node_modules/react-dom/lib/traverseAllChildren.js:93:23)
    at traverseAllChildren (tp-client/node_modules/react-dom/lib/traverseAllChildren.js:172:10)
    at Object.ReactChildReconciler.instantiateChildren (tp-client/node_modules/react-dom/lib/ReactChildReconciler.js:70:7)
    at ReactDOMComponent.ReactMultiChild.Mixin._reconcilerInstantiateChildren (tp-client/node_modules/react-dom/lib/ReactMultiChild.js:187:41)
    at ReactDOMComponent.ReactMultiChild.Mixin.mountChildren (tp-client/node_modules/react-dom/lib/ReactMultiChild.js:226:27)
    at ReactDOMComponent.Mixin._createInitialChildren (tp-client/node_modules/react-dom/lib/ReactDOMComponent.js:691:32)
    at ReactDOMComponent.Mixin.mountComponent (tp-client/node_modules/react-dom/lib/ReactDOMComponent.js:516:12)
    at Object.ReactReconciler.mountComponent (tp-client/node_modules/react-dom/lib/ReactReconciler.js:46:35)
    at null.ReactCompositeComponent.performInitialMount (tp-client/node_modules/react-dom/lib/ReactCompositeComponent.js:371:34)
    at null.ReactCompositeComponent.mountComponent (tp-client/node_modules/react-dom/lib/ReactCompositeComponent.js:258:21)
    at Object.ReactReconciler.mountComponent (tp-client/node_modules/react-dom/lib/ReactReconciler.js:46:35)
    at null.ReactCompositeComponent.performInitialMount (tp-client/node_modules/react-dom/lib/ReactCompositeComponent.js:371:34)
    at null.ReactCompositeComponent.mountComponent (tp-client/node_modules/react-dom/lib/ReactCompositeComponent.js:258:21)
    at Object.ReactReconciler.mountComponent (tp-client/node_modules/react-dom/lib/ReactReconciler.js:46:35)
    at null.ReactCompositeComponent.performInitialMount (tp-client/node_modules/react-dom/lib/ReactCompositeComponent.js:371:34)
    at null.ReactCompositeComponent.mountComponent (tp-client/node_modules/react-dom/lib/ReactCompositeComponent.js:258:21)
    at Object.ReactReconciler.mountComponent (tp-client/node_modules/react-dom/lib/ReactReconciler.js:46:35)
    at null.ReactCompositeComponent.performInitialMount (tp-client/node_modules/react-dom/lib/ReactCompositeComponent.js:371:34)
    at null.ReactCompositeComponent.mountComponent (tp-client/node_modules/react-dom/lib/ReactCompositeComponent.js:258:21)
    at Object.ReactReconciler.mountComponent (tp-client/node_modules/react-dom/lib/ReactReconciler.js:46:35)
    at mountComponentIntoNode (tp-client/node_modules/react-dom/lib/ReactMount.js:104:32)
    at ReactReconcileTransaction.TransactionImpl.perform (tp-client/node_modules/react-dom/lib/Transaction.js:140:20)
    at batchedMountComponentIntoNode (tp-client/node_modules/react-dom/lib/ReactMount.js:126:15)
    at ReactDefaultBatchingStrategyTransaction.TransactionImpl.perform (tp-client/node_modules/react-dom/lib/Transaction.js:140:20)
    at Object.ReactDefaultBatchingStrategy.batchedUpdates (tp-client/node_modules/react-dom/lib/ReactDefaultBatchingStrategy.js:62:26)
    at Object.batchedUpdates (tp-client/node_modules/react-dom/lib/ReactUpdates.js:97:27)
    at Object.ReactMount._renderNewRootComponent (tp-client/node_modules/react-dom/lib/ReactMount.js:320:18)
    at Object.ReactMount._renderSubtreeIntoContainer (tp-client/node_modules/react-dom/lib/ReactMount.js:401:32)
    at Object.ReactMount.render (tp-client/node_modules/react-dom/lib/ReactMount.js:422:23)
    at Object.ReactTestUtils.renderIntoDocument (tp-client/node_modules/react-dom/lib/ReactTestUtils.js:79:21)
    at renderWithOptions (tp-client/node_modules/enzyme/build/react-compat.js:187:26)
    at new ReactWrapper (tp-client/node_modules/enzyme/build/ReactWrapper.js:94:59)
    at mount (tp-client/node_modules/enzyme/build/mount.js:19:10)
    at Context.<anonymous> (tp-client/test/components/dynamicJsonComponents/OneOfFieldRadioTests.js:26:23)

These errors go away after downgrading again.

Steps to Reproduce

I have a custom form component which encapsulates some logic and presentation in order to present the form as we need it.

import React from 'react';
import Form from 'react-jsonschema-form';
import {TpFieldTemplate} from './TpFieldTemplate';
import {NotificationPanel} from './NotificationPanel';
import * as widgetUtils from '../../lib/WidgetUtils';
import * as schemaUtils from '../../lib/SchemaUtils';
import OneOfField from '../dynamicJsonComponents/OneOfField';
import TextWidget from '../../../node_modules/react-jsonschema-form/lib/components/widgets/TextWidget';
import SelectWidget from '../../../node_modules/react-jsonschema-form/lib/components/widgets/SelectWidget';

const NovalidateTextWidget = (props) => (
    <TextWidget {...widgetUtils.removeRequiredFromProps(props)}/>
);

const NovalidateSelectWidget = (props) => (
    <SelectWidget {...widgetUtils.removeRequiredFromProps(props)}/>
);

// Render empty title field, to avoid displaying the title twice
const EmptyTitleField = () => {
    return null;
};

// chain all schema transformers here
const applyTransformations = (schema) => {
    return schemaUtils.transformOneOf(schema);
};

const widgets = {
    TextWidget: NovalidateTextWidget,
    SelectWidget: NovalidateSelectWidget
};

const fields = {
    oneOf: OneOfField,
    TitleField: EmptyTitleField
};

/*
 * Custom Form component encapsulating all TP behaviour.
 * Just pass schema, uiSchema and formData as props.
 */
export default class TpForm extends React.Component {

    constructor(props) {
        super(props);
        this.state = {};
        this._onError = this._onError.bind(this);
        this._onSubmit = this._onSubmit.bind(this);
        this._onChange = this._onChange.bind(this);
    }

    // if called, then form has passed validation, so clear notifications panel
    _onSubmit() {
        this.setState({formErrors: []})
    }

    _onError(errors) {
        this.setState({formErrors: errors});
    }

    _onChange(event) {
        this.setState({formData: event.formData});
    }

    render() {
        const transformedSchema = applyTransformations(this.props.schema);
        return (
            <Form schema={transformedSchema}
                  uiSchema={this.props.uiSchema}
                  formData={this.props.formData}
                  widgets={widgets}
                  fields={fields}
                  onSubmit={this._onSubmit}
                  onError={this._onError}
                  onChange={this._onChange}
                  FieldTemplate={TpFieldTemplate}
                  showErrorList={false}>
                <button className="submit">Submit</button>
                <NotificationPanel errors={this.state.formErrors}/>
            </Form>
        );
    }
}

my tests use Enzyme's mount, and this set up file for js-dom

var jsdom = require("jsdom");
if (!global.hasOwnProperty("window")) {
    global.document = jsdom.jsdom("<!doctype html><html><body></body></html>");
    global.window = document.defaultView;
    global.navigator = global.window.navigator;

}

example test

import React from 'react';
import {mount} from 'enzyme';
import expect from 'expect';
import TpForm from '../../../src/components/form/TpForm';

describe('<TpForm>', () => {

    describe('Form rendering tests', () => {

        it('should not show the error notification panel when form first rendered', () => {
            const schema = {
                type: "object",
                required: ["firstName"],
                properties: {
                    firstName: {
                        type: "string"
                    }
                }
            };
            const wrapper = mount(<TpForm schema={schema}/>);
            expect(wrapper.find('.notification-panel').length).toEqual(0);
        });
[...]

Version

These are all my dependencies

├── axios@0.13.1
├── babel-cli@6.18.0
├── babel-core@6.18.2
├── babel-istanbul@0.11.0
├── babel-loader@6.2.8
├── babel-preset-es2015@6.18.0
├── babel-preset-react@6.16.0
├── babel-preset-stage-0@6.16.0
├── babel-register@6.18.0
├── browserify@13.1.1
├── css-loader@0.26.0
├── deep-freeze-node@1.1.2
├── enzyme@2.6.0
├── es6-promise@4.0.5
├── expect@1.20.2
├── file-loader@0.9.0
├── isomorphic-fetch@2.2.1
├── istanbul@0.4.5
├── jsdom@9.8.3
├── json-loader@0.5.4
├── mocha@3.2.0
├── mocha-sonar-generic-test-coverage-file@0.0.3
├── mocha-sonar-reporter@0.1.6
├── nock@9.0.2
├── react@15.4.1
├── react-addons-test-utils@15.4.1
├── react-dom@15.4.1
├── react-hot-loader@3.0.0-beta.6
├── react-if@2.1.0
├── react-jsonschema-form@0.41.2
├── react-notification-system@0.2.10
├── react-notification-system-redux@1.0.8
├── react-redux@4.4.6
├── react-router@3.0.0
├── redux@3.6.0
├── redux-mock-store@1.2.1
├── redux-thunk@2.1.0
├── requirejs@2.3.2
├── style-loader@0.13.1
├── url-loader@0.5.7
├── watchify@3.7.0
├── webpack@1.13.3
└── webpack-dev-server@1.16.2
goncy commented 7 years ago

Having the same error Invariant Violation: Element type is invalid: expected a string (for built-in components) or a class/function (for composite components) but got: undefined. Check the render method of 'Form'

0.41.0 Works 0.41.1 Doesn't

With exactly the same code

import commonCustomWidgets from 'common/custom-form-widgets';
import commonCustomFields from 'common/custom-form-fields';
import styles from './styles.css';
import React from 'react';
import Form from 'react-jsonschema-form';

const MadForm = props => {
  const {
    showErrorList = false,
    hideSubmit = true,
    children,
    widgets = {},
    fields = {},
    ...other
  } = props;

  return (
    <div className={ styles.container }>
      <Form
        showErrorList={ showErrorList }
        widgets={{ ...commonCustomWidgets, ...widgets }}
        fields={{ ...commonCustomFields, ...fields }}
        { ...other }
      >
        { children }
        { hideSubmit && <button type='submit' hidden>Save</button> }
      </Form>
    </div>
  );
};

Props differs between components so it's not always the same, but never works, switching versions works well. Hope it helps!

n1k0 commented 7 years ago

We have very limited time to investigate this specific error with mocha. Could you please help us investigating and reducing the source of the error to its very minimal bit? Thanks for your understanding.

spacebaboon commented 7 years ago

Sure, although I'm not sure I understand exactly what you need.

If it helps, I've only ever seen this error when I've made a mistake with import statements and used import blah from 'blah' instead of import {blah} from 'blah' so I think it must have come from a change in imports or exports involving Form.js that went into the 0.41.2 release.

this is still broken in v 0.42.0.

n1k0 commented 7 years ago

Sure, although I'm not sure I understand exactly what you need.

A reproducible test case.

If it helps, I've only ever seen this error when I've made a mistake with import statements

Wait, wouldn't that mean that the error is normal and expected?

spacebaboon commented 7 years ago

Is the test in my original bug report not reproducible? Sorry if I'm missing something.

I meant that I've seen this error message when I've made a mistake in the past with imports from my code, so perhaps that's the also source of this bug in RJSF.

spacebaboon commented 7 years ago

Yeah, my code isn't runnable, as I haven't included all my files. Let me see if I can give you a full example...

spacebaboon commented 7 years ago

Well that was a fun half day! For some as yet unknown and utterly baffling reason, the presence of another test file, with which I can find no connection to RJSF causes something to happen which breaks the tests involving RJSF v0.41.2 and above. It will probably be something to do with the test environment needing to be cleaned up.

There was never a problem running our code with v0.41.2 or v0.42.0 in a browser, it was only test failures. I still need to find out why it's doing it, and I'll update this if I find anything related to RJSF, but I'm happy to close the bug.

I don't know if @goncy can also verify this, and if his problem is also test-related. Could you please try deleting other tests and just running the one where you found the error, and seeing if that fixes it?

spacebaboon commented 7 years ago

I've failed to put together a cut-down reproducable test case which will show the error, and I'm not allowed to share all our code, and unfortunately I don't have any more time to spend on this right now.

I have narrowed it down to one class under test, which imports and uses SchemaField for some custom behaviour. Even if the class under test doesn't use the imported SchemaField, the presence of the import alone is enough. This somehow causes another test to fail with the error in the stacktrace I originally posted. This second test tests a class which imports and uses Form for custom behaviour. Both tests will pass if the other is deleted, so I guess the Mocha framework has both imported simultaneously, and this may be causing the problem. The failure also happens when ObjectField or StringField are imported, even if not used. Importing any of the other fields in the directory does not cause the error. There are no other error messages shown during the test run, only the stack trace I posted. This error is thrown during the test set up, before any individual test case has run. I have tried explicitly cleaning the jsdom environment between tests, this has no effect. The error happens in the second from top line in the stack trace, in instantiateReactComponent, as the element.type for Form is undefined, and it needs to be a string or function. The failure only started happening with v0.41.2.

n1k0 commented 7 years ago

Thanks for your investigation around this. Unfortunately, without a reproducible test case I'm gonna have to close the issue. Anyone feel free to reopen if that still happens and we can identify where does that come from precisely.