Skip to content

Commit

Permalink
rebuild
Browse files Browse the repository at this point in the history
  • Loading branch information
jquense committed Oct 10, 2015
1 parent f5b02ed commit 7e88563
Show file tree
Hide file tree
Showing 6 changed files with 295 additions and 6 deletions.
61 changes: 59 additions & 2 deletions lib/README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
bill
=======

A set of tools for matching React Elements against CSS selectors.
A set of tools for matching React Elements against CSS selectors, or easily creating new ways to match react components.
against css selectors.

`bill` is meant to be a substrate library for building more interesting and user friendly testing utilities.
It probably shouldn't be used as a standalone tool.

```js
import { match } from 'bill';
Expand All @@ -16,7 +20,7 @@ let matches = match('div li.foo'
)

matches.length // 1
matches[0] // { type: 'li', props: { className: 'foo' } }
matches[0] // ReactElement{ type: 'li', props: { className: 'foo' } }
```

For selecting non string values, like custom Component types, we can use a [tagged template strings](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/template_strings)
Expand Down Expand Up @@ -51,3 +55,56 @@ matches[0] // { type: List, props }
- sibling selectors
- pseudo selectors (except for has)
- non string interpolations for anything other than "tag" or prop values


## API

### `match(selector, elementOrInstance) -> array<Element|Instance>`

`bill` will match against either a plain old ReactElement in which case it will walk `props.children`,
or if you provide it with a component instance, it will match against the entire rendered tree.

__note:__ matching instances returns __private__ component instances not the normal instances you are used to
working with. This is because DOM and Stateless components do not have public instances that can be further traversed.
To get the normal instances you are used to call `.getPubliceInstance()` on each match.

```js
let matches = match('div li.foo'
<div>
<List>
<li className='foo'>John</li>
<li>Betty</li>
</List>
</div>
)
```

Or with a rendered instance

```js
let root = ReactDOM.render(<div>
<List>
<li className='foo'>John</li>
<li>Betty</li>
</List>
</div>, document.body)

let matches = match('div li.foo', root)

```

### `selector() -> Selector`

A function used for tagged template strings,

```js
selector`div > .foo`
```

You really only need to use the `selector` function when you want to write a selector matching exact prop values or a
composite type.


```js
selector`div > ${List}[length=${5}]`
```
79 changes: 79 additions & 0 deletions lib/element-selector.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
'use strict';

exports.__esModule = true;
exports.match = match;

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }

var _react = require('react');

var _react2 = _interopRequireDefault(_react);

var _lodashObjectTransform = require('lodash/object/transform');

var _lodashObjectTransform2 = _interopRequireDefault(_lodashObjectTransform);

var _lodashObjectHas = require('lodash/object/has');

var _lodashObjectHas2 = _interopRequireDefault(_lodashObjectHas);

var _compiler = require('./compiler');

var _utils = require('./utils');

var compiler = _compiler.create();

exports.compiler = compiler;
compiler.registerPseudo('has', function (compiledSelector) {
return function (root) {
var matches = findAll(root, compiledSelector);
return !!matches.length;
};
});

compiler.registerPseudo('dom', _utils.isDomElement);
compiler.registerPseudo('composite', _utils.isCompositeElement);

compiler.registerNesting('any', function (test) {
return _utils.anyParent.bind(null, test);
});

compiler.registerNesting('>', function (test) {
return _utils.directParent.bind(null, test);
});

function match(selector, tree) {
var includeSelf = arguments.length <= 2 || arguments[2] === undefined ? true : arguments[2];

return findAll(tree, compiler.compile(selector), includeSelf);
}

function findAll(root, test, includeSelf) {
var getParent = arguments.length <= 3 || arguments[3] === undefined ? function () {
return { parent: null };
} : arguments[3];

var found = [];

if (!_react2['default'].isValidElement(root)) return found;

var children = root.props.children;

if (includeSelf && test(root, getParent)) found.push(root);

if (_react2['default'].Children.count(children) === 0) return found;

_react2['default'].Children.forEach(children, function (child) {
var parent = function parent() {
return { parent: root, getParent: getParent };
};

if (_react2['default'].isValidElement(child)) {
if (test(child, parent)) found.push(child);

found = found.concat(findAll(child, test, false, parent));
}
});

return found;
}
21 changes: 19 additions & 2 deletions lib/index.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,24 @@
'use strict';

