Skip to content

Support react test renderer #185

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .eslintrc
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@
"mocha": true,
"node": true,
"phantomjs": true,
"worker": true
"worker": true,
"jest": true
},
"globals": {
"__DEV__": true
Expand Down
35 changes: 35 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,41 @@ If you need an onScroll handler, just add the handler to the div wrapping your R
</div>
```

##### 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
Expand Down
48 changes: 48 additions & 0 deletions __tests__/__snapshots__/react-list.spec.js.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`react-list uniform react-list renders into DOM 1`] = `
<div
data-reactroot=""
style="position: relative;"
>
<div>
<ul>
<li>
<!-- react-text: 5 -->
list item:
<!-- /react-text -->
<!-- react-text: 6 -->
1
<!-- /react-text -->
</li>
</ul>
</div>
</div>
`;

exports[`react-list uniform react-list renders with react-test-renderer 1`] = `
<div
style={
Object {
"position": "relative",
}
}
>
<div
style={
Object {
"WebkitTransform": "translate(0px, 0px)",
"msTransform": "translate(0px, 0px)",
"transform": "translate(0px, 0px)",
}
}
>
<ul>
<li>
list item:
1
</li>
</ul>
</div>
</div>
`;
82 changes: 82 additions & 0 deletions __tests__/react-list.spec.js
Original file line number Diff line number Diff line change
@@ -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 <UniformList>
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 (
<li key={key}>
list item: {this.props.items[index]}
</li>
);
}

listRenderer(items, ref) {
return (
<ul ref={ref}>
{items}
</ul>
);
}

render() {
return (
<ReactList
length={this.props.items.length}
type={this.props.type}
itemRenderer={this.listItemRenderer}
itemsRenderer={this.listRenderer}
/>
);
}
}

describe('uniform react-list', () => {

it('renders with react-test-renderer', function () {
const tree = render(
<MyList type={'uniform'} items={[1, 2, 3, 4, 5]} />
);
expect(tree).toMatchSnapshot();
});

it('renders into DOM', function () {
const tree = ReactTestUtils.renderIntoDocument(
<MyList type={'uniform'} items={[1, 2, 3, 4, 5]} />
);
const domNode = ReactDOM.findDOMNode(tree);
expect(domNode).toMatchSnapshot();
});
});
});
21 changes: 20 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
},
Expand All @@ -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"
]
}
}
}
}
19 changes: 8 additions & 11 deletions react-list.es6
Original file line number Diff line number Diff line change
@@ -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'};
Expand Down Expand Up @@ -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]) {
Expand All @@ -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);
Expand Down Expand Up @@ -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];
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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];
Expand All @@ -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]];
}

Expand Down Expand Up @@ -474,6 +471,6 @@ module.exports = class ReactList extends Component {
WebkitTransform: transform,
transform
};
return <div {...{style}}><div style={listStyle}>{items}</div></div>;
return <div {...{style}} ref={node => this.rootDOMNode = node}><div style={listStyle}>{items}</div></div>;
}
};
Loading