From 386ef82141c84accc10266458bdb60b76c637021 Mon Sep 17 00:00:00 2001 From: jquense Date: Mon, 28 Sep 2015 12:43:16 -0400 Subject: [PATCH] [added] much better selector support --- README.md | 42 +++++-- lib/README.md | 106 +++++++++++++++++ lib/package.json | 30 +++++ lib/shallow.js | 155 ++++++++++++++++++++++++ package.json | 5 +- src/index.js | 248 ++++++++++++++++++--------------------- src/instance-selector.js | 85 ++++++++++++++ src/shallow.js | 16 ++- src/utils.js | 68 ----------- test/dom.js | 46 +++++++- test/shallow.js | 2 - 11 files changed, 577 insertions(+), 226 deletions(-) create mode 100644 lib/README.md create mode 100644 lib/package.json create mode 100644 lib/shallow.js create mode 100644 src/instance-selector.js diff --git a/README.md b/README.md index b4728b3..24620b9 100644 --- a/README.md +++ b/README.md @@ -3,20 +3,42 @@ React TestUtils utils A simple jquery like api wrapper for the React TestUtils to make them a bit friendlier to use. -Updates for react 0.14, works with Stateless Components and you can scry and filter on DOM components +Updated for react 0.14; works seamlessly with Stateless Components and you can find and filter on DOM components as well. +### using selectors + +The selector syntax is subset of normal css selectors. You can query by tag: `'div > li'` or +by `className` with `'.my-class'`. Attribute selectors work on props: `'[show=true]'` or `'[name="my-input"]'`. +You can even use the `has()` pseudo selector for selecting parents. + +Unlike normal css selectors though, React Elements often have prop values, and element types that are not serializable +to a nice string. What if you needed to select a `MyList` component by its "tag" or wanted to get all elements with +a `date` prop equal to today? + +To write selectors for these values we use an es6 tagged template string! Both the DOM and shallow rendering +imports expose a `$.selector` (also aliased as `$.s`) for writing complex selectors like so: + +``` +//select all ``s that are children of divs +$.s`div > ${List}` + +//select components with `start` props equal to `min` +let min = 10 +$.s`[start=${10}]` +``` + ### Traditional DOM rendering ```js var $r = require('react-testutil-query') var elements = ( - - -
- - + + +
+ + ) var $root = $r(elements) // renders and returns a wrapped instance @@ -27,8 +49,13 @@ $r($root[0]) // | //-- simple selector syntax -- $root.find('.fun-div') //class $root.find('div') // tag name + $root.find(MyInput) // component type +// complex selectors +$root.find('div.foo > span:has(div.bar)') +$root.find($.s`${MyList} > li.foo`) + $root.find(':dom') // all dom nodes $root.find(':composite') // all non DOM components @@ -61,7 +88,7 @@ $root.find(MyInput).trigger('change', { target: { value: 6 }}) // triggers onCha ### Shallow rendering -We can use an even more powerful selector syntax will shallow rendering +To query shallow rendered Components use the `'react-testutil-query/shallow'` import ```js var $ = require('react-testutil-query/shallow'); @@ -89,7 +116,6 @@ $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 diff --git a/lib/README.md b/lib/README.md new file mode 100644 index 0000000..b4728b3 --- /dev/null +++ b/lib/README.md @@ -0,0 +1,106 @@ +React TestUtils utils +======== + +A simple jquery like api wrapper for the React TestUtils to make them a bit friendlier to use. + +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') + +var elements = ( + + +
+ + + ) + +var $root = $r(elements) // renders and returns a wrapped instance + +$r($root) // | calling it again won't rerender or rewrap +$r($root[0]) // | + +//-- simple selector syntax -- +$root.find('.fun-div') //class +$root.find('div') // tag name +$root.find(MyInput) // component type + +$root.find(':dom') // all dom nodes +$root.find(':composite') // all non DOM components + +$root.find() // everything! all descendents + +//-- like jquery you get an arraylike thing +$root.find(MyInput).length // 2 + +$root.find(MyInput).each( (component, idx) => /*do something */) + +// use the index or `get()` to unwrap the collection into a single component or real array +$root.find('.fun-div')[0] + + +$root.find(MyInput).first() +$root.find(MyInput).last() + +// you can still get the implicit asserts for finding single components +$root.find('.fun-div').only() // throws a TypeError .length === 0 +$root.single('.fun-div') // is the same thing + + +// -- getting DOM nodes +$root.single('.fun-div').dom() // returns the single DOM node +$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 =>
    {props.children}