var _select = require('./select');
function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj['default'] = obj; return newObj; } }

var _react = require('react');

var _elementSelector = require('./element-selector');

var elements = _interopRequireWildcard(_elementSelector);

var _instanceSelector = require('./instance-selector');

var instance = _interopRequireWildcard(_instanceSelector);

function match(selector, element) {
if (_react.isValidElement(element)) return elements.match(selector, element);

return instance.match(selector, element);
}

module.exports = {
match: _select.match, selector: _select.selector
match: match,
selector: elements.compiler.selector
};
102 changes: 102 additions & 0 deletions lib/instance-selector.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
'use strict';

exports.__esModule = true;
exports.match = match;

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }

var _reactLibReactInstanceMap = require('react/lib/ReactInstanceMap');

var _reactLibReactInstanceMap2 = _interopRequireDefault(_reactLibReactInstanceMap);

var _compiler = require('./compiler');

var _utils = require('./utils');

var isDOMComponent = function isDOMComponent(inst) {
return !!(inst && inst.nodeType === 1 && inst.tagName);
};

var isCompositeComponent = function isCompositeComponent(inst) {
return !isDOMComponent(inst) || inst === null || typeof inst.render === 'function' && typeof inst.setState === 'function';
};

var compiler = _compiler.create();

exports.compiler = compiler;
compiler.registerPseudo('has', function (compiledSelector) {
return function (_, inst) {
var matches = findAll(inst, compiledSelector);
return !!matches.length;
};
});

compiler.registerPseudo('dom', _utils.isDomElement);
compiler.registerPseudo('composite', _utils.isCompositeElement);

compiler.registerNesting('any', function (test) {
return function (element, inst, parent) {
return _utils.anyParent(test, element, parent);
};
});

compiler.registerNesting('>', function (test) {
return function (element, inst, parent) {
return _utils.directParent(test, element, parent);
};
});

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 (isDOMComponent(publicInst)) {
(function () {
var renderedChildren = inst._renderedChildren || {};

Object.keys(renderedChildren).forEach(function (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;
}

/**
* The matcher actually works on internal instances, not public ones
* since DOM and stateless components don't have helpful public instances
*/

function match(selector, inst) {
var includeSelf = arguments.length <= 2 || arguments[2] === undefined ? true : arguments[2];

var tree = inst.getPublicInstance ? inst //already a private instance
: inst._reactInternalComponent //is a DOM node
? inst._reactInternalComponent : _reactLibReactInstanceMap2['default'].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;
4 changes: 2 additions & 2 deletions lib/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "bill",
"version": "1.1.0",
"version": "1.2.0",
"description": "css selectors for React Elements",
"main": "index.js",
"repository": {
Expand All @@ -19,7 +19,7 @@
},
"homepage": "https://github.com/jquense/bill",
"peerDependencies": {
"react": ">=0.13.0 ||^0.14.0-alpha1"
"react": ">=0.13.0 || ^0.14.0-alpha1"
},
"dependencies": {
"css-selector-parser": "^1.1.0",
Expand Down
34 changes: 34 additions & 0 deletions lib/utils.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
'use strict';

exports.__esModule = true;
exports.anyParent = anyParent;
exports.directParent = directParent;
var isDomElement = function isDomElement(element) {
return typeof element.type === 'string' && element.type.toLowerCase() === element.type;
};

exports.isDomElement = isDomElement;
var isCompositeElement = function isCompositeElement(element) {
return typeof element.type === 'function';
};

exports.isCompositeElement = isCompositeElement;

function anyParent(test, element, 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, parentNode) {
element = parentNode().parent;
return !!(element && test(element, parentNode().getParent));
}

0 comments on commit 7e88563

Please sign in to comment.