diff --git a/lib/README.md b/lib/README.md index b4728b3..24620b9 100644 --- a/lib/README.md +++ b/lib/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/index.js b/lib/index.js index 4dca740..ac2190a 100644 --- a/lib/index.js +++ b/lib/index.js @@ -1,41 +1,63 @@ 'use strict'; -function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } +var _templateObject = _taggedTemplateLiteralLoose(['', ''], ['', '']); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } -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'); +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 _reactDom = require('react-dom'); + +var _reactDom2 = _interopRequireDefault(_reactDom); + +var _reactLibReactInstanceMap = require('react/lib/ReactInstanceMap'); + +var _reactLibReactInstanceMap2 = _interopRequireDefault(_reactLibReactInstanceMap); + +var _reactAddonsTestUtils = require('react-addons-test-utils'); + +var _reactAddonsTestUtils2 = _interopRequireDefault(_reactAddonsTestUtils); + +var _instanceSelector = require('./instance-selector'); + +var _domHelpersClassHasClass = require('dom-helpers/class/hasClass'); + +var _domHelpersClassHasClass2 = _interopRequireDefault(_domHelpersClassHasClass); var $r = module.exports = rtq; var isRtq = function isRtq(item) { - return item && item.__isRTQ; + return item && item._isRTQ; }; -rtq.react = React; + +rtq.react = _react2['default']; +rtq.s = rtq.selector = _instanceSelector.selector; function rtq(element, mount) { var renderIntoDocument = arguments.length <= 2 || arguments[2] === undefined ? mount === true : arguments[2]; return (function () { var context; - if (!mount || mount === true) { - mount = document.createElement('div'); - } + 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)) context = element;else if (isRtq(element)) { + if (_reactAddonsTestUtils2['default'].isElement(element)) context = element = render(element, mount, renderIntoDocument);else if (_reactAddonsTestUtils2['default'].isDOMComponent(element) || _reactAddonsTestUtils2['default'].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); })(); } @@ -46,202 +68,189 @@ function render(element, mountPoint) { if (renderIntoDocument) document.body.appendChild(mount); - var instance = ReactDOM.render(element, mount); + var instance = _reactDom2['default'].render(element, mount); if (instance === null) { - instance = ReactDOM.render(wrapStateless(element), mount); + instance = _reactDom2['default'].render(wrapStateless(element), mount); } if (!instance.renderWithProps) { instance.renderWithProps = function (newProps) { - return render(React.cloneElement(element, newProps), mount, renderIntoDocument); + return render(_react2['default'].cloneElement(element, newProps), mount, renderIntoDocument); }; } 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 = _instanceSelector.selector(_templateObject, selector); - this._privateInstances = Object.create(null); + return _instanceSelector.match(selector, tree, includeSelf); +} - while (++idx < len) { - var component = components[idx]; +rtq.dom = function (component) { + return component instanceof HTMLElement ? component : _reactDom2['default'].findDOMNode(component); +}; - // if this is a private instance, get the public one - if (component.getPublicInstance) { - this._privateInstances[idx] = component; - component = component.getPublicInstance(); +var ComponentCollection = (function () { + function ComponentCollection(_components, context, mountPoint, selector) { + _classCallCheck(this, ComponentCollection); - //stateless - if (component === null) component = ReactDOM.findDOMNode(this._privateInstances[idx]._instance); - } - // if this a root Stateless component - else if (component && component.__isRTQstatelessWrapper) { - var wrapperInstance = ReactInstanceMap.get(component); - this._privateInstances[idx] = wrapperInstance._renderedComponent; - component = ReactDOM.findDOMNode(component); - } else { - this._privateInstances[idx] = ReactInstanceMap.get(component) || component._reactInternalComponent; - } + var components = _components == null ? [] : [].concat(_components), + idx = -1, + len = components.length; - this[idx] = component; - } + this._privateInstances = Object.create(null); - this.length = len; - this.context = context; - this.selector = selector; - this.__isRTQ = true; -} + 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 = _reactDom2['default'].findDOMNode(this._privateInstances[idx]._instance); + } + // if this a root Stateless component + else if (component && component.__isRTQstatelessWrapper) { + var wrapperInstance = _reactLibReactInstanceMap2['default'].get(component); + this._privateInstances[idx] = wrapperInstance._renderedComponent; + component = _reactDom2['default'].findDOMNode(component); + } else { + this._privateInstances[idx] = _reactLibReactInstanceMap2['default'].get(component) || component._reactInternalComponent; + } + + this[idx] = component; + } - constructor: ComponentCollection, + this.length = len; + this.context = context; + this.selector = selector; + this._mountPoint = mountPoint; + this._isRTQ = true; + } - unmount: function unmount() { + ComponentCollection.prototype._root = function _root() { + return this.context._reactInternalComponent || this.context; + }; + + ComponentCollection.prototype.unmount = function unmount() { var inBody = !!this.context.parentNode; + _reactDom2['default'].unmountComponentAtNode(this._mountPoint); + + if (inBody) document.body.removeChild(this._mountPoint); - ReactDOM.unmountComponentAtNode(this.context); - if (inBody) document.body.removeChild(this.context); this.context = null; - }, + }; - setProps: function setProps(newProps) { + ComponentCollection.prototype.setProps = function setProps(newProps) { return this.mapInPlace(function (element) { return element.renderWithProps(newProps); }); - }, + }; - each: function each(cb, thisArg) { + ComponentCollection.prototype.each = function each(cb, thisArg) { var idx = -1, len = this.length; while (++idx < len) cb.call(thisArg, this[idx], idx, this); return this; - }, + }; - mapInPlace: function mapInPlace(cb, thisArg) { + ComponentCollection.prototype.mapInPlace = function mapInPlace(cb, thisArg) { var _this = this; return this.each(function (el, idx, list) { return _this[idx] = cb(el, idx, list); }); - }, + }; - map: function map(cb, thisArg) { + ComponentCollection.prototype.map = function map(cb, thisArg) { var idx = -1, len = this.length, result = []; while (++idx < len) result.push(cb.call(thisArg, this[idx], idx, this)); return result; - }, + }; - _getInstances: function _getInstances() { + ComponentCollection.prototype._reduce = function _reduce(cb, initial) { + return new ComponentCollection(this._getInstances().reduce(cb, initial), this.context, this._mountPount, this.selector); + }; + + ComponentCollection.prototype.reduce = function reduce(cb, initial) { + return new ComponentCollection([].reduce.call(this, cb, initial), this.context, this._mountPount, this.selector); + }; + + ComponentCollection.prototype._getInstances = function _getInstances() { var _this2 = this; - return this.map(function (component, idx) { + return this.map(function (_, idx) { return _this2._privateInstances[idx]; }); - }, + }; - get: function get() { + ComponentCollection.prototype.get = function get() { return unwrap(this.map(function (component) { return component; })); - }, + }; - dom: function dom() { + ComponentCollection.prototype.dom = function dom() { return unwrap(this.map(rtq.dom)); - }, + }; - find: function find(selector) { - var _this3 = this; + ComponentCollection.prototype.find = function find(selector) { + return this._reduce(function (result, instance) { + return result.concat(match(selector, instance, false)); + }, []); + }; - var result = []; + ComponentCollection.prototype.filter = function filter(selector) { + if (!selector) return this; - this.each(function (component, idx) { - component = component !== null ? _this3._privateInstances[idx] || component : component; + var matches = match(selector, this._root()); - result = result.concat(_find(component, selector)); - }); + return this._reduce(function (result, el) { + return matches.indexOf(el) !== -1 ? result.concat(el) : result; + }, []); + }; - return new ComponentCollection(result, this.context, selector); - }, - - only: function only() { + ComponentCollection.prototype.only = function only() { if (this.length !== 1) throw Error('`' + this.selector + '` found: ' + this.length + ' not 1 '); return this.first(); - }, + }; - single: function single(selector) { + ComponentCollection.prototype.single = function single(selector) { return selector ? this.find(selector).only() : this.only(); - }, - - first: function first(selector) { - return selector ? this.find(selector).first() : new ComponentCollection(this[0], this.context, this.selector); - }, + }; - last: function last(selector) { - return selector ? this.find(selector).last() : new ComponentCollection(this[this.length - 1], this.context, this.selector); - }, + ComponentCollection.prototype.first = function first(selector) { + return selector ? this.find(selector).first() : new ComponentCollection(this[0], this.context, this._mountPount, this.selector); + }; - is: function is(selector) { - var instances = this._getInstances(); - var getPublicInst = function getPublicInst(inst) { - return inst.getPublicInstance ? inst.getPublicInstance() : inst; - }; + ComponentCollection.prototype.last = function last(selector) { + return selector ? this.find(selector).last() : new ComponentCollection(this[this.length - 1], this.context, this._mountPount, this.selector); + }; - if (typeof selector === 'function') { - return instances.every(function (inst) { - var publicInst = getPublicInst(inst); - return findAll.isCompositeComponent(publicInst) && inst._currentElement.type === selector; - }); - } else if (selector === ':dom') return instances.every(function (inst) { - return utils.isDOMComponent(getPublicInst(inst)); - });else if (selector === ':composite') return instances.every(function (inst) { - return findAll.isCompositeComponent(getPublicInst(inst)); - });else if (selector[0] === '.') return instances.every(function (inst) { - return hasClass(rtq.dom(getPublicInst(inst)), selector.substr(1)); - });else return instances.every(function (inst) { - return rtq.dom(getPublicInst(inst)).tagName.toUpperCase() === selector.toUpperCase(); - }); - }, + ComponentCollection.prototype.is = function is(selector) { + return this.filter(selector).length === this.length; + }; - trigger: function trigger(event, data) { + ComponentCollection.prototype.trigger = function trigger(event, data) { data = data || {}; if (event.substr(0, 2) === 'on') event = event.substr(2, 1).toLowerCase() + event.substr(3); - if (!(event in utils.Simulate)) throw new TypeError('"' + event + '" is not a supported DOM event'); + if (!(event in _reactAddonsTestUtils2['default'].Simulate)) throw new TypeError('"' + event + '" is not a supported DOM event'); return this.each(function (component) { - return utils.Simulate[event]($r.dom(component), data); + return _reactAddonsTestUtils2['default'].Simulate[event]($r.dom(component), data); }); - } -}; + }; -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 || []; -} + return ComponentCollection; +})(); function unwrap(arr) { return arr && arr.length === 1 ? arr[0] : arr; @@ -263,7 +272,7 @@ function wrapStateless(Element) { }; return StatelessWrapper; - })(React.Component); + })(_react2['default'].Component); - return React.createElement(StatelessWrapper, null); + return _react2['default'].createElement(StatelessWrapper, null); } \ No newline at end of file diff --git a/lib/instance-selector.js b/lib/instance-selector.js new file mode 100644 index 0000000..4de19e2 --- /dev/null +++ b/lib/instance-selector.js @@ -0,0 +1,109 @@ +'use strict'; + +exports.__esModule = true; +exports.match = match; + +var _billCompiler = require('bill/compiler'); + +var _utils = require('./utils'); + +var React = require('react'); +var ReactDOM = require('react-dom'); +var ReactInstanceMap = require('react/lib/ReactInstanceMap'); +var ReactTestUtils = require('react-addons-test-utils'); + +var compiler = _billCompiler.create(); + +compiler.registerPseudo('has', function (compiledSelector) { + return function (root, inst) { + var matches = findAll(inst, compiledSelector); + return !!matches.length; + }; +}); + +compiler.registerPseudo('dom', function () { + return function (root, inst) { + return typeof root.type === 'string' && root.type.toLowerCase() === root.type; + }; +}); + +compiler.registerPseudo('composite', function () { + return function (root, inst) { + return typeof root.type === 'function'; + }; +}); + +compiler.registerNesting('any', function (test) { + return anyParent.bind(null, test); +}); + +compiler.registerNesting('>', function (test) { + return directParent.bind(null, test); +}); + +function findAll(inst, test) { + var getParent = arguments.length <= 2 || arguments[2] === undefined ? function () { + return { parent: null }; + } : arguments[2]; + var excludeSelf = arguments.length <= 3 || arguments[3] === undefined ? true : arguments[3]; + + var found = []; + + if (!inst || !inst.getPublicInstance) return found; + + var publicInst = inst.getPublicInstance(), + element = inst._currentElement, + parent = function parent() { + return { parent: element, getParent: getParent }; + }; + + if (!excludeSelf && test(element, inst, getParent)) found = found.concat(inst); + + if (ReactTestUtils.isDOMComponent(publicInst)) { + (function () { + var renderedChildren = inst._renderedChildren || {}; + + Object.keys(renderedChildren).forEach(function (key) { + found = found.concat(findAll(renderedChildren[key], test, parent, false)); + }); + })(); + } else if (_utils.isCompositeComponent(publicInst)) { + found = found.concat(findAll(inst._renderedComponent, test, parent, false)); + } + + return found; +} + +function anyParent(test, element, inst, parentNode) { + do { + var _parentNode = parentNode(); + + var getParent = _parentNode.getParent; + var parent = _parentNode.parent; + + 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)); +} + +function match(selector, inst) { + var includeSelf = arguments.length <= 2 || arguments[2] === undefined ? true : arguments[2]; + + var tree = inst.getPublicInstance ? inst : ReactInstanceMap.get(inst); + + return findAll(tree, compiler.compile(selector), undefined, !includeSelf); +} + +var compile = compiler.compile; +var compileRule = compiler.compileRule; +var selector = compiler.selector; +exports.compile = compile; +exports.compileRule = compileRule; +exports.selector = selector; \ No newline at end of file diff --git a/lib/package.json b/lib/package.json index 81e4ca6..55e81f5 100644 --- a/lib/package.json +++ b/lib/package.json @@ -1,6 +1,6 @@ { "name": "react-testutil-query", - "version": "2.2.0", + "version": "3.0.0", "description": "small wrapper around react test utils so I don't have to write long method names", "main": "index.js", "repository": { @@ -23,7 +23,7 @@ "react-dom": ">=0.14.0-rc1" }, "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/lib/shallow.js b/lib/shallow.js index a32f505..896162e 100644 --- a/lib/shallow.js +++ b/lib/shallow.js @@ -65,7 +65,6 @@ function rtq(element) { } else if (isRtq(element)) { context = element.root; element = element.get(); - //rerender = element._rerender } return new ShallowCollection(element, context, rerender); @@ -136,8 +135,6 @@ var ShallowCollection = (function () { 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) { @@ -145,6 +142,14 @@ var ShallowCollection = (function () { }), this.root); }; + ShallowCollection.prototype.first = function first(selector) { + return selector ? this.find(selector).first() : new ShallowCollection(this[0], this.root); + }; + + ShallowCollection.prototype.last = function last(selector) { + return selector ? this.find(selector).last() : new ComponentCollection(this[this.length - 1], this.root); + }; + ShallowCollection.prototype.is = function is(selector) { return this.filter(selector).length === this.length; }; diff --git a/lib/utils.js b/lib/utils.js index be20af6..71153bb 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -1,44 +1,9 @@ 'use strict'; exports.__esModule = true; -exports._findAllInRenderedTree = _findAllInRenderedTree; exports.isCompositeComponent = isCompositeComponent; -exports.isCompositeComponentWithType = isCompositeComponentWithType; -exports.findAllInRenderedTree = findAllInRenderedTree; -exports.componentsByTagName = componentsByTagName; -exports.componentsByClassName = componentsByClassName; -exports.componentsByType = componentsByType; -var React = require('react'); -var ReactDOM = require('react-dom'); -var ReactInstanceMap = require('react/lib/ReactInstanceMap'); var ReactTestUtils = require('react-addons-test-utils'); -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; -} - function isCompositeComponent(inst) { if (ReactTestUtils.isDOMComponent(inst)) { // Accessing inst.setState warns; just return false as that'll be what @@ -46,40 +11,4 @@ function isCompositeComponent(inst) { return false; } return inst === null || typeof inst.render === 'function' && typeof inst.setState === 'function'; -} - -function isCompositeComponentWithType(inst, type) { - if (!isCompositeComponent(inst)) return false; - var internalInstance = ReactInstanceMap.get(inst); - var constructor = internalInstance._currentElement.type; - return constructor === type; -} - -function findAllInRenderedTree(inst, test) { - if (!inst) return []; - return _findAllInRenderedTree(inst.getPublicInstance ? inst : ReactInstanceMap.get(inst), test); -} - -function componentsByTagName(root, tagName) { - return findAllInRenderedTree(root, function (inst) { - return ReactTestUtils.isDOMComponent(inst) && inst.tagName.toUpperCase() === tagName.toUpperCase(); - }); -} - -function componentsByClassName(root, className) { - return findAllInRenderedTree(root, function (inst) { - if (ReactTestUtils.isDOMComponent(inst)) { - var instClassName = ReactDOM.findDOMNode(inst).className; - return instClassName && ('' + instClassName).split(/\s+/).indexOf(className) !== -1; - } - }); -} - -function componentsByType(root, componentType) { - return findAllInRenderedTree(root, function (inst, privateInst) { - if (!isCompositeComponent(inst)) return false; - - var constructor = privateInst._currentElement.type; - return constructor === componentType; - }); } \ No newline at end of file diff --git a/src/shallow.js b/src/shallow.js index 5823e00..adaee26 100644 --- a/src/shallow.js +++ b/src/shallow.js @@ -116,6 +116,11 @@ class ShallowCollection { }), this.root) } + + is(selector) { + return this.filter(selector).length === this.length + } + first(selector){ return selector ? this.find(selector).first() @@ -128,7 +133,21 @@ class ShallowCollection { : new ComponentCollection(this[this.length - 1], this.root) } - is(selector) { - return this.filter(selector).length === this.length + text(){ + let str = ''; + + this.each(element => traverse(element, + el => typeof el === 'string' && (str += el)) + ) + return str } } + +function traverse(element, cb){ + cb(element) + + if (element && element.props) + React.Children.forEach(element.props.children, child => { + traverse(child, cb) + }) +} diff --git a/test/shallow.js b/test/shallow.js index ef734bb..dbd43dd 100644 --- a/test/shallow.js +++ b/test/shallow.js @@ -94,6 +94,11 @@ describe('Shallow rendering', ()=> { instance.filter().should.equal(instance) }) + it.only('text content', ()=>{ + let instance = $() + instance.text().should.equal('hi 1hi 2hi 3') + }) + it('should: is()', ()=>{ $().find('.foo') .is('li').should.equal(true)