Skip to content

Commit

Permalink
Added context support
Browse files Browse the repository at this point in the history
  • Loading branch information
lelandrichardson committed Dec 9, 2015
1 parent 2db7c67 commit a089c9f
Show file tree
Hide file tree
Showing 16 changed files with 353 additions and 48 deletions.
2 changes: 2 additions & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
* [simulate(event[, data])](/docs/api/ShallowWrapper/simulate.md)
* [setState(nextState)](/docs/api/ShallowWrapper/setState.md)
* [setProps(nextProps)](/docs/api/ShallowWrapper/setProps.md)
* [setContext(context)](/docs/api/ShallowWrapper/setContext.md)
* [instance()](/docs/api/ShallowWrapper/instance.md)
* [update()](/docs/api/ShallowWrapper/update.md)
* [debug()](/docs/api/ShallowWrapper/debug.md)
Expand Down Expand Up @@ -69,6 +70,7 @@
* [simulate(event[, data])](/docs/api/ReactWrapper/simulate.md)
* [setState(nextState)](/docs/api/ReactWrapper/setState.md)
* [setProps(nextProps)](/docs/api/ReactWrapper/setProps.md)
* [setContext(context)](/docs/api/ReactWrapper/setContext.md)
* [instance()](/docs/api/ReactWrapper/instance.md)
* [update()](/docs/api/ReactWrapper/update.md)
* [type()](/docs/api/ReactWrapper/type.md)
Expand Down
55 changes: 55 additions & 0 deletions docs/api/ReactWrapper/setContext.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# `.setContext(context) => Self`

A method that sets the context of the root component, and re-renders. Useful for when you are
wanting to test how the component behaves over time with changing contexts.

NOTE: can only be called on a wrapper instance that is also the root instance.


#### Arguments

1. `context` (`Object`): An object containing new props to merge in with the current state



#### Returns

`ReactWrapper`: Returns itself.



#### Example

```jsx
const SimpleComponent = React.createClass({
contextTypes: {
name: React.PropTypes.string,
},
render() {
return <div>{this.context.name}</div>;
},
});
```
```jsx
const context = { name: 'foo' };
const wrapper = mount(<SimpleComponent />, { context });
expect(wrapper.text()).to.equal('foo');
wrapper.setContext({ name: 'bar' });
expect(wrapper.text()).to.equal('bar');
wrapper.setContext({ name: 'baz' });
expect(wrapper.text()).to.equal('baz');
```

#### Common Gotchas

- `.setContext()` can only be used on a wrapper that was initially created with a call to `mount()`
that includes a `context` specified in the options argument.
- The root component you are rendering must have a `contextTypes` static property.


#### Related Methods

- [`.setState(state) => Self`](setState.md)
- [`.setProps(props) => Self`](setProps.md)


1 change: 1 addition & 0 deletions docs/api/ReactWrapper/setProps.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,5 +59,6 @@ expect(spy.calledOnce).to.be.true;
#### Related Methods

- [`.setState(state) => Self`](setState.md)
- [`.setContext(context) => Self`](setContext.md)


1 change: 1 addition & 0 deletions docs/api/ReactWrapper/setState.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,5 +53,6 @@ expect(wrapper.find('.bar')).to.have.length(1);
#### Related Methods

- [`.setProps(props) => Self`](setProps.md)
- [`.setContext(context) => Self`](setContext.md)


55 changes: 55 additions & 0 deletions docs/api/ShallowWrapper/setContext.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# `.setContext(context) => Self`

A method that sets the context of the root component, and re-renders. Useful for when you are
wanting to test how the component behaves over time with changing contexts.

NOTE: can only be called on a wrapper instance that is also the root instance.


#### Arguments

1. `context` (`Object`): An object containing new props to merge in with the current state



#### Returns

`ShallowWrapper`: Returns itself.



#### Example

