-
-);
+$(
+
+).find('ul > li')
+```
-var $elements = $(elements).render();
+##### `$.fn.filter(selector)`
-$elements = $(elements).render(true); //accessible by document.querySelectorAll
+Filter the current collection matching against the provided
+selector.
+
+```js
+let $list = $([
+
1,
+
2,
+
3,
+]);
-$elements = $(elements).render(true, document.createElement('span')); //mounts the component to the
+$list.filter('.foo').length // 1
```
-#### `$.fn.shallowRender([props]) -> ElementCollection`
+##### `$.fn.is(selector) -> Bool`
-Use the React shallow renderer utilities to _shallowly_ render the first element of the collection.
+Test if each item in the collection matches the provided
+selector.
+
+##### `$.fn.children([selector])`
+
+Return the children of the current selection, optionally filtered by those matching a provided selector.
+
+__note:__ rendered "Composite" components will only ever have one child since Components can only return a single node.
```js
-let MyComponent ()=>
Hi there!
+let $list = $(
+
+);
-$(
).find('div').length // 0
+$list.children().length // 3
-$(
).shallowRender().is('div') // true
+$list.children('.foo').length // 1
```
-### `$.fn.update()`
-Rerenders and updates a shallowly rendered element collection.
+##### `$.fn.parent([selector])`
+
+Get the parent of each node in the current collection, optionally filtered by a selector.
+
+##### `$.fn.parents([selector])`
+
+Get the ancestors of each node in the current collection, optionally filtered by a selector.
-__note:__ `.update()` must be called on a "root" collection (the result of `.shallowRender()`)
+##### `$.fn.closest([selector])`
-#### `$.fn.trigger(String eventName, [Object data])`
+For each node in the set, get the first element that matches the selector by testing the element
+and traversing up through its ancestors.
-"trigger" an event on an element. More of convenience method than a real event trigger, since shallow rendering
-doesn't actually involve DOM event system. `trigger()` looks for a function prop of the element and calls it, it also
-updates the root collection, so that any state/context/prop changes at the top component propagate down.
+##### `$.fn.first([selector])`
+
+return the first item in a collection, alternatively search all
+collection descendants matching the provided selector and return
+the first match.
+
+##### `$.fn.last([selector])`
+
+return the last item in a collection, alternatively search all
+collection descendants matching the provided selector and return
+the last match.
+
+##### `$.fn.only()`
+
+Assert that the current collection as only one item.
```js
-let root = $(
).shallowRender()
+let $list = $(
+
+);
-root.find('button').trigger('click', {
- target: { value: 'hello' }
-})
+$list.find('li').only() // Error! Matched more than one
+
+$list.find('li.foo').only().length // 1
```
-### InstanceCollection
+##### `$.fn.single(selector)`
-InstanceCollections are created when selecting Component instances, such as
-the result of a `ReactDOM.render()` call.
+Find and assert that only item matches the provided selector.
-The public "instances" for components differ. DOM component instances
-are the DOM nodes themselves, and Stateless Components technically don't have any
-(we use the DOM node though). One key advantage to over the normal React
-test utils is that here you can continue to chain `find` and `filter` on
-DOM and Stateless components.
+```js
+let $list = $(
+
+);
-#### `$.fn.dom -> HTMLElement`
+$list.single('li') // Error! Matched more than one
-Returns the DOM nodes for each item in the Collection, if the exist
+$list.single('.foo').length // 1
+```
-#### `$.fn.unmount -> HTMLElement`
+##### `$.fn.text()`
-Unmount the current tree and remove it from the DOM. `unmount()` returns an
-ElementCollection of the _root_ component element.
+Return the text content of the matched Collection.
+
+```js
+let $els = $(
Hello John
).render()
+ .trigger('click', { target: { value: 'hello ' } }).
+```
+
+##### `$.element.fn.trigger(String eventName, [Object data])`
-#### `$.fn.trigger(String eventName, [Object data])`
+Simulates (poorly) event triggering for shallow collections. The method looks for a prop
+following the convention 'on[EventName]': `trigger('click')` calls `props.onClick()`, and rerenders the root collection
-Trigger a "synthetic" (React) event on the collection items.
+Events don't bubble and don't have a proper event object.
```js
-$(
).render().trigger('click', { target: { value: 'hello' } }).
+ $(
).shallowRender()
+ .find('button')
+ .trigger('click', { target: { value: 'hello ' } }).
```
diff --git a/lib/element.js b/lib/element.js
index 1ebf8bb..3ffaec0 100644
--- a/lib/element.js
+++ b/lib/element.js
@@ -63,7 +63,7 @@ _extends(eQuery.fn, {
if (instance === null) instance = _reactDom2['default'].render(utils.wrapStateless(element), mount);
- return _instance2['default'](instance, utils.getInternalInstance(instance), mount);
+ return _instance2['default'](instance, utils.(instance), mount);
},
shallowRender: function shallowRender(props) {
diff --git a/lib/instance.js b/lib/instance.js
index 2e8f2fa..8296058 100644
--- a/lib/instance.js
+++ b/lib/instance.js
@@ -51,7 +51,7 @@ var $ = _QueryCollection2['default'](utils.match, _bill2['default'], function in
mount = mount || context && context._mountPoint || utils.getMountPoint(first);
- this.context = context && context.context || context || utils.getInternalInstance(utils.getRootInstance(mount));
+ this.context = context && context.context || context || utils.(utils.getRootInstance(mount));
this._mountPoint = mount;
this._privateInstances = Object.create(null);
diff --git a/lib/utils.js b/lib/utils.js
index 10a7796..2f45240 100644
--- a/lib/utils.js
+++ b/lib/utils.js
@@ -3,7 +3,7 @@
exports.__esModule = true;
exports.isCompositeComponent = isCompositeComponent;
exports.getInstances = getInstances;
-exports.getInternalInstance = getInternalInstance;
+exports. = ;
exports.wrapStateless = wrapStateless;
exports.getMountPoint = getMountPoint;
exports.getRootInstance = getRootInstance;
@@ -65,7 +65,7 @@ function isCompositeComponent(inst) {
function getInstances(component) {
var _public = component,
- _private = getInternalInstance(component);
+ _private = (component);
if (component.getPublicInstance) {
_public = component.getPublicInstance();
@@ -79,7 +79,7 @@ function getInstances(component) {
return { 'private': _private, 'public': _public };
}
-function getInternalInstance(component) {
+function (component) {
if (!component) return;
if (component.getPublicInstance) return component;
diff --git a/package.json b/package.json
index 1251782..ca05512 100644
--- a/package.json
+++ b/package.json
@@ -6,7 +6,8 @@
"scripts": {
"test": "karma start --single-run",
"tdd": "karma start",
- "build": "babel src --out-dir lib && cpy ./README.md ./lib",
+ "toc": "doctoc README.md --github",
+ "build": "babel src --out-dir lib && npm run toc && cpy ./README.md ./lib",
"release": "release"
},
"repository": {
@@ -34,10 +35,13 @@
"babel-plugin-object-assign": "^1.2.1",
"chai": "^3.3.0",
"cpy": "^3.4.1",
+ "doctoc": "^0.15.0",
+ "isparta": "^4.0.0",
"karma": "^0.13.10",
"karma-chai": "^0.1.0",
"karma-chrome-launcher": "^0.2.0",
"karma-cli": "^0.1.0",
+ "karma-coverage": "^0.5.3",
"karma-mocha": "^0.2.0",
"karma-mocha-reporter": "^1.1.1",
"karma-sinon": "^1.0.4",
@@ -52,9 +56,10 @@
"webpack": "^1.12.2"
},
"dependencies": {
- "bill": "^2.0.0",
+ "bill": "^2.0.3",
"dom-helpers": "^2.4.0",
"lodash": "^3.10.1",
+ "promise": "^7.1.1",
"react-addons-test-utils": "^0.14.0-rc1",
"warning": "^2.1.0"
},
diff --git a/src/QueryCollection.js b/src/QueryCollection.js
index d397c89..f92f85b 100644
--- a/src/QueryCollection.js
+++ b/src/QueryCollection.js
@@ -1,7 +1,10 @@
-import common from './common';
-import { createNode } from 'bill/node';
+
import { selector } from 'bill';
-import { match, getPublicInstances } from './utils';
+import {
+ isQueryCollection, getPublicInstances
+ , unwrapAndCreateNode, attachElementsToCollection } from './utils';
+
+import common from './common';
export default function createCollection(ctor) {
let $ = QueryCollection
@@ -12,35 +15,26 @@ export default function createCollection(ctor) {
let elements = element == null ? [] : [].concat(element);
- if (element && $.isQueryCollection(element)) {
+ if (element && isQueryCollection(element)) {
return new element.constructor(element.get(), element)
}
this._isQueryCollection = true
- this.context = lastCollection || this
- this.nodes = elements.map(el => createNode(el))
- this.length = elements.length
+ this.root = lastCollection || this
- getPublicInstances(this.nodes)
- .forEach((el, idx)=> this[idx] = el)
+ attachElementsToCollection(this, elements)
- return ctor.call(this, element, lastCollection)
+ return ctor.call(this, elements, lastCollection)
}
- Object.assign($, {
- match,
- selector,
- s: selector,
- isQueryCollection(inst) {
- return !!inst._isQueryCollection
- }
- })
-
- $.fn = $.prototype = {
- constructor: $,
- }
+ $.fn = $.prototype = Object.create(common)
- common($)
+ Object.defineProperty($.prototype, 'constructor', {
+ value: $,
+ enumerable: false,
+ writable: true,
+ configurable: true
+ })
return $
}
diff --git a/src/common.js b/src/common.js
index a6a4d9c..10145ac 100644
--- a/src/common.js
+++ b/src/common.js
@@ -1,7 +1,10 @@
-import { findAll } from 'bill';
+import invariant from 'invariant';
+import { findAll, registerPseudo } from 'bill';
import { NODE_TYPES } from 'bill/node';
import * as utils from './utils';
import findIndex from 'lodash/array/findIndex';
+import create from 'lodash/object/create'
+let { assertLength, is } = utils;
function indexOfNode(arr, findNode) {
return findIndex(arr, (node, i) => {
@@ -14,141 +17,186 @@ function noTextNodes(nodes) {
return nodes.filter(node => node.nodeType !== NODE_TYPES.TEXT)
}
-function assertLength(collection, method) {
- if (collection.length === 0)
- throw new Error('the method `' + method + '()` found no matching elements')
- return collection
-}
+let $ = (t, ...args) => new t.constructor(...args)
-export default function($){
- Object.assign($, {
- dom(component){
- return utils.findDOMNode(component)
- }
- })
- // return values
- ;['every', 'some']
- .forEach(method => {
- let fn = [][method];
+let common = {
+
+ _reduce(...args) {
+ return $(this, this.nodes.reduce(...args), this)
+ },
+
+ _map(cb){
+ var result = []
+ this.each((...args) => result.push(cb(...args)))
+ return result
+ },
+
+ each(fn, thisArg) {
+ [].forEach.call(this, fn, thisArg || this)
+ return this;
+ },
+
+ tap(fn) {
+ fn.call(this, this)
+ return this
+ },
+
+ get() {
+ var result = []
+ this.each(el => result.push(el))
+ return result
+ },
+
+ find(selector, includeSelf = false) {
+ return this._reduce((result, node) => {
+ return result.concat(utils.match(selector, node, includeSelf))
+ }, [])
+ },
- $.fn[method] = function (...args) {
- return fn.apply(this, args)
+ traverse(test) {
+ return this._reduce((result, node) => {
+ return result.concat(findAll(node, test))
+ }, [])
+ },
+
+ filter(selector) {
+ if (!selector) return this
+
+ return this._reduce((result, node) => {
+ return is(selector, node) ? result.concat(node) : result
+ }, [])
+ },
+
+ is(selector) {
+ return this.filter(selector).length === this.length
+ },
+
+ children(selector) {
+ return this
+ ._reduce((result, node) => result.concat(noTextNodes(node.children)), [])
+ .filter(selector)
+ },
+
+ parent(selector) {
+ return this._reduce((nodes, node) => {
+ let match = true;
+
+ if (node = node.parentNode) {
+ if (selector)
+ match = is(selector, node)
+
+ if (match && nodes.indexOf(node) === -1)
+ nodes.push(node)
}
- })
+ return nodes
+ }, [])
+ },
+
+ parents(selector) {
+ return this._reduce((nodes, node) => {
+ while (node = node.parentNode) {
+ let match = true;
- // return collections
- ;['map', 'reduce', 'reduceRight']
- .forEach(method => {
- let fn = [][method];
+ if (selector)
+ match = is(selector, node)
- $.fn[method] = function (...args) {
- return $(fn.apply(this, args))
+ if (match && nodes.indexOf(node) === -1)
+ nodes.push(node)
}
- })
-
- Object.assign($.fn, {
-
- _reduce(...args) {
- return $(this.nodes.reduce(...args), this)
- },
-
- _map(cb){
- var result = []
- this.each((...args) => result.push(cb(...args)))
- return result
- },
-
- each(fn, thisArg) {
- [].forEach.call(this, fn, thisArg || this)
- return this;
- },
-
- tap(fn) {
- fn.call(this, this)
- return this
- },
-
- get() {
- var result = []
- this.each(el => result.push(el))
- return result
- },
-
- find(selector, includeSelf = false) {
- return this._reduce((result, node) => {
- return result.concat(utils.match(selector, node, includeSelf))
- }, [])
- },
-
- traverse(test) {
- return this._reduce((result, node) => {
- return result.concat(findAll(node, test))
- }, [])
- },
-
- filter(selector) {
- if (!selector) return this
-
- let matches = utils.match(selector, this.context.nodes[0], true);
-
- return this._reduce((result, node) => {
- if (indexOfNode(matches, node) !== -1)
- result.push(node);
-
- return result
- }, [])
- },
-
- is(selector) {
- return this.filter(selector).length === this.length
- },
-
- children(selector) {
- return this
- ._reduce((result, node) => result.concat(noTextNodes(node.children)), [])
- .filter(selector)
- },
-
- text() {
- let isText = el => typeof el === 'string';
-
- return this.find(':text').nodes
- .reduce((str, node) => str + node.element, '')
- },
-
- first(selector) {
- return selector
- ? this.find(selector).first()
- : $(assertLength(this, 'first')[0], this)
- },
-
- last(selector) {
- return selector
- ? this.find(selector).last()
- : $(assertLength(this, 'last')[this.length - 1], this)
- },
-
- only() {
- if (this.length !== 1)
- throw new Error('The query found: ' + this.length + ' items not 1')
-
- return this.first()
- },
-
- single(selector) {
- return selector
- ? this.find(selector).only()
- : this.only()
- },
-
- unwrap() {
- return this.single()[0]
- },
-
- elements() {
- return this.nodes.map(node => node.element)
+
+ return nodes
+ }, [])
+ },
+
+ closest(selector) {
+ let test = selector ? n => is(selector, n) : (() => true)
+
+ return this._reduce((nodes, node) => {
+ do {
+ node = node.parentNode
+ }
+ while (node && !test(node))
+
+ if (node && nodes.indexOf(node) === -1)
+ nodes.push(node)
+
+ return nodes
+ }, [])
+ },
+
+ text() {
+ let isText = el => typeof el === 'string';
+
+ return this.find(':text').nodes
+ .reduce((str, node) => str + node.element, '')
+ },
+
+ first(selector) {
+ return selector
+ ? this.find(selector).first()
+ : $(this, assertLength(this, 'first')[0], this)
+ },
+
+ last(selector) {
+ return selector
+ ? this.find(selector).last()
+ : $(this, assertLength(this, 'last')[this.length - 1], this)
+ },
+
+ only() {
+ if (this.length !== 1)
+ throw new Error('The query found: ' + this.length + ' items not 1')
+
+ return this.first()
+ },
+
+ single(selector) {
+ return selector
+ ? this.find(selector).only()
+ : this.only()
+ },
+
+ unwrap() {
+ return this.single()[0]
+ },
+
+ elements() {
+ return this.nodes.map(node => node.element)
+ }
+}
+
+function unwrap(arr){
+ return arr && arr.length === 1 ? arr[0] : arr
+}
+
+// return values
+;['every', 'some']
+ .forEach(method => {
+ let fn = [][method];
+
+ common[method] = function (...args) {
+ return fn.apply(this, args)
+ }
+ })
+
+// return collections
+;['map', 'reduce', 'reduceRight']
+ .forEach(method => {
+ let fn = [][method];
+
+ common[method] = function (...args) {
+ return $(this, fn.apply(this, args))
}
})
+
+let aliases = {
+ get: 'alias',
+ each: 'forEach'
}
+
+Object.keys(aliases)
+ .forEach(method => common[aliases[method]] = common[method])
+
+export default common
diff --git a/src/element.js b/src/element.js
index 1cadba2..c9a6e80 100644
--- a/src/element.js
+++ b/src/element.js
@@ -6,10 +6,25 @@ import createQueryCollection from './QueryCollection';
import iQuery from './instance'
import * as utils from './utils';
import { selector } from 'bill';
+import { createNode } from 'bill/node';
+import invariant from 'invariant';
-function assertRoot(inst, msg) {
- if (inst.context && inst.context !== inst)
- throw new Error(msg || 'You can only preform this action on "root" element.')
+let {
+ assertLength, assertRoot, assertStateful
+ , render, attachElementsToCollection } = utils;
+
+let createCallback = (collection, fn) => ()=> fn.call(collection, collection)
+
+function getShallowInstance(renderer) {
+ return renderer && renderer._instance._instance;
+}
+
+function getShallowTreeWithRoot(renderer) {
+ let children = renderer.getRenderOutput()
+ , instance = getShallowInstance(renderer)
+ , element = createNode(instance).element;
+
+ return React.cloneElement(element, { children })
}
function spyOnUpdate(inst, fn) {
@@ -22,39 +37,43 @@ function spyOnUpdate(inst, fn) {
}
}
-
-
let $ = createQueryCollection(function (elements, lastCollection) {
- if (lastCollection && lastCollection.renderer) {
- this.context = this;
- this._renderer = renderer; // different name to protect back compat
- spyOnUpdate(this._instance(), ()=> this.update())
+ if (lastCollection) {
+ this._rendered = lastCollection._rendered
}
})
-$.instance = iQuery
-
Object.assign($.fn, {
_instance() {
- return this._renderer && this._renderer._instance._instance;
+ return getShallowInstance(this._renderer);
},
- render(intoDocument, mountPoint) {
+ render(intoDocument, mountPoint, context) {
+ if (arguments.length && typeof intoDocument !== 'boolean') {
+ context = mountPoint
+ mountPoint = intoDocument
+ intoDocument = false
+ }
+
+ if (mountPoint && !(mountPoint instanceof HTMLElement)) {
+ context = mountPoint
+ mountPoint = null
+ }
+
var mount = mountPoint || document.createElement('div')
- , element = this[0];
+ , element = assertLength(this, 'render')[0];
if (intoDocument)
document.body.appendChild(mount)
- let instance = ReactDOM.render(element, mount);
+ let { instance, wrapper } = render(element, mount, null, context)
- if (instance === null) {
- instance = ReactDOM.render(utils.wrapStateless(element), mount)
- instance = utils.getInternalInstance(instance)
- }
+ let collection = iQuery(instance);
- return iQuery(instance);
+ collection._mountPoint = mount
+ collection._rootWrapper = wrapper;
+ return collection;
},
shallowRender(props, context) {
@@ -69,35 +88,107 @@ Object.assign($.fn, {
if (isDomElement)
return $(element)
- if (!this.renderer)
- this.renderer = ReactTestUtils.createRenderer()
+ let renderer = ReactTestUtils.createRenderer()
- this.renderer.render(element, context);
+ renderer.render(element, context);
- let collection = $(this.renderer.getRenderOutput());
+ let collection = $(getShallowTreeWithRoot(renderer));
- collection._renderer = this.renderer;
+ collection._rendered = true;
+ collection._renderer = renderer;
+
+ spyOnUpdate(collection._instance(), ()=> collection.update())
return collection;
},
update() {
+ assertRoot(this)
if (!this._renderer)
throw new Error('You can only preform this action on a "root" element.')
- this.context =
- this[0] = this._renderer.getRenderOutput()
+ attachElementsToCollection(this, getShallowTreeWithRoot(this._renderer))
return this
},
- prop(key) {
- return key ? this[0].props[key] : this[0].props;
+ props(...args) {
+ let value = utils.collectArgs(...args)
+ let node = assertLength(this, 'props').nodes[0]
+
+ if (args.length === 0 || (typeof value === 'string' && args.length === 1)) {
+ let element = node.element
+ return value ? element.props[value] : element.props
+ }
+
+ if (this._rendered) {
+ assertRoot(this, 'changing the props on a shallow rendered child is an anti-pattern, ' +
+ 'since the elements props will be overridden by its parent in the next update() of the root element')
+
+ this._renderer.render(React.cloneElement(this[0], value));
+ attachElementsToCollection(this, getShallowTreeWithRoot(this._renderer))
+ return this
+ }
+
+ return this.map(el => React.isValidElement(el)
+ ? React.cloneElement(el, value) : el)
},
- state(key) {
- assertRoot(this, 'Only "root" rendered elements can have state')
- let state = this._instance().state;
- return key && state ? state[key] : state
+ state(...args) {
+ let value = utils.collectArgs(...args)
+ , callback = args[2] || args[1];
+
+ assertLength(this, 'state')
+ assertStateful(this.nodes[0])
+
+ invariant(this._rendered,
+ 'Only rendered trees can be stateful; ' +
+ 'use either `shallowRender` or `render` first before inspecting or setting state.'
+ )
+
+ assertRoot(this,
+ 'Only the root component of shallowly rendered tree is instantiated; ' +
+ 'children elements are stateless so inspecting or setting state on them does\'t make sense ' +
+ 'use DOM rendering to verifying child state, or select and shallowRender the child itself.'
+ )
+
+ if (args.length === 0 || (typeof value === 'string' && args.length === 1)) {
+ let key = value
+ , state = this._instance().state;
+
+ return key && state ? state[key] : state
+ }
+
+ callback = typeof callback === 'function'
+ ? createCallback(this, callback) : undefined
+
+ this._instance().setState(value, callback)
+
+ return this
+ },
+
+ context(...args) {
+ let value = utils.collectArgs(...args)
+ let inst = assertLength(this, 'context')._instance()
+ let context = inst.context
+
+ invariant(this._rendered,
+ 'Only rendered trees can pass context; ' +
+ 'use either `shallowRender` or `render` first before inspecting or setting context.'
+ )
+
+ assertRoot(this,
+ 'Only the root component of a shallowly rendered tree is instantiated; ' +
+ 'The children are jsut plain elements and are not passed context.'
+ )
+
+ if (args.length === 0 || (typeof value === 'string' && args.length === 1)) {
+ return value && context ? context[value] : context
+ }
+
+ this._renderer.render(this[0], { ...context, ...value });
+ attachElementsToCollection(this, getShallowTreeWithRoot(this._renderer))
+
+ return this
},
trigger(event, ...args) {
@@ -107,8 +198,6 @@ Object.assign($.fn, {
return this.each(component => {
component.props[event]
&& component.props[event](...args)
-
- this._root && this._root.update()
});
}
diff --git a/src/index.js b/src/index.js
index 002d3c3..3fb60cf 100644
--- a/src/index.js
+++ b/src/index.js
@@ -1,18 +1,57 @@
-import eQuery from './element'
-import iQuery from './instance'
+import ElementCollection from './element'
+import InstanceCollection from './instance'
+import commonPrototype from './common';
+import { selector, registerPseudo } from 'bill';
+import { createNode, NODE_TYPES } from 'bill/node';
+import { match, getPublicInstances } from './utils';
+
import * as utils from './utils';
let isComponent = el => utils.isDOMComponent(el) || utils.isCompositeComponent(el)
+let $ = NodeCollection;
function NodeCollection(elements) {
- let first = [].concat(elements).filter(e => !!e)[0];
+ let first = [].concat(elements).filter(e => !!e)[0]
+ , node = first && createNode(first);
- if (first && isComponent(first))
- return new iQuery(elements);
+ if (node && node.privateInstance)
+ return new InstanceCollection(elements)
- return new eQuery(elements)
+ return new ElementCollection(elements);
}
-NodeCollection = Object.assign(NodeCollection, eQuery, iQuery)
+$.fn = $.prototype = commonPrototype
+
+Object.assign($, {
+ match,
+ selector,
+ s: selector,
+ isQueryCollection: utils.isQueryCollection,
+ dom: utils.findDOMNode
+})
+
+$.element = ElementCollection
+$.instance = InstanceCollection
+
+$.registerPseudo = (pseudo, isSelector, fn)=> {
+ if (typeof isSelector === 'function')
+ fn = isSelector, isSelector = true;
+
+ registerPseudo(pseudo, isSelector, test =>
+ node => fn(node, test))
+}
+
+$.registerPseudo('contains', false, (node, text) => {
+ return ($(node).text() || '').indexOf(text) !== -1
+})
+
+$.registerPseudo('textContent', false, (node, text) => {
+ let textContent = node.children
+ .filter(n => n.nodeType === NODE_TYPES.TEXT)
+ .map(n => n.element)
+ .join('')
+
+ return (!text && !!textContent) || text === textContent
+})
-module.exports = NodeCollection
+module.exports = $
diff --git a/src/instance.js b/src/instance.js
index a1cfcd1..d36d58f 100644
--- a/src/instance.js
+++ b/src/instance.js
@@ -9,66 +9,105 @@ import createCollection from './QueryCollection';
import * as utils from './utils';
import selector from 'bill';
+let { assertLength, assertRoot, assertStateful, collectArgs } = utils;
+
+let createCallback = (collection, fn) => ()=> fn.call(collection, collection)
+
+function getSetterMethod(key){
+ return function (...args) {
+ let value = collectArgs(...args)
+ let node = assertLength(this, key).nodes[0]
+ let data = node.instance && node.instance[key]
+
+ if (args.length === 0 || (typeof value === 'string' && args.length === 1)) {
+ if (!data)
+ data = node.privateInstance._currentElement[key];
+
+ return value && data ? data[value] : data
+ }
+
+ if (key === 'props')
+ utils.render(this, value)
+ else if (key === 'context')
+ utils.render(this, null, { ...data, ...value })
+ else throw new Error('invalid key: ' + key)
+
+ return this
+ }
+}
+
let $ = createCollection(function (element, lastCollection) {
let first = this.nodes[0]
if (!lastCollection) {
- this._mountPoint = utils.getMountPoint(first.instance)
- }
-})
-
-Object.assign($, {
- dom(component){
- return utils.findDOMNode(component)
+ try {
+ // no idea if I can do this in 0.15
+ this._mountPoint = utils.getMountPoint(first.instance)
+ }
+ catch (err) {}
}
+ else
+ this._mountPoint = lastCollection._mountPoint
})
Object.assign($.fn, {
- _reduce(cb, initial){
- return $(this.nodes.reduce(cb, initial), this)
+ render(...args) {
+ let collection = new ElementCollection(this.elements()[0])
+
+ return collection.render(...args)
+ },
+
+ shallowRender(...args) {
+ let collection = new ElementCollection(this.elements()[0])
+
+ return collection.shallowRender(...args)
},
unmount() {
let inBody = this._mountPoint.parentNode
- , nextContext = this.context.nodes[0].element;
+ , nextContext = this.root.nodes[0].element;
ReactDOM.unmountComponentAtNode(this._mountPoint)
if (inBody)
document.body.removeChild(this._mountPoint)
- this.context = null
+ this.root = null
- return eQuery(nextContext)
+ return ElementCollection(nextContext)
},
dom() {
- return unwrap(this._map($.dom))
+ return unwrap(this._map(utils.findDOMNode))
},
- prop(key, value, cb) {
- if (typeof key === 'string') {
- if (arguments.length === 1)
- return this.nodes[0].element.props[key];
- else
- key = { [key]: value }
+ props: getSetterMethod('props'),
+
+ context: getSetterMethod('context'),
+
+ state(...args) {
+ let value = collectArgs(...args)
+ , callback = args[2] || args[1]
+
+ let node = assertStateful(
+ assertLength(this, 'state').nodes[0]
+ )
+
+ if (args.length === 0 || (typeof value === 'string' && args.length === 1)) {
+ let key = value
+ , state = node.instance.state;
+
+ return key && state ? state[key] : state
}
- // this.node(inst => {
- // ReactUpdateQueue.enqueueSetPropsInternal(inst, props)
- // if (cb)
- // ReactUpdateQueue.enqueueCallbackInternal(inst, cb)
- // })
- },
+ callback = typeof callback === 'function'
+ ? createCallback(this, callback) : undefined
+
+ node.instance.setState(value, callback)
- // state(key) {
- // return this._privateInstances[0].state[key];
- // },
- //
- // context(key) {
- // return this._privateInstances[0].context[key];
- // },
+ return this
+ },
trigger(event, data) {
data = data || {}
@@ -80,7 +119,7 @@ Object.assign($.fn, {
throw new TypeError( '"' + event + '" is not a supported DOM event')
return this.each(component =>
- ReactTestUtils.Simulate[event]($.dom(component), data))
+ ReactTestUtils.Simulate[event](utils.findDOMNode(component), data))
}
})
@@ -90,4 +129,4 @@ function unwrap(arr){
export default $;
-import eQuery from './element';
+import ElementCollection from './element';
diff --git a/src/utils.js b/src/utils.js
index 5b73bd5..241fdf3 100644
--- a/src/utils.js
+++ b/src/utils.js
@@ -5,18 +5,77 @@ import {
getID, getNode, findReactContainerForID
, getReactRootID, _instancesByReactRootID } from 'react/lib/ReactMount';
import ReactTestUtils from'react-addons-test-utils';
+import invariant from 'invariant';
-import closest from 'dom-helpers/query/closest';
-import { match as _match, selector as s } from 'bill';
-
-import { findAll as instanceTraverse } from 'bill/instance-selector';
-import { findAll as elementTraverse } from 'bill/element-selector';
+import { match as _match, selector as s, compile } from 'bill';
+import { createNode, NODE_TYPES } from 'bill/node';
export let isDOMComponent = ReactTestUtils.isDOMComponent;
-export function attachToInstance(inst, publicNodes) {
- inst.length = publicNodes.length;
- publicNodes.forEach((pn, idx) => inst[idx] = pn)
+export function assertLength(collection, method) {
+ invariant(!!collection.length,
+ 'the method `%s()` found no matching elements', method
+ )
+ return collection
+}
+
+export function assertStateful(node) {
+ invariant(
+ node.nodeType === NODE_TYPES.COMPOSITE && !isStatelessComponent(node),
+ 'You are trying to inspect or set state on a stateless component ' +
+ 'such as a DOM node or functional component'
+ );
+
+ return node
+}
+export function assertRoot(collection, msg) {
+ invariant(!collection.root || collection.root === collection,
+ msg || 'You can only preform this action on a "root" element.'
+ )
+ return collection
+}
+
+export function render(element, mount, props, context) {
+ let wrapper, prevWrapper;
+
+ if (isQueryCollection(element)) {
+ let node = element.nodes[0];
+
+ assertRoot(element)
+ context = props
+ props = mount
+ mount = element._mountPoint || mount
+ prevWrapper = element._rootWrapper
+ element = node.element;
+ }
+
+ if (props)
+ element = React.cloneElement(element, props);
+
+ if (context)
+ wrapper = element = wrapElement(element, context, prevWrapper)
+
+ let instance = ReactDOM.render(element, mount);
+
+ if (instance === null) {
+ wrapper = wrapElement(element, null, prevWrapper)
+ instance = ReactDOM.render(wrapper, mount)
+ }
+
+ if (wrapper) {
+ wrapper = wrapper.type;
+ }
+
+ return { wrapper, instance };
+}
+
+export function collectArgs(key, value) {
+ if (typeof key === 'string') {
+ if (arguments.length > 1)
+ key = { [key]: value }
+ }
+
+ return key
}
export function isCompositeComponent(inst) {
@@ -28,13 +87,42 @@ export function isCompositeComponent(inst) {
return inst === null || typeof inst.render === 'function' && typeof inst.setState === 'function';
}
+
+export function isStatelessComponent(node) {
+ let privInst = node.privateInstance
+ return privInst && privInst.getPublicInstance && privInst.getPublicInstance() === null
+}
+
+export function isQueryCollection(collection) {
+ return !!(collection && collection._isQueryCollection)
+}
+
+export function unwrapAndCreateNode(subject) {
+ let node = createNode(subject)
+ , inst = node.instance
+
+ if (inst && inst.__isTspWrapper)
+ return unwrapAndCreateNode(node.children[0])
+
+ return node
+}
+
+export function attachElementsToCollection(collection, elements) {
+ collection.nodes = [].concat(elements).filter(el => !!el).map(unwrapAndCreateNode)
+ collection.length = collection.nodes.length
+
+ getPublicInstances(collection.nodes)
+ .forEach((el, idx)=> collection[idx] = el)
+}
+
export function getPublicInstances(nodes) {
let isInstanceTree = false;
return nodes.map(node => {
let privInst = node.privateInstance;
- if (isInstanceTree && !privInst && React.isValidElement(node.element))
- throw new Error('Polymorphic collections are not allowed')
+ invariant(!(isInstanceTree && !privInst && React.isValidElement(node.element)),
+ 'Polymorphic collections are not allowed'
+ )
isInstanceTree = !!privInst
return getPublicInstance(node)
})
@@ -46,44 +134,42 @@ export function getPublicInstance(node) {
if (!privInst)
inst = node.element
- else if (privInst.getPublicInstance && privInst.getPublicInstance() === null)
+ else if (isStatelessComponent(node))
inst = ReactDOM.findDOMNode(privInst._instance)
- else if (inst && inst.__isStatelessWrapper)
- inst = ReactDOM.findDOMNode(inst)
-
return inst
}
-export function getInternalInstance(component){
- if (!component) return
-
- if (component.getPublicInstance)
- return component
-
- if (component.__isStatelessWrapper)
- return ReactInstanceMap.get(component)._renderedComponent
-
- if (component._reactInternalComponent)
- return component._reactInternalComponent
-
- return ReactInstanceMap.get(component)
-}
-
-export function wrapStateless(Element){
- class StatelessWrapper extends React.Component {
+/**
+ * Wrap an element in order to provide context or an instance in the case of
+ * stateless functional components. `prevWrapper` is necessary for
+ * rerendering a wrapped root component, recreating a wrapper each time breaks
+ * React reconciliation b/c `current.type !== prev.type`. Instead we reuse and
+ * mutate (for childContextTypes) the original component type.
+ */
+export function wrapElement(element, context, prevWrapper) {
+ let TspWrapper = prevWrapper || class extends React.Component {
constructor(){
super()
- this.__isStatelessWrapper = true
+ this.__isTspWrapper = true
}
- render(){
- return Element
+ getChildContext() {
+ return this.props.context
}
+ render() {
+ return this.props.children
+ }
+ }
+
+ if (context) {
+ TspWrapper.childContextTypes = Object.keys(context)
+ .reduce((t, k) => ({ ...t, [k]: React.PropTypes.any }), {})
}
- return
+ return
{element}
}
+
export function getMountPoint(instance){
var id = getID(findDOMNode(instance));
return findReactContainerForID(id);
@@ -98,43 +184,15 @@ export function findDOMNode(component){
? component
: component && component._rootID
? getNode(component._rootID)
- : ReactDOM.findDOMNode(component)
-}
-
-export function getInstanceChildren(inst){
- let publicInst;
-
- if (!inst) return [];
-
- if (inst.getPublicInstance)
- publicInst = inst.getPublicInstance()
-
- if (ReactTestUtils.isDOMComponent(publicInst)) {
- let renderedChildren = inst._renderedChildren || {};
-
- return Object.keys(renderedChildren)
- .map(key => renderedChildren[key])
- .filter(node => typeof node._currentElement !== 'string' )
- }
- else if (isCompositeComponent(publicInst)) {
- let rendered = inst._renderedComponent;
- if (rendered && typeof rendered._currentElement !== 'string')
- return [rendered]
- }
-
- return []
+ : component ? ReactDOM.findDOMNode(component) : null
}
-export function match(selector, tree, includeSelf){
- if (typeof selector === 'function')
- selector = s`${selector}`
+let buildSelector = sel => typeof sel === 'function' ? s`${sel}` : sel
- return _match(selector, tree, includeSelf)
+export function is(selector, tree, includeSelf) {
+ return !!compile(buildSelector(selector))(tree)
}
-export function traverse(tree, test, includeSelf = true){
- if (React.isValidElement(tree))
- return elementTraverse(tree, test, includeSelf)
-
- return instanceTraverse(tree, test, includeSelf)
+export function match(selector, tree, includeSelf) {
+ return _match(buildSelector(selector), tree, includeSelf)
}
diff --git a/test/common.js b/test/common.js
index f2df446..7e07307 100644
--- a/test/common.js
+++ b/test/common.js
@@ -1,6 +1,10 @@
import React from 'react';
+import ReactTestUtils from 'react-addons-test-utils';
import { unmountComponentAtNode, render } from 'react-dom';
import $ from '../src';
+import ElementCollection from '../src/element';
+import InstanceCollection from '../src/instance';
+import commonPrototype from '../src/common';
describe('common', ()=> {
let Stateless = () =>
foo
@@ -14,19 +18,20 @@ describe('common', ()=> {
)
let Example = React.createClass({
- // to test getters
- getInitialState(){ return { count: 1 } },
+ contextTypes: {
+ question: React.PropTypes.string
+ },
+
+ getInitialState(){ return { greeting: 'hello there: ' } },
render() {
return (
-
-
- {'hello there: ' + (this.props.name || 'person')}
-
- foo
-
- {list}
-
-
+
+ {this.state.greeting + (this.props.name || 'person') + (this.context.question || '')}
+
+ foo
+
+ {list}
+
)
}
})
@@ -36,9 +41,50 @@ describe('common', ()=> {
$(
)[0].type.should.equal('div')
})
+ it('collection types should have the same common prototype', ()=>{
+ Object.getPrototypeOf(ElementCollection.prototype)
+ .should.equal(Object.getPrototypeOf(InstanceCollection.prototype))
+ .and.equal($.prototype)
+ .and.equal(commonPrototype)
+ })
+
+ it('$ should have a reference to ElementCollection', ()=>{
+ $.element.should.equal(ElementCollection)
+ })
+
+ it('$ should have a reference to InstanceCollection', ()=>{
+ $.instance.should.equal(InstanceCollection)
+ })
+
+ it('should allow extending both collections by assigning to .fn', ()=>{
+ $.fn.type = function(){
+ return this.nodes[0].element.type
+ }
+
+ $(
).type()
+ .should.equal('div')
+
+ $(ReactTestUtils.renderIntoDocument(
)).type()
+ .should.equal(Example)
+
+ delete $.fn.type
+ })
+
+ it('extending an single collection type should not effect the other.', ()=>{
+ $.element.fn.type = function(){
+ return this.nodes[0].element.type
+ }
+
+ $(
).type()
+ .should.equal('div')
+
+ expect($(ReactTestUtils.renderIntoDocument(
)).type)
+ .to.not.exist
+ })
+
it('should shallow render element', ()=> {
let inst = $(
).shallowRender()
- inst[0].type.should.equal('main')
+ inst[0].type.should.equal(Example)
})
it('should render element', ()=> {
@@ -50,10 +96,11 @@ describe('common', ()=> {
it('should render element using provided mount element', ()=> {
let mount = document.createElement('div')
- let instance = $(
).render(false, mount)
+ let instance = $(
).render(mount)
mount.children[0].classList.contains('test').should.equal(true)
instance._mountPoint.should.equal(mount)
+ document.contains(mount).should.equal(false)
})
it('should render into document', ()=> {
@@ -71,6 +118,8 @@ describe('common', ()=> {
document.querySelectorAll('.test').length.should.equal(1)
instance._mountPoint.should.equal(mount)
+ document.contains(mount).should.equal(true)
+
unmountComponentAtNode(instance._mountPoint)
})
@@ -79,27 +128,31 @@ describe('common', ()=> {
let instanceB = $(instance);
instance.should.not.equal(instanceB)
- expect(instance.context).to.equal(instanceB.context)
+ expect(instance.root).to.equal(instanceB.root)
expect(instance[0]).to.equal(instanceB[0])
})
+
describe(null, ()=> {
let types = {
- shallow: (elements) => $(elements).shallowRender(),
- DOM: (elements) => $(elements).render(),
+ shallow: (elements, context) => $(elements).shallowRender(null, context),
+ DOM: (elements, context) => $(elements).render(context),
}
Object.keys(types).forEach(type => {
- let render = types[type]
- , isDomTest = type === 'DOM';
-
+ let render = types[type];
describe(type + ' rendering', function(){
+ it('should maintain root elements after render', ()=>{
+ render(
).is(Example)
+ })
+
it('should work with Stateless components as root', ()=>{
let inst = render(
)
inst[0].should.exist
+ inst.is(Stateless).should.equal(true)
})
it('.get()', ()=> {
@@ -119,6 +172,14 @@ describe('common', ()=> {
expect(count).to.equal(inst.length);
})
+ it('registerPseudo() should allow pseudo extensions', ()=> {
+ $.registerPseudo('foo', (node, test) => {
+ return $(node).is('.foo')
+ })
+
+ render(
).find(':foo').length.should.equal(3)
+ })
+
it('.tap()', ()=> {
let spy = sinon.spy(function (n) { expect(n).to.exist.and.equal(this) })
, inst = render(
);
@@ -136,20 +197,116 @@ describe('common', ()=> {
.tap(node => node.length.should.equal(3))
})
- // it('should get props', ()=>{
- // $(
)
- // .prop('name').should.equal('rikki-tikki-tavi')
- //
- // render(
)
- // .prop('name').should.equal('rikki-tikki-tavi')
- // })
- //
- // it('should get state', ()=>{
- // let inst = $(
)
- //
- // inst.state().should.eql({ count: 1 });
- // inst.shallowRender().state('count').should.equal(1)
- // })
+ it('props() should get props', ()=> {
+ // -- unrendered elements ---
+ $(
)
+ .props('name').should.equal('rikki-tikki-tavi')
+ $(
)
+ .props().name.should.equal('rikki-tikki-tavi')
+
+ // -- rendered versions ---
+ render(
)
+ .props('name').should.equal('rikki-tikki-tavi')
+
+ render(
)
+ .props().name.should.equal('rikki-tikki-tavi')
+ })
+
+ it('props() should change props', ()=> {
+
+ render(
)
+ .tap(inst => {
+ inst.first('div > :text').unwrap()
+ .should.equal('hello there: rikki-tikki-tavi')
+ })
+ .props('name', 'Nagaina')
+ .tap(inst =>
+ inst.first('div > :text').unwrap()
+ .should.equal('hello there: Nagaina'))
+ .props({ name: 'Nag' })
+ .tap(inst =>
+ inst.first('div > :text').unwrap()
+ .should.equal('hello there: Nag'))
+
+ })
+
+ it('props() should throw on empty collecitons', ()=> {
+ ;(() => render(
).find('article').props({ name: 'Steven' }))
+ .should.throw('the method `props()` found no matching elements')
+
+ ;(() => render(
).find('article').props())
+ .should.throw('the method `props()` found no matching elements')
+ })
+
+ it('state() should get component state', ()=>{
+ let inst = render(
)
+
+ inst.state().should.eql({ greeting: 'hello there: ' });
+ inst.state('greeting').should.equal('hello there: ')
+ })
+
+ it('state() should change component state', done => {
+
+ render(
)
+ .tap(inst => {
+ inst.first('div > :text').unwrap()
+ .should.equal('hello there: John')
+ })
+ .state('greeting', 'yo yo! ')
+ .tap(inst =>
+ inst.first('div > :text').unwrap()
+ .should.equal('yo yo! John'))
+ .state({ greeting: 'huzzah good sir: ' }, inst => {
+ inst.first('div > :text').unwrap()
+ .should.equal('huzzah good sir: John')
+ done()
+ })
+ })
+
+ it('state() should throw on empty collections', ()=> {
+ ;(() => render(
).find('article').state({ name: 'Steven' }))
+ .should.throw('the method `state()` found no matching elements')
+
+ ;(() => render(
).find('article').state())
+ .should.throw('the method `state()` found no matching elements')
+ })
+
+ it('context() should get context', ()=> {
+ let context = { question: ', who dis?'};
+
+ render(
, context)
+ .context('question').should.equal(context.question)
+
+ render(
, context)
+ .context().should.eql(context)
+ })
+
+ it('context() should change context', ()=> {
+
+ render(
, { question: ', who dis?'})
+ .tap(inst => {
+ inst.first('div > :text').unwrap()
+ .should.equal('hello there: person, who dis?')
+ })
+ .context('question', ', how are you?')
+ .tap(inst =>
+ inst.first('div > :text').unwrap()
+ .should.equal('hello there: person, how are you?'))
+ .context({ question: ', whats the haps?' })
+ .tap(inst =>
+ inst.first('div > :text').unwrap()
+ .should.equal('hello there: person, whats the haps?'))
+
+ })
+
+ it('context() should throw on empty collections', ()=> {
+ ;(() => render(
).find('article').context({ name: 'Steven' }))
+ .should.throw('the method `context()` found no matching elements')
+
+ ;(() => render(
).find('article').context())
+ .should.throw('the method `context()` found no matching elements')
+ })
+
it('.find() by tag or classname', ()=> {
render(
).find('li').length.should.equal(3)
@@ -165,21 +322,21 @@ describe('common', ()=> {
})
it('.find() by :dom', ()=>{
- render(
).find('main :dom').length.should.equal(6);
+ render(
).find('div :dom').length.should.equal(5);
})
it('.find() should allow chaining ', (done)=> {
render(
)
.find('ul.foo, Stateless')
- .tap(coll => {
- expect(coll.length).to.equal(2)
- coll.filter('ul').length.should.equal(1)
- coll.filter(Stateless).length.should.equal(1)
+ .tap(inst => {
+ expect(inst.length).to.equal(2)
+ inst.filter('ul').length.should.equal(1)
+ inst.filter(Stateless).length.should.equal(1)
})
.find('li')
- .tap(coll => {
- expect(coll.length).to.equal(3)
- coll.get().every(node => $(node).is('li'))
+ .tap(inst => {
+ expect(inst.length).to.equal(3)
+ inst.get().every(node => $(node).is('li'))
done()
})
})
@@ -201,7 +358,6 @@ describe('common', ()=> {
instance.filter().should.equal(instance)
})
-
it('.children()', ()=> {
render(
)
.find('ul')
@@ -216,7 +372,47 @@ describe('common', ()=> {
.length.should.equal(2)
})
- it('.text() content', ()=>{
+ it('.parent()', ()=> {
+ render(
)
+ .find('li')
+ .parent()
+ .tap(inst =>
+ inst.elements()[0].type.should.equal('ul')
+ )
+ .length.should.equal(1)
+ })
+
+ it('.parents()', ()=> {
+ render(
)
+ .find('li')
+ .parents()
+ .tap(inst =>
+ inst.elements().map(e => e.type).should.eql(['ul', 'div', Example])
+ )
+ .length.should.equal(3)
+ })
+
+ it('.parents() with a selector', ()=> {
+ render(
)
+ .find('li')
+ .parents(':dom')
+ .tap(inst =>
+ inst.elements().map(e => e.type).should.eql(['ul', 'div'])
+ )
+ .length.should.equal(2)
+ })
+
+ it('.closest()', ()=> {
+ render(
)
+ .find('li')
+ .closest('div')
+ .tap(inst =>
+ inst.elements()[0].type.should.equal('div')
+ )
+ .length.should.equal(1)
+ })
+
+ it('.text()', ()=>{
render(
)
.find(Stateless)
.text().should.equal('foo')
@@ -226,6 +422,22 @@ describe('common', ()=> {
.text().should.equal('item 1item 2item 3')
})
+ it(':contains', ()=>{
+ render(
)
+ .find(':contains(foo)')
+ .length.should.equal(3)
+ })
+
+ it(':textContent', ()=>{
+ render(
)
+ .find('strong:textContent')
+ .length.should.equal(1)
+
+ render(
)
+ .find(':textContent(foo)')
+ .length.should.equal(1)
+ })
+
it('.first()', ()=> {
render(
)
diff --git a/test/dom.js b/test/dom.js
index b6b9642..bef1c4b 100644
--- a/test/dom.js
+++ b/test/dom.js
@@ -3,7 +3,7 @@ import { unmountComponentAtNode, render } from 'react-dom';
import $ from '../src';
import * as utils from '../src/utils';
-describe('DOM rendering', ()=> {
+describe('DOM rendering specific', ()=> {
let Stateless = props =>
{props.children}
let List = class extends React.Component {
render(){
@@ -55,7 +55,7 @@ describe('DOM rendering', ()=> {
let next = instance.unmount()
document.querySelectorAll('.test').length.should.equal(0)
- expect(instance.context).to.not.exist
+ expect(instance.root).to.not.exist
expect(mount.parentNode).to.not.exist
expect(next[0].type).to.equal('div')
@@ -72,20 +72,16 @@ describe('DOM rendering', ()=> {
$.dom(div).should.equal(div)
})
- it('should `get()` underlying element', ()=> {
- let instance = $(
)
+ it('should throw when retrieving state from a stateless node', ()=> {
+ let msg = 'You are trying to inspect or set state on a stateless component ' +
+ 'such as a DOM node or functional component';
- instance.get()[0].should.equal(instance[0])
+ ;(() => $(
).render().find('div').state())
+ .should.throw(msg)
+
+ ;(() => $(
).render().find(Stateless).state())
+ .should.throw(msg)
})
-//
-// it('should set props', ()=> {
-// let instance = $(
)
-//
-// instance.setProps({ min: 5 })
-//
-// instance[0].props.min.should.equal(5)
-// })
-//
it('should trigger event', ()=> {
let clickSpy = sinon.spy();
diff --git a/test/shallow.js b/test/shallow.js
index 894344c..93e0f1a 100644
--- a/test/shallow.js
+++ b/test/shallow.js
@@ -2,7 +2,7 @@ import React, { cloneElement } from 'react';
import $ from '../src/element';
-describe.only('shallow rendering', ()=> {
+describe('shallow rendering specific', ()=> {
let counterRef, StatefulExample, updateSpy;
beforeEach(() => {
@@ -18,7 +18,10 @@ describe.only('shallow rendering', ()=> {
render() {
return (
-
+ {this.props.name || 'folk'}
+
+ {this.state.count}
+
)
}
@@ -29,65 +32,91 @@ describe.only('shallow rendering', ()=> {
it('should not try to render primitives', ()=>{
let el =
- $(el).shallowRender().context[0].should.equal(el)
+ $(el).shallowRender().unwrap().should.equal(el)
})
it('should render Composite Components', ()=>{
let el =
- , Element = ()=> el;
+ , Example = ()=> el
+ , element =
;
- $(
).shallowRender().unwrap().should.equal(el)
+ let inst = $(element).shallowRender();
+ inst.unwrap().should.not.equal(element)
+ inst.unwrap().type.should.equal(Example)
})
it('should filter out invalid Elements', ()=>{
- let instance = $(
-
- { false }
- { null}
- {'text'}
- - hi 1
- - hi 2
- - hi 3
-
- )
-
- instance.children().length.should.equal(3)
- instance.shallowRender().length.should.equal(3)
+ let instance = $([
+ false,
+ null,
+ 'text',
+
hi 1,
+
hi 2,
+
hi 3,
+ ])
+
+ // breaking? 3 -> 4
+ instance.length.should.equal(4)
})
- it('should maintain state between renders', ()=>{
- let counter = $(
)
- counter.shallowRender().state('count').should.equal(0)
- counterRef.increment()
- counter.shallowRender().state('count').should.equal(1)
+ it('prop() should throw when updating a non-root rendered collection', ()=> {
+ ;(() => $(
).shallowRender().find('span').props({ name: 'Steven' }))
+ .should.throw(
+ 'changing the props on a shallow rendered child is an anti-pattern, ' +
+ 'since the elements props will be overridden by its parent in the next update() of the root element'
+ )
})
- it('should update', ()=> {
+ it('should update when a root update occurs', ()=> {
let counter = $(
).shallowRender()
counter.state('count').should.equal(0)
+ counter.find('span').text().should.equal('0')
+
counterRef.increment()
+
updateSpy.should.have.been.calledOnce
counter.state('count').should.equal(1)
+ counter.find('span').text().should.equal('1')
})
- it('should throw when updating none root elements', ()=> {
+ it('should throw when updating non-root elements', ()=> {
let counter = $(
).shallowRender()
;(() => counter.find('span').update())
.should.throw('You can only preform this action on a "root" element.')
})
- it('should update root collections', ()=> {
+ it('should update root when props or state are changed', ()=> {
+ let inst = $(
).shallowRender();
+
+ inst
+ .props({ name: 'The boy' })
+ .tap(inst =>
+ inst.find('div > :first-child').unwrap().should.equal('The boy'))
+ .state({ count: 40 })
+ .find('span').text().should.equal('40')
+
+ updateSpy.should.have.been.calledOnce
+ })
+
+ it('trigger() should update root collections', ()=> {
let inst = $(
).shallowRender();
inst
.find('span')
.trigger('click')
- .context
- .state('count').should.equal(1)
+ .root
+ .tap(inst =>
+ inst.find('span').text().should.equal('1'))
+ .state('count').should.equal(
+ inst.state('count')
+ )
updateSpy.should.have.been.calledOnce
})
+
+
+
})