Files
react/src/test/reactComponentExpect.js
T
Marshall Roch b91396be8e Contexts
Summary:
adds `this.context` which you can think of as implicit props, which are passed automatically down the //ownership// hierarchy.

Contexts should be used sparingly, since they essentially allow components to communicate with descendants (in the ownership sense, not parenthood sense), which is not usually a good idea. You probably would only use contexts in places where you'd normally use a global, but contexts allow you to override them for certain view subtrees which you can't do with globals.

The context starts out `null`:

  var RootComponent = React.createClass({
    render: function() {
      // this.context === null
    }
  });

You should **never** mutate the context directly, just like props and state.

You can change the context of your children (the ones you own, not `this.props.children` or via other props) using the new `withContext` method on `React`:

  var RootComponent = React.createClass({
    render: function() {
      // this.context === null
      var children = React.withContext({foo: 'a', bar: 'b'}, () => (
        // In ChildComponent#render, this.context === {foo: 'a', bar: 'b'}
        <ChildComponent />
      ));
      // this.context === null
    }
  });

Contexts are merged, so a component can override its owner's context **for its children**:

  var ChildComponent = React.createClass({
    render: function() {
      // this.context === {foo: 'a', bar: 'b'} (for the caller above)
      var children = React.withContext({foo: 'c'},() => (
        // In GrandchildComponent#render,
        // this.context === {foo: 'c', bar: 'b'}
        <GrandchildComponent />
      ));
      // this.context === {foo: 'a', bar: 'b'}
    }
  });
2013-11-18 10:56:24 -08:00

219 lines
6.4 KiB
JavaScript

/**
* Copyright 2013 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* @providesModule reactComponentExpect
* @nolint
*/
var ReactComponent = require('ReactComponent');
var ReactTestUtils = require('ReactTestUtils');
var mergeInto = require('mergeInto');
function reactComponentExpect(instance) {
if (instance instanceof reactComponentExpect) {
return instance;
}
if (!(this instanceof reactComponentExpect)) {
return new reactComponentExpect(instance);
}
this._instance = instance;
this.toBeValidReactComponent();
}
mergeInto(reactComponentExpect.prototype, {
// Getters -------------------------------------------------------------------
/**
* @instance: Retrieves the backing instance.
*/
instance: function() {
return this._instance;
},
/**
* There are two types of components in the world.
* - A component created via React.createClass() - Has a single child
* subComponent - the return value from the .render() function. This
* function @subComponent expects that this._instance is component created
* with React.createClass().
* - A primitive DOM component - which has many renderedChildren, each of
* which may have a name that is unique with respect to its siblings. This
* method will fail if this._instance is a primitive component.
*
* TL;DR: An instance may have a subComponent (this._renderedComponent) or
* renderedChildren, but never both. Neither will actually show up until you
* render the component (simply instantiating is not enough).
*/
expectRenderedChild: function() {
this.toBeCompositeComponent();
return new reactComponentExpect(this.instance()._renderedComponent);
},
/**
* The nth child of a DOMish component instance that is not falsy.
*/
expectRenderedChildAt: function(childIndex) {
// Currently only dom components have arrays of children, but that will
// change soon.
this.toBeDOMComponent();
var renderedChildren = this.instance()._renderedChildren || {};
var nonEmptyCount = 0;
var name;
for (name in renderedChildren) {
if (!renderedChildren.hasOwnProperty(name)) {
continue;
}
if (renderedChildren[name]) {
if (nonEmptyCount === childIndex) {
return new reactComponentExpect(renderedChildren[name]);
}
nonEmptyCount++;
}
}
throw new Error('Child:' + childIndex + ' is not found');
},
toBeDOMComponentWithChildCount: function(n) {
this.toBeDOMComponent();
expect(this.instance()._renderedChildren).toBeTruthy();
var len = Object.keys(this.instance()._renderedChildren).length;
expect(len).toBe(n);
return this;
},
toBeDOMComponentWithNoChildren: function() {
this.toBeDOMComponent();
expect(this.instance()._renderedChildren).toBeFalsy();
return this;
},
// Matchers ------------------------------------------------------------------
toBeComponentOfType: function(convenienceConstructor) {
expect(ReactTestUtils.isComponentOfType(
this.instance(),
convenienceConstructor
)).toBe(true);
return this;
},
/**
* A component that is created with React.createClass. Just duck typing
* here.
*/
toBeCompositeComponent: function() {
this.toBeValidReactComponent();
expect(ReactTestUtils.isCompositeComponent(this.instance())).toBe(true);
return this;
},
toBeCompositeComponentWithType: function(convenienceConstructor) {
expect(ReactTestUtils.isCompositeComponentWithType(
this.instance(),
convenienceConstructor
)).toBe(true);
return this;
},
toBeTextComponent: function() {
expect(ReactTestUtils.isTextComponent(this.instance())).toBe(true);
return this;
},
/**
* Falsy values are valid components - the vanished component that is.
*/
toBeValidReactComponent: function() {
expect(ReactComponent.isValidComponent(this.instance())).toBe(true);
return this;
},
toBePresent: function() {
expect(this.instance()).toBeTruthy();
return this;
},
/**
* A terminal type of component representing some virtual dom node. Just duck
* typing here.
*/
toBeDOMComponent: function() {
expect(ReactTestUtils.isDOMComponent(this.instance())).toBe(true);
return this;
},
/**
* @deprecated
* @see toBeComponentOfType
*/
toBeDOMComponentWithTag: function(tag) {
this.toBeDOMComponent();
expect(this.instance().tagName).toBe(tag.toUpperCase());
return this;
},
/**
* Check that internal state values are equal to a state of expected values.
*/
scalarStateEqual: function(stateNameToExpectedValue) {
expect(this.instance()).toBeTruthy();
for (var stateName in stateNameToExpectedValue) {
if (!stateNameToExpectedValue.hasOwnProperty(stateName)) {
continue;
}
expect(this.instance().state[stateName])
.toEqual(stateNameToExpectedValue[stateName]);
}
return this;
},
/**
* Check a set of props are equal to a set of expected values - only works
* with scalars.
*/
scalarPropsEqual: function(propNameToExpectedValue) {
expect(this.instance()).toBeTruthy();
for (var propName in propNameToExpectedValue) {
if (!propNameToExpectedValue.hasOwnProperty(propName)) {
continue;
}
expect(this.instance().props[propName])
.toEqual(propNameToExpectedValue[propName]);
}
return this;
},
/**
* Check a set of props are equal to a set of expected values - only works
* with scalars.
*/
scalarContextEqual: function(contextNameToExpectedValue) {
expect(this.instance()).toBeTruthy();
for (var contextName in contextNameToExpectedValue) {
if (!contextNameToExpectedValue.hasOwnProperty(contextName)) {
continue;
}
expect(this.instance().context[contextName])
.toEqual(contextNameToExpectedValue[contextName]);
}
return this;
}
});
module.exports = reactComponentExpect;