+ +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/lib/package.json b/lib/package.json new file mode 100644 index 0000000..81e4ca6 --- /dev/null +++ b/lib/package.json @@ -0,0 +1,30 @@ +{ + "name": "react-testutil-query", + "version": "2.2.0", + "description": "small wrapper around react test utils so I don't have to write long method names", + "main": "index.js", + "repository": { + "type": "git", + "url": "https://github.com/jquense/react-testutil-query" + }, + "keywords": [ + "react", + "test", + "query" + ], + "author": "jquense", + "license": "MIT", + "bugs": { + "url": "https://github.com/jquense/react-testutil-query/issues" + }, + "homepage": "https://github.com/jquense/react-testutil-query", + "peerDependencies": { + "react": ">=0.14.0-rc1", + "react-dom": ">=0.14.0-rc1" + }, + "dependencies": { + "bill": "^1.0.4", + "dom-helpers": "^2.4.0", + "react-addons-test-utils": "^0.14.0-rc1" + } +} diff --git a/lib/shallow.js b/lib/shallow.js new file mode 100644 index 0000000..a32f505 --- /dev/null +++ b/lib/shallow.js @@ -0,0 +1,155 @@ +'use strict'; + +exports.__esModule = true; + +var _templateObject = _taggedTemplateLiteralLoose(['', ''], ['', '']); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } + +function _taggedTemplateLiteralLoose(strings, raw) { strings.raw = raw; return strings; } + +var _react = require('react'); + +var _react2 = _interopRequireDefault(_react); + +var _reactAddonsTestUtils = require('react-addons-test-utils'); + +var _reactAddonsTestUtils2 = _interopRequireDefault(_reactAddonsTestUtils); + +var _bill = require('bill'); + +var isRtq = function isRtq(item) { + return item && item.__isRTQ; +}; + +rtq.s = rtq.selector = _bill.selector; + +exports['default'] = rtq; + +function match(selector, tree, includeSelf) { + if (typeof selector === 'function') selector = _bill.selector(_templateObject, selector); + + return _bill.match(selector, tree, includeSelf); +} + +function render(element) { + var root = element; + + if (!(typeof root.type === 'string' && root.type.toLowerCase() === root.type)) { + var renderer = _reactAddonsTestUtils2['default'].createRenderer(); + renderer.render(element); + root = renderer.getRenderOutput(); + } + + return { + root: root, + setProps: function setProps(props) { + return render(_react.cloneElement(element, props)); + } + }; +} + +function rtq(element) { + var context, rerender; + + if (_reactAddonsTestUtils2['default'].isElement(element)) { + var _render = render(element); + + var root = _render.root; + var setProps = _render.setProps; + + element = context = root; + rerender = setProps; + } else if (isRtq(element)) { + context = element.root; + element = element.get(); + //rerender = element._rerender + } + + return new ShallowCollection(element, context, rerender); +} + +var ShallowCollection = (function () { + function ShallowCollection(elements, root, rerender) { + _classCallCheck(this, ShallowCollection); + + elements = [].concat(elements).filter(function (el) { + return _react.isValidElement(el); + }); + + var idx = -1; + + while (++idx < elements.length) this[idx] = elements[idx]; + + this._rerender = rerender; + this.length = elements.length; + this.root = root; + } + + ShallowCollection.prototype.setProps = function setProps(props) { + this._rerender && this._rerender(props); + return this; + }; + + ShallowCollection.prototype.each = function each(cb) { + var idx = -1, + len = this.length; + while (++idx < len) cb(this[idx], idx, this); + return this; + }; + + ShallowCollection.prototype.get = function get() { + var result = []; + this.each(function (el) { + return result.push(el); + }); + return result; + }; + + ShallowCollection.prototype.reduce = function reduce(cb, initial) { + return new ShallowCollection([].reduce.call(this, cb, initial), this.root); + }; + + ShallowCollection.prototype.map = function map(cb) { + var result = []; + this.each(function (v, i, l) { + return result.push(cb(v, i, l)); + }); + + return new ShallowCollection(result, this.root); + }; + + ShallowCollection.prototype.find = function find(selector) { + return this.reduce(function (result, element) { + return result.concat(match(selector, element)); + }, []); + }; + + ShallowCollection.prototype.children = function children(selector) { + return this.reduce(function (result, element) { + return result.concat(element.props.children || []); + }, []).filter(selector); + }; + + ShallowCollection.prototype.filter = function filter(selector) { + if (!selector) return this; + + if (typeof selector === 'function') return new ShallowCollection([].filter.call(this, selector), this.root); + + var matches = match(selector, this.root); + + return new ShallowCollection([].filter.call(this, function (el) { + return matches.indexOf(el) !== -1; + }), this.root); + }; + + ShallowCollection.prototype.is = function is(selector) { + return this.filter(selector).length === this.length; + }; + + return ShallowCollection; +})(); + +module.exports = exports['default']; \ No newline at end of file diff --git a/package.json b/package.json index 7bba334..dbac6aa 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,7 @@ "scripts": { "test": "karma start --single-run", "tdd": "karma start", - "build": "babel src --out-dir lib", + "build": "babel src --out-dir lib && cpy ./README.md ./lib", "release": "release" }, "repository": { @@ -32,6 +32,7 @@ "babel-core": "^5.8.25", "babel-loader": "^5.3.2", "chai": "^3.3.0", + "cpy": "^3.4.1", "karma": "^0.13.10", "karma-chai": "^0.1.0", "karma-chrome-launcher": "^0.2.0", @@ -49,7 +50,7 @@ "webpack": "^1.12.2" }, "dependencies": { - "bill": "^1.0.4", + "bill": "^1.1.0", "dom-helpers": "^2.4.0", "react-addons-test-utils": "^0.14.0-rc1" }, diff --git a/src/index.js b/src/index.js index 44dca98..ae076f9 100644 --- a/src/index.js +++ b/src/index.js @@ -1,37 +1,38 @@ - -var React = require('react'); -var ReactDOM = require('react-dom') -var ReactInstanceMap = require('react/lib/ReactInstanceMap') -var utils = require('react-addons-test-utils') -var findAll = require('./utils') -var hasClass = require('dom-helpers/class/hasClass') +import React from 'react'; +import ReactDOM from 'react-dom'; +import ReactInstanceMap from 'react/lib/ReactInstanceMap'; +import utils from 'react-addons-test-utils'; +import { match as _match, selector as sel } from './instance-selector'; +import hasClass from 'dom-helpers/class/hasClass'; var $r = module.exports = rtq -let isRtq = item => item && item.__isRTQ +let isRtq = item => item && item._isRTQ + rtq.react = React; +rtq.s = rtq.selector = sel; function rtq(element, mount, renderIntoDocument = (mount === true)) { var context; - if (!mount || mount === true) { + if (!mount || mount === true) mount = document.createElement('div') - } - if (utils.isElement(element)) { - context = mount - element = render(element, mount, renderIntoDocument) - } - else if (utils.isDOMComponent(element) || utils.isCompositeComponent(element)) + if (utils.isElement(element)) + context = element = render(element, mount, renderIntoDocument) + else if (utils.isDOMComponent(element) || utils.isCompositeComponent(element)){ context = element + mount = rtq.dom(element).parentNode + } else if (isRtq(element)) { + mount = element._mountPoint context = element.context element = element.get(); } else throw new TypeError('Wrong type: must either be ReactElement or a Component Instance') - return new ComponentCollection(element, context) + return new ComponentCollection(element, context, mount) } function render(element, mountPoint, renderIntoDocument = false) { @@ -57,162 +58,165 @@ function render(element, mountPoint, renderIntoDocument = false) { return instance; } -function ComponentCollection(_components, context, selector){ - var components = _components == null ? [] : [].concat(_components) - , idx = -1, len = components.length +function match(selector, tree, includeSelf){ + if (typeof selector === 'function') + selector = sel`${selector}` - this._privateInstances = Object.create(null) + return _match(selector, tree, includeSelf) +} - while( ++idx < len ) { - var component = components[idx] +rtq.dom = function(component){ + return component instanceof HTMLElement ? component : ReactDOM.findDOMNode(component) +} - // if this is a private instance, get the public one - if (component.getPublicInstance) { - this._privateInstances[idx] = component - component = component.getPublicInstance(); - //stateless - if (component === null) - component = ReactDOM.findDOMNode(this._privateInstances[idx]._instance) - } - // if this a root Stateless component - else if (component && component.__isRTQstatelessWrapper){ - let wrapperInstance = ReactInstanceMap.get(component); - this._privateInstances[idx] = wrapperInstance._renderedComponent; - component = ReactDOM.findDOMNode(component) - } - else { - this._privateInstances[idx] = ReactInstanceMap.get(component) || component._reactInternalComponent - } +class ComponentCollection { - this[idx] = component - } + constructor(_components, context, mountPoint, selector){ + var components = _components == null ? [] : [].concat(_components) + , idx = -1, len = components.length - this.length = len - this.context = context - this.selector = selector - this.__isRTQ = true -} + this._privateInstances = Object.create(null) + while (++idx < len) { + var component = components[idx] -rtq.dom = function(component){ - return component instanceof HTMLElement ? component : ReactDOM.findDOMNode(component) -} + if (component.getPublicInstance) { + this._privateInstances[idx] = component + component = component.getPublicInstance(); -ComponentCollection.prototype = { + //stateless + if (component === null) + component = ReactDOM.findDOMNode(this._privateInstances[idx]._instance) + } + // if this a root Stateless component + else if (component && component.__isRTQstatelessWrapper){ + let wrapperInstance = ReactInstanceMap.get(component); + this._privateInstances[idx] = wrapperInstance._renderedComponent; + component = ReactDOM.findDOMNode(component) + } + else { + this._privateInstances[idx] = ReactInstanceMap.get(component) || component._reactInternalComponent + } + + this[idx] = component + } - constructor: ComponentCollection, + this.length = len + this.context = context + this.selector = selector + this._mountPoint = mountPoint + this._isRTQ = true + } + + _root(){ + return this.context._reactInternalComponent || this.context + } unmount(){ let inBody = !!this.context.parentNode; + ReactDOM.unmountComponentAtNode(this._mountPoint) - ReactDOM.unmountComponentAtNode(this.context) if (inBody) - document.body.removeChild(this.context) + document.body.removeChild(this._mountPoint) + this.context = null - }, + } setProps(newProps){ return this.mapInPlace(element => element.renderWithProps(newProps)) - }, + } each(cb, thisArg) { var idx = -1, len = this.length; while( ++idx < len ) cb.call(thisArg, this[idx], idx, this) return this - }, + } mapInPlace(cb, thisArg) { return this.each((el, idx, list)=> this[idx] = cb(el, idx, list)) - }, + } map(cb, thisArg) { var idx = -1, len = this.length, result = [] - while( ++idx < len ) result.push(cb.call(thisArg, this[idx], idx, this)) + while (++idx < len) result.push(cb.call(thisArg, this[idx], idx, this)) return result - }, + } + + _reduce(cb, initial){ + return new ComponentCollection( + this._getInstances().reduce(cb, initial) + , this.context + , this._mountPount + , this.selector + ) + } + + reduce(cb, initial){ + return new ComponentCollection( + [].reduce.call(this, cb, initial) + , this.context + , this._mountPount + , this.selector + ) + } _getInstances(){ - return this.map((component, idx) => { + return this.map((_, idx) => { return this._privateInstances[idx] }) - }, + } get(){ return unwrap(this.map(component => component)) - }, + } dom() { return unwrap(this.map(rtq.dom)) - }, + } find(selector){ - let result = [] + return this._reduce((result, instance) => { + return result.concat(match(selector, instance, false)) + }, []) + } - this.each((component, idx) => { - component = component !== null - ? this._privateInstances[idx] || component - : component + filter(selector) { + if (!selector) return this - result = result.concat(_find(component, selector)) - }) + let matches = match(selector, this._root()); - return new ComponentCollection(result, this.context, selector) - }, + return this._reduce((result, el) => { + return matches.indexOf(el) !== -1 ? result.concat(el) : result + }, []) + } only(){ if (this.length !== 1) throw Error('`' + this.selector +'` found: ' + this.length + ' not 1 ') return this.first() - }, + } single(selector) { return selector ? this.find(selector).only() : this.only() - }, + } first(selector){ return selector ? this.find(selector).first() - : new ComponentCollection(this[0], this.context, this.selector) - }, + : new ComponentCollection(this[0], this.context, this._mountPount, this.selector) + } last(selector){ return selector ? this.find(selector).last() - : new ComponentCollection(this[this.length - 1], this.context, this.selector) - }, - - is(selector){ - let instances = this._getInstances(); - let getPublicInst = inst => inst.getPublicInstance ? inst.getPublicInstance() : inst; - - if( typeof selector === 'function') { - return instances.every(inst => { - let publicInst = getPublicInst(inst); - return findAll.isCompositeComponent(publicInst) - && inst._currentElement.type === selector - }) - } + : new ComponentCollection(this[this.length - 1], this.context, this._mountPount, this.selector) + } - else if( selector === ':dom' ) - return instances.every(inst => { - return utils.isDOMComponent(getPublicInst(inst)) - }) - - else if( selector === ':composite' ) - return instances.every(inst => { - return findAll.isCompositeComponent(getPublicInst(inst)) - }) - else if ( selector[0] === '.' ) - return instances.every(inst => { - return hasClass(rtq.dom(getPublicInst(inst)), selector.substr(1)) - }) - else - return instances.every(inst => { - return rtq.dom(getPublicInst(inst)).tagName.toUpperCase() === selector.toUpperCase() - }) - }, + is(selector) { + return this.filter(selector).length === this.length + } trigger(event, data){ data = data || {} @@ -228,34 +232,6 @@ ComponentCollection.prototype = { } } -function _find(context, selector){ - var components; - - if( typeof selector === 'function') { - components = findAll.componentsByType(context, selector) - selector = selector.name || '<>' - } - else if ( !selector ) - components = findAll.findAllInRenderedTree(context, function(){ return true }) - - else if( selector === ':dom' ) - components = findAll.findAllInRenderedTree(context, function(item){ - return utils.isDOMComponent(item) - }) - - else if( selector === ':composite' ) - components = findAll.findAllInRenderedTree(context, function(item){ - return !utils.isDOMComponent(item) - }) - - else if ( selector[0] === '.' ) - components = findAll.componentsByClassName(context, selector.substr(1)) - - else - components = findAll.componentsByTagName(context, selector) - - return components || [] -} function unwrap(arr){ return arr && arr.length === 1 ? arr[0] : arr diff --git a/src/instance-selector.js b/src/instance-selector.js new file mode 100644 index 0000000..9bf5ecd --- /dev/null +++ b/src/instance-selector.js @@ -0,0 +1,85 @@ +var React = require('react'); +var ReactDOM = require('react-dom') +var ReactInstanceMap = require('react/lib/ReactInstanceMap'); +var ReactTestUtils = require('react-addons-test-utils') + +import { create as createCompiler, parse } from 'bill/compiler'; +import { isCompositeComponent } from './utils'; + +let compiler = createCompiler() + +compiler.registerPseudo('has', function(compiledSelector) { + return (root, inst) => { + let matches = findAll(inst, compiledSelector) + return !!matches.length + } +}) + +compiler.registerPseudo('dom', function() { + return (root, inst) => { + return typeof root.type === 'string' && root.type.toLowerCase() === root.type + } +}) + +compiler.registerPseudo('composite', function() { + return (root, inst) => { + return typeof root.type === 'function' + } +}) + +compiler.registerNesting('any', test => anyParent.bind(null, test)) + +compiler.registerNesting('>', test => directParent.bind(null, test)) + + +function findAll(inst, test, getParent = ()=> ({ parent: null }), excludeSelf = true) { + let found = []; + + if (!inst || !inst.getPublicInstance) + return found; + + let publicInst = inst.getPublicInstance() + , element = inst._currentElement + , parent = ()=> ({ parent: element, getParent }); + + if (!excludeSelf && test(element, inst, getParent)) + found = found.concat(inst) + + if (ReactTestUtils.isDOMComponent(publicInst)) { + let renderedChildren = inst._renderedChildren || {}; + + Object.keys(renderedChildren).forEach(key => { + found = found.concat( + findAll(renderedChildren[key], test, parent, false) + ); + }) + } + else if (isCompositeComponent(publicInst)) { + found = found.concat(findAll(inst._renderedComponent, test, parent, false)); + } + + return found; +} + +function anyParent(test, element, inst, parentNode){ + do { + var { getParent, parent } = parentNode(); + element = parent + parentNode = getParent + } while(element && !test(element, test, getParent)) + + return !!element +} + +function directParent(test, element, inst, parentNode) { + element = parentNode().parent + return !!(element && test(element, parentNode().getParent)) +} + +export function match(selector, inst, includeSelf = true) { + let tree = inst.getPublicInstance ? inst : ReactInstanceMap.get(inst) + + return findAll(tree, compiler.compile(selector), undefined, !includeSelf) +} + +export let { compile, compileRule, selector } = compiler diff --git a/src/shallow.js b/src/shallow.js index 0888373..5823e00 100644 --- a/src/shallow.js +++ b/src/shallow.js @@ -43,7 +43,6 @@ function rtq(element) { else if (isRtq(element)) { context = element.root element = element.get(); - //rerender = element._rerender } return new ShallowCollection(element, context, rerender) @@ -110,9 +109,6 @@ class ShallowCollection { 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 => { @@ -120,6 +116,18 @@ class ShallowCollection { }), this.root) } + first(selector){ + return selector + ? this.find(selector).first() + : new ShallowCollection(this[0], this.root) + } + + last(selector){ + return selector + ? this.find(selector).last() + : new ComponentCollection(this[this.length - 1], this.root) + } + is(selector) { return this.filter(selector).length === this.length } diff --git a/src/utils.js b/src/utils.js index 594497f..a6a25da 100644 --- a/src/utils.js +++ b/src/utils.js @@ -1,35 +1,5 @@ -var React = require('react'); -var ReactDOM = require('react-dom') -var ReactInstanceMap = require('react/lib/ReactInstanceMap'); var ReactTestUtils = require('react-addons-test-utils') -export function _findAllInRenderedTree(inst, test) { - if (!inst || !inst.getPublicInstance) - return []; - - var publicInst = inst.getPublicInstance(); - - var ret = []; - - if (test(publicInst, inst)){ - ret = ret.concat(inst) - } - - if (ReactTestUtils.isDOMComponent(publicInst)) { - var key, renderedChildren = inst._renderedChildren - - for (key in renderedChildren) { - if (!renderedChildren.hasOwnProperty(key)) continue; - ret = ret.concat(_findAllInRenderedTree(renderedChildren[key], test)); - } - } - else if (isCompositeComponent(publicInst)) { - ret = ret.concat(_findAllInRenderedTree(inst._renderedComponent, test)); - } - - return ret; -} - export function isCompositeComponent(inst) { if (ReactTestUtils.isDOMComponent(inst)) { // Accessing inst.setState warns; just return false as that'll be what @@ -38,41 +8,3 @@ export function isCompositeComponent(inst) { } return inst === null || typeof inst.render === 'function' && typeof inst.setState === 'function'; } - -export function isCompositeComponentWithType(inst, type) { - if (!isCompositeComponent(inst)) return false; - var internalInstance = ReactInstanceMap.get(inst); - var constructor = internalInstance._currentElement.type; - return constructor === type; -} - -export function findAllInRenderedTree(inst, test){ - if (!inst) return []; - return _findAllInRenderedTree( - inst.getPublicInstance ? inst : ReactInstanceMap.get(inst), test); -} - -export function componentsByTagName(root, tagName) { - return findAllInRenderedTree(root, (inst) => { - return ReactTestUtils.isDOMComponent(inst) - && inst.tagName.toUpperCase() === tagName.toUpperCase(); - }); -} - -export function componentsByClassName(root, className) { - return findAllInRenderedTree(root, (inst) => { - if (ReactTestUtils.isDOMComponent(inst)) { - var instClassName = ReactDOM.findDOMNode(inst).className; - return instClassName && ('' + instClassName).split(/\s+/).indexOf(className) !== -1; - } - }) -} - -export function componentsByType(root, componentType) { - return findAllInRenderedTree(root, (inst, privateInst) => { - if (!isCompositeComponent(inst)) return false; - - var constructor = privateInst._currentElement.type; - return constructor === componentType; - }) -} diff --git a/test/dom.js b/test/dom.js index 7e63779..c8dd125 100644 --- a/test/dom.js +++ b/test/dom.js @@ -1,6 +1,7 @@ import React from 'react'; import { unmountComponentAtNode, render } from 'react-dom'; import $ from '../src/index'; +import { match, selector as sel } from '../src/instance-selector'; chai.use(require('sinon-chai')) @@ -37,6 +38,24 @@ describe('DOM rendering', ()=> { } } + describe('css selector parsing', ()=>{ + + it('should match nested', ()=>{ + let inst = $().get(); + + match('.list-wrapper', inst).length.should.equal(1) + + match(sel`div.list-wrapper > ${List}`, inst).length.should.equal(1) + + match(sel`${Stateless}`, inst).length.should.equal(1) + + match(sel`.list-wrapper:has(${List})`, inst).length.should.equal(1) + + match(sel`span:has(${List})`, inst).length.should.equal(0) + }) + + }) + it('should wrap existing mounted component', ()=> { let instance = render(
    , document.createElement('div')) @@ -59,7 +78,7 @@ describe('DOM rendering', ()=> { let instance = $(
    ) instance.context.tagName.should.equal('DIV') - expect(instance.context.parentNode).to.not.exist + expect(instance._mountPoint.parentNode).to.not.exist }) it('should render element at mountPoint', ()=> { @@ -67,7 +86,7 @@ describe('DOM rendering', ()=> { let instance = $(
    , mount) mount.children[0].classList.contains('test').should.equal(true) - instance.context.should.equal(mount) + instance._mountPoint.should.equal(mount) }) it('should render into document', ()=> { @@ -75,7 +94,7 @@ describe('DOM rendering', ()=> { document.querySelectorAll('.test').length.should.equal(1) - unmountComponentAtNode(instance.context) + unmountComponentAtNode(instance._mountPoint) }) it('should render mount into document', ()=> { @@ -83,9 +102,9 @@ describe('DOM rendering', ()=> { let instance = $(
    , mount, true) document.querySelectorAll('.test').length.should.equal(1) - instance.context.should.equal(mount) + instance._mountPoint.should.equal(mount) - unmountComponentAtNode(instance.context) + unmountComponentAtNode(instance._mountPoint) }) it('should work with Stateless components as root', ()=>{ @@ -151,7 +170,7 @@ describe('DOM rendering', ()=> { let instance = $() let result = instance.find(':composite') - result.length.should.equal(3); + result.length.should.equal(2); }) it('should find by :dom', ()=>{ @@ -259,6 +278,21 @@ describe('DOM rendering', ()=> { }) }) + it('should: filter()', ()=>{ + let items = $().find('li') + + items.length.should.equal(3) + + items.filter('.item').length.should.equal(1) + + $().find('div > *').filter(List).length.should.equal(1) + }) + + it('an empty filter should be a noop', ()=>{ + let instance = $() + instance.filter().should.equal(instance) + }) + it('should find single', ()=> { let instance = $() diff --git a/test/shallow.js b/test/shallow.js index 1752fb4..ef734bb 100644 --- a/test/shallow.js +++ b/test/shallow.js @@ -87,8 +87,6 @@ describe('Shallow rendering', ()=> { 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', ()=>{