Skip to content

Commit

Permalink
Added context-pure mixin and tests
Browse files Browse the repository at this point in the history
  • Loading branch information
Hai Nguyen committed Aug 12, 2015
1 parent 8fb5a7a commit 980c84e
Show file tree
Hide file tree
Showing 3 changed files with 260 additions and 0 deletions.
39 changes: 39 additions & 0 deletions src/mixins/context-pure.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
const shallowEqual = require('../utils/shallow-equal');

function contextPropsEqual(classObject, currentContext, nextContext) {

//Check if current object's context props changed
if (classObject.getContextProps) {
const currentContextProps = classObject.getContextProps(currentContext);
const nextContextProps = classObject.getContextProps(nextContext);
if (!shallowEqual(currentContextProps, nextContextProps)) {
return false;
}
}

//Check if children context props changed
if (classObject.getChildrenClasses) {
const childrenArray = classObject.getChildrenClasses();
for (let i = 0; i < childrenArray.length; i++) {
if (!contextPropsEqual(childrenArray[i], currentContext, nextContext)) {
return false;
}
}
}

//Props are equal
return true;
}

module.exports = {

//Don't update if state, prop, and context are equal
shouldComponentUpdate(nextProps, nextState, nextContext) {
return (
!shallowEqual(this.props, nextProps) ||
!shallowEqual(this.state, nextState) ||
!contextPropsEqual(this.constructor, this.context, nextContext)
);
},

};
27 changes: 27 additions & 0 deletions src/utils/shallow-equal.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
export default function shallowEqual(objA, objB) {
if (objA === objB) {
return true;
}

if (typeof objA !== 'object' || objA === null ||
typeof objB !== 'object' || objB === null) {
return false;
}

const keysA = Object.keys(objA);
const keysB = Object.keys(objB);

if (keysA.length !== keysB.length) {
return false;
}

// Test for A's keys different from B.
const bHasOwnProperty = Object.prototype.hasOwnProperty.bind(objB);
for (let i = 0; i < keysA.length; i++) {
if (!bHasOwnProperty(keysA[i]) || objA[keysA[i]] !== objB[keysA[i]]) {
return false;
}
}

return true;
}
194 changes: 194 additions & 0 deletions test/mixin-context-pure-spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
import React from 'react/addons';
import ContextPure from 'mixins/context-pure';

const TestUtils = React.addons.TestUtils;

const GrandChildComponent = React.createClass({
mixins: [ContextPure],

contextTypes: {
testContext: React.PropTypes.object,
},

statics: {
getContextProps(context) {
return {
grandChildContextProp: context.testContext.grandChildContextProp,
}
},
},

renderCount: 0,

render() {
this.renderCount++;
return <div />;
},

getRenderCount() {
return this.renderCount;
},
});

const ChildComponent = React.createClass({
mixins: [ContextPure],

contextTypes: {
testContext: React.PropTypes.object,
},

getInitialState: function() {
return {
childState: 0,
};
},

statics: {
getContextProps(context) {
return {
childContextProp: context.testContext.childContextProp,
}
},
getChildrenClasses() {
return [
GrandChildComponent,
];
},
},

renderCount: 0,

render() {
this.renderCount++;
return <GrandChildComponent ref="grandChild" />;
},

getGrandChildRenderCount() {
return this.refs.grandChild.getRenderCount();
},

getRenderCount() {
return this.renderCount;
},

updateState(childState) {
this.setState({childState});
},
});

const ParentComponent = React.createClass({

childContextTypes: {
testContext: React.PropTypes.object,
},

getChildContext() {
return {
testContext: {
childContextProp: this.state.childContextProp,
grandChildContextProp: this.state.grandChildContextProp,
},
};
},

getInitialState() {
return {
childProp: 0,
childContextProp: 0,
grandChildContextProp: 0,
};
},

renderCount: 0,

render() {
this.renderCount++;
return <ChildComponent ref="child" testProp={this.state.childProp} />;
},

getChildRenderCount() {
return this.refs.child.getRenderCount();
},

getGrandChildRenderCount() {
return this.refs.child.getGrandChildRenderCount();
},

getRenderCount() {
return this.renderCount;
},

updateChildState(childState) {
this.refs.child.updateState(childState);
},

updateChildContextProp(childContextProp) {
this.setState({childContextProp});
},

updateGrandChildContextProp(grandChildContextProp) {
this.setState({grandChildContextProp});
},

updateChildProp(childProp) {
this.setState({childProp});
},
});

describe('Mixin-ContextPure', () => {
let parentElement;

beforeEach(() => {
parentElement = TestUtils.renderIntoDocument(<ParentComponent />);
});

it('should not render when context is updated but did not change', () => {
parentElement.updateChildContextProp(0);
parentElement.getRenderCount().should.equal(2);
parentElement.getChildRenderCount().should.equal(1);
parentElement.getGrandChildRenderCount().should.equal(1);
});

it('should not render when prop is updated but did not change', () => {
parentElement.updateChildProp(0);
parentElement.getRenderCount().should.equal(2);
parentElement.getChildRenderCount().should.equal(1);
parentElement.getGrandChildRenderCount().should.equal(1);
});

it('should not render when state is updated but did not change', () => {
parentElement.updateChildState(0);
parentElement.getRenderCount().should.equal(1);
parentElement.getChildRenderCount().should.equal(1);
parentElement.getGrandChildRenderCount().should.equal(1);
});

it('should render when context props change', () => {
parentElement.updateChildContextProp(1);
parentElement.getRenderCount().should.equal(2);
parentElement.getChildRenderCount().should.equal(2);
parentElement.getGrandChildRenderCount().should.equal(1);
});

it('should render when props change', () => {
parentElement.updateChildProp(1);
parentElement.getRenderCount().should.equal(2);
parentElement.getChildRenderCount().should.equal(2);
parentElement.getGrandChildRenderCount().should.equal(1);
});

it('should render when state change', () => {
parentElement.updateChildState(1);
parentElement.getRenderCount().should.equal(1);
parentElement.getChildRenderCount().should.equal(2);
parentElement.getGrandChildRenderCount().should.equal(1);
});

it('should render grandchild when grandchild context props change', () => {
parentElement.updateGrandChildContextProp(1);
parentElement.getRenderCount().should.equal(2);
parentElement.getChildRenderCount().should.equal(2);
parentElement.getGrandChildRenderCount().should.equal(2);
});

});

0 comments on commit 980c84e

Please sign in to comment.