```jsx
const SimpleComponent = React.createClass({
contextTypes: {
name: React.PropTypes.string,
},
render() {
return <div>{this.context.name}</div>;
},
});
```
```jsx
const context = { name: 'foo' };
const wrapper = shallow(<SimpleComponent />, { context });
expect(wrapper.text()).to.equal('foo');
wrapper.setContext({ name: 'bar' });
expect(wrapper.text()).to.equal('bar');
wrapper.setContext({ name: 'baz' });
expect(wrapper.text()).to.equal('baz');
```

#### Common Gotchas

- `.setContext()` can only be used on a wrapper that was initially created with a call to `mount()`
that includes a `context` specified in the options argument.
- The root component you are rendering must have a `contextTypes` static property.


#### Related Methods

- [`.setState(state) => Self`](setState.md)
- [`.setProps(props) => Self`](setProps.md)


1 change: 1 addition & 0 deletions docs/api/ShallowWrapper/setProps.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,5 +59,6 @@ expect(spy.calledOnce).to.be.true;
#### Related Methods

- [`.setState(state) => Self`](setState.md)
- [`.setContext(context) => Self`](setContext.md)


1 change: 1 addition & 0 deletions docs/api/ShallowWrapper/setState.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,5 +53,6 @@ expect(wrapper.find('.bar')).to.have.length(1);
#### Related Methods

- [`.setProps(props) => Self`](setProps.md)
- [`.setContext(context) => Self`](setContext.md)


15 changes: 15 additions & 0 deletions docs/api/mount.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,18 @@ describe('<Foo />', () => {
});
```

## `mount(node[, options]) => ReactWrapper`

#### Arguments

1. `node` (`ReactElement`): The node to render
2. `options` (`Object` [optional]):
- `options.context`: (`Object` [optional]): Context to be passed into the component

#### Returns

`ReactWrapper`: The wrapper instance around the rendered output.


## ReactWrapper API

Expand Down Expand Up @@ -109,6 +121,9 @@ Manually sets state of the root component.
#### [`.setProps(nextProps) => ReactWrapper`](ReactWrapper/setProps.md)
Manually sets props of the root component.

#### [`.setContext(context) => ReactWrapper`](ReactWrapper/setContext.md)
Manually sets context of the root component.

#### [`.instance() => ReactComponent`](ReactWrapper/instance.md)
Returns the instance of the root component.

Expand Down
15 changes: 15 additions & 0 deletions docs/api/shallow.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,18 @@ describe('<MyComponent />', () => {

```

## `shallow(node[, options]) => ReactWrapper`

#### Arguments

1. `node` (`ReactElement`): The node to render
2. `options` (`Object` [optional]):
- `options.context`: (`Object` [optional]): Context to be passed into the component

#### Returns

`ShallowWrapper`: The wrapper instance around the rendered output.


## ShallowWrapper API

Expand Down Expand Up @@ -121,6 +133,9 @@ Manually sets state of the root component.
#### [`.setProps(nextProps) => ShallowWrapper`](ShallowWrapper/setProps.md)
Manually sets props of the root component.

#### [`.setContext(context) => ShallowWrapper`](ShallowWrapper/setContext.md)
Manually sets context of the root component.

#### [`.instance() => ReactComponent`](ShallowWrapper/instance.md)
Returns the instance of the root component.

