Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Can't pass Material-UI muiTheme in context #778

Closed
ghost opened this issue Jan 19, 2017 · 14 comments
Closed

Can't pass Material-UI muiTheme in context #778

ghost opened this issue Jan 19, 2017 · 14 comments

Comments

@ghost
Copy link

ghost commented Jan 19, 2017

My input locker component adds a FloatingActionButton on the right side of it's child () disabling or enabling it. It don't paste the code here for the sake of brevity.
The component works perfectly in the browser.
I tried this:

const muiTheme = getMuiTheme();

const TestComponent = (props) => (
    <InputLocker {...props}>
        <TextField/>
    </InputLocker>
);

describe('InputLocker structural assets', () => {

    it('should be ok', () => {
        const props = {
            ongoingApi: false
        };
        const wrapper = shallow(<TestComponent {...props}/>,{
            context: {muiTheme},
            childContextTypes: {muiTheme: React.PropTypes.object}
        } ).find(InputLocker).shallow();
        assert.equal(wrapper.find('FloatingActionButton')["nodes"].length, 1);
        assert.equal(wrapper.find('LockClose')["nodes"].length, 1);
    });
 });

I dropped a console.log(context) in the InputLocker's constructor. It prints:

{muiTheme: undefined}

I wrapped everithing in a a . Same result.

I'm not a "pro" so I might miss something but after 3 days of googling and banging my head against the wall, I decided to open another issue here.

@ljharb
Copy link
Member

ljharb commented Jan 20, 2017

Context won't be passed unless a component has contextTypes defined, and childContextTypes applies to class-based components or createClass components that have a getChildContext method.

@ghost
Copy link
Author

ghost commented Jan 20, 2017

Thanks a lot and sorry that this definitely isn't an issue. It might be helpful though to others, while this doesn't look to be very well documented.
Pleas close the issue and thanks again.

@ljharb ljharb closed this as completed Jan 20, 2017
@ljharb
Copy link
Member

ljharb commented Jan 20, 2017

(note that this isn't specific to enzyme, this is how react itself works)

@aweary
Copy link
Collaborator

aweary commented Jan 20, 2017

@vladblindu here's the React doc page on context: https://facebook.github.io/react/docs/context.html

@ghost
Copy link
Author

ghost commented Jan 20, 2017

I answered before even checking, because I was sure I missed something, but now I realise has contextTypes defined. In fact it looks like this:

import React from "react";
import {AutoComplete, TextField, FloatingActionButton, DropDownMenu, Checkbox} from "material-ui";
import LockClose from "../../../lib/svg-icons/lock-close";
import LockOpen from "../../../lib/svg-icons/lock-open";
import * as styles from "./styles.less";
import _ from "../../../helpers/mini-underscore";

class InputLocker extends React.Component {
    constructor(props) {
        super(props);
        this._childProps = _.omit(this.props, "autolock", "ongoingApi", "children");
        this._lock = !(props["autolock"] === false);
        this["state"] = {
            locked: this._lock
        };
        this._lock = !(props["autolock"] === false);
        this.getInputRef = this.getInputRef.bind(this);
        this.autoLock = this.autoLock.bind(this);
    }

    componentWillMount() {
        this._childProps.ref = this.getInputRef;
        switch (this.props["children"]["type"]) {
            case AutoComplete:
            case TextField:
                this._childProps.onBlur = this.autoLock;
                break;
            case DropDownMenu:
                this._childProps.onClose = this.autoLock;
                break;
            case Checkbox:
                this._childProps.onCheck = this.autoLock;
                break;
            default:
                console.warn("Warning: InputLock expects an input type element as a child.");
        }
    }

    componentDidUpdate() {
        if (!this.state.locked && this.inputRef.focus)
            this.inputRef.focus();
    }

    autoLock() {
        if (this._lock)
            this.setState({locked: true})
    }

    getInputRef(input) {
        this.inputRef = input;
    }

    renderChildInput() {
        this._childProps.disabled = this.state.locked || this.props["ongoingApi"];
        return React.cloneElement(this.props.children, this._childProps)
    }

    render() {
        return (
            <div className={styles["inputLockerContainer"]}>
                <div className={styles["inputLocker"]}>
                    {this.renderChildInput()}
                </div>
                <div className={styles["inputLocker"]}>
                    <FloatingActionButton
                        disabled={this.props["ongoingApi"]}
                        mini={true}
                        onClick={() => {
                            this.setState({locked: !this.state.locked});
                        }}
                    >
                        {this.state.locked ? <LockClose /> : <LockOpen />}
                    </FloatingActionButton>
                </div>
            </div>)
    }
}

InputLocker.contextTypes = {
    muiTheme: React.PropTypes.object
};

InputLocker.propsType = {
    children: React.PropTypes.element.isRequired,
    autolock: React.PropTypes.bool,
    ongoingApi: React.PropTypes.bool
};

export default InputLocker;

So, it has to be something else.
I also added this:

TestComponent.contextTypes = {
    muiTheme: React.PropTypes.object
};

Still no context.

@ljharb
Copy link
Member

ljharb commented Jan 20, 2017

Try passing the context in .shallow() also.

@ghost
Copy link
Author

ghost commented Jan 20, 2017

Now I can see the context in <InputLocker />'s constructor, but it won't pass through to TextField.

@ljharb
Copy link
Member

ljharb commented Jan 20, 2017

@vladblindu Does TextField also define contextTypes?

@ghost
Copy link
Author

ghost commented Jan 20, 2017

TextField is a material-ui component and is defining contextTypes with muiTheme as required.
This is what I get in the console:

Warning: Failed context type: The context `muiTheme` is marked as required in `TextField`, but its value is `undefined`.

@ljharb
Copy link
Member

ljharb commented Jan 20, 2017

OK, let's step back. If you're getting React errors in your browser console, it's an issue with your own code, not with enzyme.

Do you have an issue with enzyme?

@ghost
Copy link
Author

ghost commented Jan 20, 2017

It looks like I have. I run the mocha test from WebStorm.(so it's WebStomr's console) Somehow it looks like enzyme's shallow render function does'n pass the context down the chain.
When rendered in the browser the component works fine (with a context provider on top).
I obviously tried to add the same context provider in the testing component and the context didn't pass through.

@ljharb
Copy link
Member

ljharb commented Jan 20, 2017

Yes, that's right, shallow does not pass context down the chain by design. (related: #664, #770).

@ghost
Copy link
Author

ghost commented Jan 20, 2017

@ljharb
Copy link
Member

ljharb commented Jan 20, 2017

shallow works for only one level, that's the point of it. Certainly that comment is misleading.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants