Skip to content
This repository has been archived by the owner on Sep 20, 2018. It is now read-only.

Commit

Permalink
[added] shallow rendering utils
Browse files Browse the repository at this point in the history
  • Loading branch information
jquense committed Sep 28, 2015
1 parent fa5d48d commit d862503
Show file tree
Hide file tree
Showing 7 changed files with 298 additions and 7 deletions.
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
v2.1.0 - Mon, 28 Sep 2015 13:45:13 GMT
--------------------------------------

- [df40903](../../commit/df40903) [added] shallow rendering utils



47 changes: 47 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ A simple jquery like api wrapper for the React TestUtils to make them a bit frie
Updates for react 0.14, works with Stateless Components and you can scry and filter on DOM components
as well.

### Traditional DOM rendering

```js
var $r = require('react-testutil-query')

Expand Down Expand Up @@ -55,5 +57,50 @@ $root.find(MyInput).dom() //returns an array of DOM nodes

// -- events
$root.find(MyInput).trigger('change', { target: { value: 6 }}) // triggers onChange for all of them
```

### Shallow rendering

We can use an even more powerful selector syntax will shallow rendering

```js
var $ = require('react-testutil-query/shallow');

let label = 'list item';

let BasicList = props => <ul>{props.children}</ul>

let DivList = ()=> (
<div>
<BasicList className='my-list'>
<li className='foo'>hi 1</li>
<li className='foo'>hi 2</li>
<li aria-label={label}>hi 3</li>
</BasicList>
</div>
)


let $root = $(<DivList);

$root.find('.my-list > li.foo').length // 2

$root.find('.my-list').children('.foo').length // 2

$root.find('div li[aria-label="list item"]').length // 1

// you can even use es6 template strings to write
// selectors for your custom components
$root.find($.s`${BasicList} > li.foo`).length // 2

//or for prop values
$root.find($.s`li[aria-label=${label}]`).length // 1

$root.find(BasicList)
.children()
.filter(element => element.props.className === 'foo')
.length // 2

$root.find(BasicList).is('.my-list').length // true

```
7 changes: 3 additions & 4 deletions karma.conf.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ module.exports = function (config) {
reporters: ['mocha'],

files: [
'test.js'
'./test/*.js'
],

port: 9876,
Expand All @@ -19,14 +19,13 @@ module.exports = function (config) {

logLevel: config.LOG_INFO,

browsers: ['Chrome'],
browsers: ['Chrome'],

preprocessors: {
'test.js': ['webpack']
'test/*.js': ['webpack']
},

webpack: {
entry: './test.js',
module: {
loaders: [{ test: /\.js$/, loader: 'babel', exclude: /node_modules/ }]
}
Expand Down
6 changes: 4 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "react-testutil-query",
"version": "2.0.0",
"version": "2.1.0",
"description": "small wrapper around react test utils so I don't have to write long method names",
"main": "lib/index.js",
"scripts": {
Expand Down Expand Up @@ -49,10 +49,12 @@
"webpack": "^1.12.2"
},
"dependencies": {
"bill": "^1.0.4",
"dom-helpers": "^2.4.0",
"react-addons-test-utils": "^0.14.0-rc1"
},
"release-script": {
"defaultDryRun": "false"
"defaultDryRun": "false",
"altPkgRootFolder": "lib"
}
}
126 changes: 126 additions & 0 deletions src/shallow.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
import React, { cloneElement, isValidElement } from 'react';
import TestUtils from 'react-addons-test-utils';
import { match as _match, selector as s } from 'bill';

let isRtq = item => item && item.__isRTQ

rtq.s = rtq.selector = s;

export default rtq;

function match(selector, tree, includeSelf){
if (typeof selector === 'function')
selector = s`${selector}`

return _match(selector, tree, includeSelf)
}

function render(element){
let root = element;

if (!(typeof root.type === 'string' && root.type.toLowerCase() === root.type)){
let renderer = TestUtils.createRenderer()
renderer.render(element)
root = renderer.getRenderOutput();
}

return {
root,
setProps(props){
return render(cloneElement(element, props))
}
}
}

function rtq(element) {
var context, rerender;

if (TestUtils.isElement(element)) {
let { root, setProps } = render(element)
element = context = root
rerender = setProps
}
else if (isRtq(element)) {
context = element.root
element = element.get();
//rerender = element._rerender
}

return new ShallowCollection(element, context, rerender)
}

class ShallowCollection {
constructor(elements, root, rerender){
elements = [].concat(elements).filter(el => isValidElement(el))

var idx = -1;

while( ++idx < elements.length)
this[idx] = elements[idx]

this._rerender = rerender
this.length = elements.length
this.root = root
}

setProps(props){
this._rerender && this._rerender(props)
return this
}

each(cb) {
var idx = -1, len = this.length;
while (++idx < len) cb(this[idx], idx, this)
return this
}

get() {
var result = []
this.each(el => result.push(el))
return result
}

reduce(cb, initial){
return new ShallowCollection(
[].reduce.call(this, cb, initial)
, this.root
)
}

map(cb) {
var result = []
this.each((v, i, l) => result.push(cb(v, i, l)))

return new ShallowCollection(result, this.root)
}

find(selector) {
return this.reduce((result, element) => {
return result.concat(match(selector, element))
}, [])
}

children(selector) {
return this
.reduce((result, element) => result.concat(element.props.children || []), [])
.filter(selector)
}

filter(selector) {
if (!selector)
return this

if (typeof selector === 'function')
return new ShallowCollection([].filter.call(this, selector), this.root)

let matches = match(selector, this.root);

return new ShallowCollection([].filter.call(this, el => {
return matches.indexOf(el) !== -1
}), this.root)
}

is(selector) {
return this.filter(selector).length === this.length
}
}
2 changes: 1 addition & 1 deletion test.js → test/dom.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React from 'react';
import { unmountComponentAtNode, render } from 'react-dom';
import $ from './src/index';
import $ from '../src/index';

chai.use(require('sinon-chai'))

Expand Down
110 changes: 110 additions & 0 deletions test/shallow.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
import React, { cloneElement } from 'react';
import $ from '../src/shallow';
import { selector as sel } from 'bill';

chai.use(require('sinon-chai'))

describe('Shallow rendering', ()=> {
let Stateless = props => <div onClick={props.onClick}>{props.children}</div>
let List = class extends React.Component {
render(){
return (
<div onClick={this.props.onClick}>
<Stateless onClick={this.props.onClick}>
<span className='stateless-inner' onClick={this.props.onClick}/>
</Stateless>
<ul onClick={this.props.onClick}>
<li className='item' onClick={this.props.onClick}>hi 1</li>
<li onClick={this.props.onClick}>hi 2</li>
<li>hi 3</li>
</ul>
</div>
)
}
}

it('create rtq object', ()=>{
let instance = $(<div/>)

instance.root.type.should.equal('div')
instance.length.should.equal(1)
})

it('should not try to render primitives', ()=>{
let el = <div/>
, instance = $(el)

instance.root.should.equal(el)
})

it('should render Composite Components', ()=>{
let el = <div/>
, Element = ()=> el
, instance = $(<Element/>)

instance.root.should.equal(el)
})

it('should filter out invalid Elements', ()=>{
let instance = $(
<ul>
{ false }
{ null}
{'text'}
<li>hi 1</li>
<li>hi 2</li>
<li>hi 3</li>
</ul>
)

instance.children().length.should.equal(3)
})

describe('querying', ()=> {
let FancyList = props => <ul className='fancy-list'>{props.children}</ul>
let List = ()=> (
<div>
<FancyList>
<li className='foo'>hi 1</li>
<li className='foo'>hi 2</li>
<li>hi 3</li>
</FancyList>
</div>
)

it('should: find()', ()=>{
$(<List/>).find('li').length.should.equal(3)
})

it('should: children()', ()=> {
$(<List/>).find(FancyList).children().length.should.equal(3)

$(<List/>).find(FancyList).children('.foo').length.should.equal(2)
})

it('should: filter()', ()=>{
let items = $(<List/>).find('li')

items.length.should.equal(3)
items.filter('.foo').length.should.equal(2)

items.filter((el, idx) => idx === 0).length.should.equal(1)
})

it('an empty filter should be a noop', ()=>{
let instance = $(<List/>)
instance.filter().should.equal(instance)
})

it('should: is()', ()=>{
$(<List/>).find('.foo')
.is('li').should.equal(true)

$(<List/>).find('.foo')
.is(sel`${FancyList} > li`).should.equal(true)

$(<List/>).find(FancyList)
.is('div').should.equal(false)
})
})
})

0 comments on commit d862503

Please sign in to comment.