diff --git a/package.json b/package.json index e0207b1..1304678 100644 --- a/package.json +++ b/package.json @@ -35,6 +35,9 @@ "mocha": "^3.4.2", "nyc": "^11.0.2", "pre-commit": "^1.2.2", + "react": "^16.12.0", + "react-test-renderer": "^16.12.0", + "sinon": "^7.5.0", "typescript": "^2.6.1" }, "nyc": { diff --git a/spec/react.spec.js b/spec/react.spec.js new file mode 100644 index 0000000..1fb54c2 --- /dev/null +++ b/spec/react.spec.js @@ -0,0 +1,65 @@ +'use strict'; + +const assert = require('assert'); +const sinon = require('sinon'); +const React = require('react'); +const ReactTestRenderer = require('react-test-renderer'); + +const equal = require('../es6/react'); + +class ChildWithShouldComponentUpdate extends React.Component { + shouldComponentUpdate(nextProps) { + // this.props.children is a h1 with a circular reference to its owner, Container + return !equal(this.props, nextProps); + } + render() { + return null; + } +} + +class Container extends React.Component { + render() { + return React.createElement(ChildWithShouldComponentUpdate, { + children: [ + React.createElement('h1', this.props.title || ''), + React.createElement('h2', this.props.subtitle || '') + ] + }); + } +} + +describe('advanced', () => { + let sandbox; + let warnStub; + let childRenderSpy; + + beforeEach(() => { + sandbox = sinon.createSandbox(); + warnStub = sandbox.stub(console, 'warn'); + childRenderSpy = sandbox.spy(ChildWithShouldComponentUpdate.prototype, 'render'); + }); + + afterEach(() => { + sandbox.restore(); + }); + + describe('React', () => { + describe('element (with circular references)', () => { + it('compares without warning or errors', () => { + const testRenderer = ReactTestRenderer.create(React.createElement(Container)); + testRenderer.update(React.createElement(Container)); + assert.strictEqual(warnStub.callCount, 0); + }); + it('elements of same type and props are equal', () => { + const testRenderer = ReactTestRenderer.create(React.createElement(Container)); + testRenderer.update(React.createElement(Container)); + assert.strictEqual(childRenderSpy.callCount, 1); + }); + it('elements of same type with different props are not equal', () => { + const testRenderer = ReactTestRenderer.create(React.createElement(Container)); + testRenderer.update(React.createElement(Container, { title: 'New' })); + assert.strictEqual(childRenderSpy.callCount, 2); + }); + }); + }); +}); diff --git a/src/index.jst b/src/index.jst index eabcf74..5d4ee2f 100644 --- a/src/index.jst +++ b/src/index.jst @@ -60,6 +60,14 @@ module.exports = function equal(a, b) { for (i = length; i-- !== 0;) { var key = keys[i]; +{{? it.react }} + if (key === '_owner' && a.$$typeof) { + // React-specific: avoid traversing React elements' _owner. + // _owner contains circular references + // and is not needed when comparing the actual elements (and not their owners) + continue; + } +{{?}} if (!equal(a[key], b[key])) return false; }