From d9c7f8c01190d56f811ac975c9f496796d478573 Mon Sep 17 00:00:00 2001 From: Benjamin Seber Date: Tue, 25 Jul 2017 09:16:54 +0200 Subject: [PATCH 1/5] added cogs build task as npm run script --- package.json | 3 +++ 1 file changed, 3 insertions(+) diff --git a/package.json b/package.json index e8f4364..26b34b8 100644 --- a/package.json +++ b/package.json @@ -8,6 +8,9 @@ "type": "git", "url": "https://github.com/orgsync/react-list" }, + "scripts": { + "build": "cogs" + }, "dependencies": { "prop-types": "15" }, From 52cab61c84d5e138f62fc2892dc9beefb1b0a340 Mon Sep 17 00:00:00 2001 From: Benjamin Seber Date: Tue, 25 Jul 2017 09:18:41 +0200 Subject: [PATCH 2/5] added jest and specs for react-list rendering of type 'uniform' --- .eslintrc | 3 +- .../__snapshots__/react-list.spec.js.snap | 21 +++++ __tests__/react-list.spec.js | 84 +++++++++++++++++++ package.json | 19 ++++- 4 files changed, 124 insertions(+), 3 deletions(-) create mode 100644 __tests__/__snapshots__/react-list.spec.js.snap create mode 100644 __tests__/react-list.spec.js diff --git a/.eslintrc b/.eslintrc index 09c3532..2b12303 100644 --- a/.eslintrc +++ b/.eslintrc @@ -17,7 +17,8 @@ "mocha": true, "node": true, "phantomjs": true, - "worker": true + "worker": true, + "jest": true }, "globals": { "__DEV__": true diff --git a/__tests__/__snapshots__/react-list.spec.js.snap b/__tests__/__snapshots__/react-list.spec.js.snap new file mode 100644 index 0000000..5b729cf --- /dev/null +++ b/__tests__/__snapshots__/react-list.spec.js.snap @@ -0,0 +1,21 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`react-list uniform react-list renders into DOM 1`] = ` +
+
+
    +
  • + + list item: + + + 1 + +
  • +
+
+
+`; diff --git a/__tests__/react-list.spec.js b/__tests__/react-list.spec.js new file mode 100644 index 0000000..094bd12 --- /dev/null +++ b/__tests__/react-list.spec.js @@ -0,0 +1,84 @@ +import React from 'react'; +import ReactDOM from 'react-dom'; +import ReactTestUtils from 'react-dom/test-utils'; +import ReactTestRenderer from 'react-test-renderer'; +import ReactList from './../react-list'; + +describe('react-list', function () { + + const createNodeMock = function createNodeMock(element) { + if (element.type === 'div') { + // div container of react-list + return document.createElement('div'); + } + if (element.type === 'ul') { + // element of the + return document.createElement('ul'); + } + // You can return any object from this method for any type of DOM component. + // React will use it as a ref instead of a DOM node when snapshot testing. + return null; + }; + + const render = function render(component) { + const options = { + createNodeMock + }; + return ReactTestRenderer.create(component, options); + }; + + class MyList extends React.Component { + constructor(props) { + super(props); + this.listRenderer = this.listRenderer.bind(this); + this.listItemRenderer = this.listItemRenderer.bind(this); + } + + listItemRenderer(index, key) { + return ( +
  • + list item: {this.props.items[index]} +
  • + ); + } + + listRenderer(items, ref) { + return ( +
      + {items} +
    + ); + } + + render() { + return ( + + ); + } + } + + describe('uniform react-list', () => { + + it('renders with react-test-renderer', function () { + expect(() => { + const tree = render( + + ); + expect(tree).toMatchSnapshot(); + }).toThrow(); + }); + + it('renders into DOM', function () { + const tree = ReactTestUtils.renderIntoDocument( + + ); + const domNode = ReactDOM.findDOMNode(tree); + expect(domNode).toMatchSnapshot(); + }); + }); +}); diff --git a/package.json b/package.json index 26b34b8..36a6420 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,8 @@ "url": "https://github.com/orgsync/react-list" }, "scripts": { - "build": "cogs" + "build": "cogs", + "test": "NODE_ENV=test jest" }, "dependencies": { "prop-types": "15" @@ -29,6 +30,20 @@ "cogs-transformer-eslint": "^3.0.0", "cogs-transformer-replace": "^3.0.0", "eslint": "^3.4.0", - "eslint-plugin-react": "^6.2.0" + "eslint-plugin-react": "^6.2.0", + "jest": "^20.0.4", + "react": "^15.6.1", + "react-dom": "^15.6.1", + "react-test-renderer": "^15.6.1" + }, + "babel": { + "env": { + "test": { + "presets": [ + "es2015", + "react" + ] + } + } } } From 8cdcac5c2ce7bde8caed9d1bab4156a87fb20a1d Mon Sep 17 00:00:00 2001 From: Benjamin Seber Date: Tue, 25 Jul 2017 09:20:01 +0200 Subject: [PATCH 3/5] replaced findDOMNode with refs to support react-test-renderer in unit tests --- .../__snapshots__/react-list.spec.js.snap | 27 +++++++++++ __tests__/react-list.spec.js | 10 ++-- react-list.es6 | 19 ++++---- react-list.js | 47 ++++++++++++------- 4 files changed, 69 insertions(+), 34 deletions(-) diff --git a/__tests__/__snapshots__/react-list.spec.js.snap b/__tests__/__snapshots__/react-list.spec.js.snap index 5b729cf..881f061 100644 --- a/__tests__/__snapshots__/react-list.spec.js.snap +++ b/__tests__/__snapshots__/react-list.spec.js.snap @@ -19,3 +19,30 @@ exports[`react-list uniform react-list renders into DOM 1`] = ` `; + +exports[`react-list uniform react-list renders with react-test-renderer 1`] = ` +
    +
    +
      +
    • + list item: + 1 +
    • +
    +
    +
    +`; diff --git a/__tests__/react-list.spec.js b/__tests__/react-list.spec.js index 094bd12..bf2966e 100644 --- a/__tests__/react-list.spec.js +++ b/__tests__/react-list.spec.js @@ -65,12 +65,10 @@ describe('react-list', function () { describe('uniform react-list', () => { it('renders with react-test-renderer', function () { - expect(() => { - const tree = render( - - ); - expect(tree).toMatchSnapshot(); - }).toThrow(); + const tree = render( + + ); + expect(tree).toMatchSnapshot(); }); it('renders into DOM', function () { diff --git a/react-list.es6 b/react-list.es6 index 3af2a81..f0a878a 100644 --- a/react-list.es6 +++ b/react-list.es6 @@ -1,9 +1,6 @@ import module from 'module'; import PropTypes from 'prop-types'; import React, {Component} from 'react'; -import ReactDOM from 'react-dom'; - -const {findDOMNode} = ReactDOM; const CLIENT_SIZE_KEYS = {x: 'clientWidth', y: 'clientHeight'}; const CLIENT_START_KEYS = {x: 'clientTop', y: 'clientLeft'}; @@ -142,7 +139,7 @@ module.exports = class ReactList extends Component { getScrollParent() { const {axis, scrollParentGetter} = this.props; if (scrollParentGetter) return scrollParentGetter(); - let el = findDOMNode(this); + let el = this.rootDOMNode; const overflowKey = OVERFLOW_KEYS[axis]; while (el = el.parentElement) { switch (window.getComputedStyle(el)[overflowKey]) { @@ -164,14 +161,14 @@ module.exports = class ReactList extends Component { scrollParent[scrollKey]; const max = this.getScrollSize() - this.getViewportSize(); const scroll = Math.max(0, Math.min(actual, max)); - const el = findDOMNode(this); + const el = this.rootDOMNode; return this.getOffset(scrollParent) + scroll - this.getOffset(el); } setScroll(offset) { const {scrollParent} = this; const {axis} = this.props; - offset += this.getOffset(findDOMNode(this)); + offset += this.getOffset(this.rootDOMNode); if (scrollParent === window) return window.scrollTo(0, offset); offset -= this.getOffset(this.scrollParent); @@ -217,7 +214,7 @@ module.exports = class ReactList extends Component { return {itemSize, itemsPerRow}; } - const itemEls = findDOMNode(this.items).children; + const itemEls = this.items.children; if (!itemEls.length) return {}; const firstEl = itemEls[0]; @@ -268,7 +265,7 @@ module.exports = class ReactList extends Component { updateSimpleFrame(cb) { const {end} = this.getStartAndEnd(); - const itemEls = findDOMNode(this.items).children; + const itemEls = this.items.children; let elEnd = 0; if (itemEls.length) { @@ -363,7 +360,7 @@ module.exports = class ReactList extends Component { cacheSizes() { const {cache} = this; const {from} = this.state; - const itemEls = findDOMNode(this.items).children; + const itemEls = this.items.children; const sizeKey = OFFSET_SIZE_KEYS[this.props.axis]; for (let i = 0, l = itemEls.length; i < l; ++i) { cache[from + i] = itemEls[i][sizeKey]; @@ -386,7 +383,7 @@ module.exports = class ReactList extends Component { // Try the DOM. if (type === 'simple' && index >= from && index < from + size && items) { - const itemEl = findDOMNode(items).children[index - from]; + const itemEl = items.children[index - from]; if (itemEl) return itemEl[OFFSET_SIZE_KEYS[axis]]; } @@ -474,6 +471,6 @@ module.exports = class ReactList extends Component { WebkitTransform: transform, transform }; - return
    {items}
    ; + return
    this.rootDOMNode = node}>
    {items}
    ; } }; diff --git a/react-list.js b/react-list.js index 5656d08..68a50f4 100644 --- a/react-list.js +++ b/react-list.js @@ -1,16 +1,16 @@ (function (global, factory) { if (typeof define === "function" && define.amd) { - define(['module', 'prop-types', 'react', 'react-dom'], factory); + define(['module', 'prop-types', 'react'], factory); } else if (typeof exports !== "undefined") { - factory(module, require('prop-types'), require('react'), require('react-dom')); + factory(module, require('prop-types'), require('react')); } else { var mod = { exports: {} }; - factory(mod, global.PropTypes, global.React, global.ReactDOM); + factory(mod, global.PropTypes, global.React); global.ReactList = mod.exports; } -})(this, function (_module2, _propTypes, _react, _reactDom) { +})(this, function (_module2, _propTypes, _react) { 'use strict'; var _module3 = _interopRequireDefault(_module2); @@ -19,14 +19,26 @@ var _react2 = _interopRequireDefault(_react); - var _reactDom2 = _interopRequireDefault(_reactDom); - function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + var _extends = Object.assign || function (target) { + for (var i = 1; i < arguments.length; i++) { + var source = arguments[i]; + + for (var key in source) { + if (Object.prototype.hasOwnProperty.call(source, key)) { + target[key] = source[key]; + } + } + } + + return target; + }; + function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); @@ -77,9 +89,6 @@ var _class, _temp; - var findDOMNode = _reactDom2.default.findDOMNode; - - var CLIENT_SIZE_KEYS = { x: 'clientWidth', y: 'clientHeight' }; var CLIENT_START_KEYS = { x: 'clientTop', y: 'clientLeft' }; var INNER_SIZE_KEYS = { x: 'innerWidth', y: 'innerHeight' }; @@ -215,7 +224,7 @@ scrollParentGetter = _props.scrollParentGetter; if (scrollParentGetter) return scrollParentGetter(); - var el = findDOMNode(this); + var el = this.rootDOMNode; var overflowKey = OVERFLOW_KEYS[axis]; while (el = el.parentElement) { switch (window.getComputedStyle(el)[overflowKey]) { @@ -239,7 +248,7 @@ document.body[scrollKey] || document.documentElement[scrollKey] : scrollParent[scrollKey]; var max = this.getScrollSize() - this.getViewportSize(); var scroll = Math.max(0, Math.min(actual, max)); - var el = findDOMNode(this); + var el = this.rootDOMNode; return this.getOffset(scrollParent) + scroll - this.getOffset(el); } }, { @@ -248,7 +257,7 @@ var scrollParent = this.scrollParent; var axis = this.props.axis; - offset += this.getOffset(findDOMNode(this)); + offset += this.getOffset(this.rootDOMNode); if (scrollParent === window) return window.scrollTo(0, offset); offset -= this.getOffset(this.scrollParent); @@ -309,7 +318,7 @@ return { itemSize: itemSize, itemsPerRow: itemsPerRow }; } - var itemEls = findDOMNode(this.items).children; + var itemEls = this.items.children; if (!itemEls.length) return {}; var firstEl = itemEls[0]; @@ -364,7 +373,7 @@ var _getStartAndEnd = this.getStartAndEnd(), end = _getStartAndEnd.end; - var itemEls = findDOMNode(this.items).children; + var itemEls = this.items.children; var elEnd = 0; if (itemEls.length) { @@ -479,7 +488,7 @@ var cache = this.cache; var from = this.state.from; - var itemEls = findDOMNode(this.items).children; + var itemEls = this.items.children; var sizeKey = OFFSET_SIZE_KEYS[this.props.axis]; for (var i = 0, l = itemEls.length; i < l; ++i) { cache[from + i] = itemEls[i][sizeKey]; @@ -512,7 +521,7 @@ // Try the DOM. if (type === 'simple' && index >= from && index < from + size && items) { - var itemEl = findDOMNode(items).children[index - from]; + var itemEl = items.children[index - from]; if (itemEl) return itemEl[OFFSET_SIZE_KEYS[axis]]; } @@ -599,6 +608,8 @@ }, { key: 'render', value: function render() { + var _this4 = this; + var _props8 = this.props, axis = _props8.axis, length = _props8.length, @@ -631,7 +642,9 @@ }; return _react2.default.createElement( 'div', - { style: style }, + _extends({ style: style }, { ref: function ref(node) { + return _this4.rootDOMNode = node; + } }), _react2.default.createElement( 'div', { style: listStyle }, From 53570505c95207776e2b3f4eef0badbcc969cbf4 Mon Sep 17 00:00:00 2001 From: Benjamin Seber Date: Tue, 25 Jul 2017 10:31:40 +0200 Subject: [PATCH 4/5] added react-test-renderer to readme FAQ --- README.md | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/README.md b/README.md index 71e275f..d5d0ba2 100644 --- a/README.md +++ b/README.md @@ -203,6 +203,41 @@ If you need an onScroll handler, just add the handler to the div wrapping your R ``` +##### Why are my unit tests failing with `TypeError: Cannot read property 'parentElement' of null`? + +Your're probably using [react-test-render](https://github.com/facebook/react/tree/master/packages/react-test-renderer) to create snapshot tests. `react-test-render` is an +abstraction layer and knows nothing about the react specific `ref` feature to get DOM nodes. +Therefore the `null` element access. + +However, you can pass options to the `react-test-renderer`: + +```js +import ReactTestRenderer from 'react-test-renderer'; + +function render (component) { + const reactTestRendererOptions = { + createNodeMock + }; + return ReactTestRenderer.create(component, reactTestRendererOptions); +} + +function isDOMElementType(type) { + return [ + 'div', + 'ul', + 'table', + // ... + ].includes(type); +} + +function createNodeMock(element) { + if (isDOMElementType(element.type)) { + return document.createElement(element.type); + } + return null; +} +``` + ## Development ```bash From 34bafb539244a4c8a9c7c23d78add0cf1c22542e Mon Sep 17 00:00:00 2001 From: Benjamin Seber Date: Tue, 25 Jul 2017 10:36:48 +0200 Subject: [PATCH 5/5] added tests as prebuild task --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index 36a6420..b801186 100644 --- a/package.json +++ b/package.json @@ -9,6 +9,7 @@ "url": "https://github.com/orgsync/react-list" }, "scripts": { + "prebuild": "npm run test", "build": "cogs", "test": "NODE_ENV=test jest" },