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/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
diff --git a/__tests__/__snapshots__/react-list.spec.js.snap b/__tests__/__snapshots__/react-list.spec.js.snap
new file mode 100644
index 0000000..881f061
--- /dev/null
+++ b/__tests__/__snapshots__/react-list.spec.js.snap
@@ -0,0 +1,48 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`react-list uniform react-list renders into DOM 1`] = `
+
+
+
+ -
+
+ list item:
+
+
+ 1
+
+
+
+
+
+`;
+
+exports[`react-list uniform react-list renders with react-test-renderer 1`] = `
+
+`;
diff --git a/__tests__/react-list.spec.js b/__tests__/react-list.spec.js
new file mode 100644
index 0000000..bf2966e
--- /dev/null
+++ b/__tests__/react-list.spec.js
@@ -0,0 +1,82 @@
+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 (
+
+ );
+ }
+
+ render() {
+ return (
+
+ );
+ }
+ }
+
+ describe('uniform react-list', () => {
+
+ it('renders with react-test-renderer', function () {
+ const tree = render(
+
+ );
+ expect(tree).toMatchSnapshot();
+ });
+
+ 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 e8f4364..b801186 100644
--- a/package.json
+++ b/package.json
@@ -8,6 +8,11 @@
"type": "git",
"url": "https://github.com/orgsync/react-list"
},
+ "scripts": {
+ "prebuild": "npm run test",
+ "build": "cogs",
+ "test": "NODE_ENV=test jest"
+ },
"dependencies": {
"prop-types": "15"
},
@@ -26,6 +31,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"
+ ]
+ }
+ }
}
}
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 ;
+ 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 },