diff --git a/CHANGELOG.md b/CHANGELOG.md
index e69de29..69f4e23 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -0,0 +1,7 @@
+v2.1.0 - Mon, 28 Sep 2015 13:45:13 GMT
+--------------------------------------
+
+- [df40903](../../commit/df40903) [added] shallow rendering utils
+
+
+
diff --git a/README.md b/README.md
index 83f14c1..b4728b3 100644
--- a/README.md
+++ b/README.md
@@ -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')
@@ -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 =>
+
+let DivList = ()=> (
+
+
+ hi 1
+ hi 2
+ hi 3
+
+
+)
+
+
+let $root = $( 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
```
diff --git a/karma.conf.js b/karma.conf.js
index 8a4c3ab..04de6c5 100644
--- a/karma.conf.js
+++ b/karma.conf.js
@@ -9,7 +9,7 @@ module.exports = function (config) {
reporters: ['mocha'],
files: [
- 'test.js'
+ './test/*.js'
],
port: 9876,
@@ -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/ }]
}
diff --git a/package.json b/package.json
index 5c3f81b..5815e47 100644
--- a/package.json
+++ b/package.json
@@ -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": {
@@ -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"
}
}
diff --git a/src/shallow.js b/src/shallow.js
new file mode 100644
index 0000000..0888373
--- /dev/null
+++ b/src/shallow.js
@@ -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
+ }
+}
diff --git a/test.js b/test/dom.js
similarity index 99%
rename from test.js
rename to test/dom.js
index 120dced..7e63779 100644
--- a/test.js
+++ b/test/dom.js
@@ -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'))
diff --git a/test/shallow.js b/test/shallow.js
new file mode 100644
index 0000000..1752fb4
--- /dev/null
+++ b/test/shallow.js
@@ -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 => {props.children}
+ let List = class extends React.Component {
+ render(){
+ return (
+
+ )
+ }
+ }
+
+ it('create rtq object', ()=>{
+ let instance = $()
+
+ instance.root.type.should.equal('div')
+ instance.length.should.equal(1)
+ })
+
+ it('should not try to render primitives', ()=>{
+ let el =
+ , instance = $(el)
+
+ instance.root.should.equal(el)
+ })
+
+ it('should render Composite Components', ()=>{
+ let el =
+ , Element = ()=> el
+ , instance = $()
+
+ instance.root.should.equal(el)
+ })
+
+ it('should filter out invalid Elements', ()=>{
+ let instance = $(
+
+ { false }
+ { null}
+ {'text'}
+ - hi 1
+ - hi 2
+ - hi 3
+
+ )
+
+ instance.children().length.should.equal(3)
+ })
+
+ describe('querying', ()=> {
+ let FancyList = props =>
+ let List = ()=> (
+
+
+ hi 1
+ hi 2
+ hi 3
+
+
+ )
+
+ it('should: find()', ()=>{
+ $(
).find('li').length.should.equal(3)
+ })
+
+ it('should: children()', ()=> {
+ $(
).find(FancyList).children().length.should.equal(3)
+
+ $(
).find(FancyList).children('.foo').length.should.equal(2)
+ })
+
+ it('should: filter()', ()=>{
+ let items = $(
).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 = $(
)
+ instance.filter().should.equal(instance)
+ })
+
+ it('should: is()', ()=>{
+ $(
).find('.foo')
+ .is('li').should.equal(true)
+
+ $(
).find('.foo')
+ .is(sel`${FancyList} > li`).should.equal(true)
+
+ $(
).find(FancyList)
+ .is('div').should.equal(false)
+ })
+ })
+})