Expand Down
24 changes: 21 additions & 3 deletions src/ReactWrapper.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React from 'react';
import { flatten, unique, compact } from 'underscore';
import ReactWrapperComponent from './ReactWrapperComponent';
import createWrapperComponent from './ReactWrapperComponent';
import {
instHasClassName,
childrenOfInst,
Expand Down Expand Up @@ -45,7 +45,7 @@ function filterWhereUnwrapped(wrapper, predicate) {
*/
export default class ReactWrapper {

constructor(nodes, root) {
constructor(nodes, root, options = {}) {
if (!global.window && !global.document) {
throw new Error(
`It looks like you called \`mount()\` without a jsdom document being loaded. ` +
Expand All @@ -54,10 +54,12 @@ export default class ReactWrapper {
}

if (!root) {
const ReactWrapperComponent = createWrapperComponent(nodes, options);
this.component = renderIntoDocument(
<ReactWrapperComponent
Component={nodes.type}
props={nodes.props}
context={options.context}
/>
);
this.root = this;
Expand Down Expand Up @@ -147,7 +149,7 @@ export default class ReactWrapper {
if (this.root !== this) {
throw new Error('ReactWrapper::setProps() can only be called on the root');
}
this.component.setProps(props);
this.component.setChildProps(props);
return this;
}

Expand All @@ -171,6 +173,22 @@ export default class ReactWrapper {
return this;
}

/**
* TODO(lmr): description
*
* NOTE: can only be called on a wrapper instance that is also the root instance.
*
* @param {Object} context object
* @returns {ReactWrapper}
*/
setContext(context) {
if (this.root !== this) {
throw new Error('ReactWrapper::setContext() can only be called on the root');
}
this.component.setChildContext(context);
return this;
}

/**
* Whether or not a given react element exists in the mount render tree.
*
Expand Down
98 changes: 64 additions & 34 deletions src/ReactWrapperComponent.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,45 +8,75 @@ import React, { PropTypes } from 'react';
* the DOM node it rendered to, so we can't really "re-render" to
* pass new props in.
*/
export default class ReactWrapperComponent extends React.Component {
export default function createWrapperComponent(node, options = {}) {
const spec = {

constructor(props) {
super(props);
this.state = Object.assign({}, props.props);
}
propTypes: {
Component: PropTypes.oneOfType([PropTypes.func, PropTypes.string]).isRequired,
props: PropTypes.object.isRequired,
context: PropTypes.object,
},

setProps(newProps) {
return new Promise(resolve => this.setState(newProps, resolve));
}
getDefaultProps() {
return {
context: null,
};
},

getInitialState() {
return {
props: this.props.props,
context: this.props.context,
};
},

setChildProps(props) {
return new Promise(resolve => this.setState({ props }, resolve));
},

setChildContext(context) {
return new Promise(resolve => this.setState({ context }, resolve));
},

getInstance() {
const component = this._reactInternalInstance._renderedComponent;
const inst = component.getPublicInstance();
if (inst === null) {
throw new Error(
`You cannot get an instance of a stateless component.`
getInstance() {
const component = this._reactInternalInstance._renderedComponent;
const inst = component.getPublicInstance();
if (inst === null) {
throw new Error(
`You cannot get an instance of a stateless component.`
);
}
return inst;
},

getWrappedComponent() {
const component = this._reactInternalInstance._renderedComponent;
const inst = component.getPublicInstance();
if (inst === null) {
return component;
}
return inst;
},

render() {
const { Component } = this.props;
return (
<Component {...this.state.props} />
);
}
return inst;
}
},
};

getWrappedComponent() {
const component = this._reactInternalInstance._renderedComponent;
const inst = component.getPublicInstance();
if (inst === null) {
return component;
}
return inst;
if (options.context && node.type.contextTypes) {
// For full rendering, we are using this wrapper component to provide context if it is
// specified in both the options AND the child component defines `contextTypes` statically.
// In that case, we define both a `getChildContext()` function and a `childContextTypes` prop.
Object.assign(spec, {
childContextTypes: node.type.contextTypes,
getChildContext() {
return this.state.context;
},
});
}

render() {
const { Component } = this.props;
return (
<Component {...this.state} />
);
}
return React.createClass(spec);
}
ReactWrapperComponent.propTypes = {
Component: PropTypes.oneOfType([PropTypes.func, PropTypes.string]).isRequired,
props: PropTypes.object.isRequired,
};
Loading

0 comments on commit a089c9f

Please sign in to comment.