- Notifications
You must be signed in to change notification settings - Fork6
jquense/bill
Folders and files
Name | Name | Last commit message | Last commit date | |
---|---|---|---|---|
Repository files navigation
Sort of likeSizzle for React,bill
isa set of tools for matching React Element and Component trees against CSS selectors.bill
is meant to be asubstrate library for building more interesting and user friendly testing utilities. It probably shouldn'tbe used as a standalone tool.
import{querySelectorAll}from'bill';letmatches=querySelectorAll('div li.foo',<div><List><liclassName='foo'>John</li><li>Betty</li></List></div>)matches.length// 1matches[0].element// ReactElement{ type: 'li', props: { className: 'foo' }}
For selecting non string values, like custom Component types, you can use atagged template strings
import{querySelectorAll,selectorass}from'bill';letmin=5;letmatches=querySelectorAll(s`div >${List}, li[min=${min}]`,<div><List><limin={min}>John</li><li>Betty</li></List></div>)matches.length// 2matches[0].element// { type: List, props }
WARNING: mising module 'react/lib/ReactDOMComponentTree'
bill supports the latest React and back to v0.13.0, because a library like this involves the use of private API's, maintaining support across major versions of React isharder than normal. In particular we need to do dynamic requires to internal apis, which makes bundlers like Webpack warning about missing modules, and bundling with a less smart bundler hard.
Don't worry though they are missing because the version of React you are using doesn't have them, and thats ok, bill knows how todo its work on each supported version.
- id:
#foo
- classes:
.foo
- attributes:
div[propName="hi"]
ordiv[boolProp]
>
: direct descendentdiv > .foo
+
: adjacent sibling selector~
: general sibling selector:has()
: parent selectordiv:has(a.foo)
:not()
: negation:first-child
:last-child
:text
matches "text" (renderable) nodes, which may be a non string value (like a number):dom
matches only DOM components:composite
matches composite (user defined) components
- other pseudo selectors
- non string interpolations for anything other than "tag" or prop values
Nodes are a light object abstraction over both instances and elements that allow for a commonmatching and traversal API between the distinct types of React objects.The interface is similar to a traditional DOM node.
Mostbill
methods that accept elements or instances will also accept a node,allowing you to use the return values of the methods directly with other methods.
Node :{nodeType:NODE_TYPE,element:ReactElement,instance:ReactComponent|HTMLElement,privateInstance:ReactPrivateInstance,nextSibling:Node,prevSibling:Node,parentNode:Node,children:Array<Node>,findAll:(test(node)=>bool,includeSelf? :bool)=>array<Node>}
Their is a caveat to thepublicInstance
property, when it comes to stateless functional components. Insteadof returningnull
as React would,bill
returns the instance of the internal wrapper component. This is to allow,potential chaining and also retrieval of the underlying DOM node if necessary (as in the example above).
Note: Nodes only have instances when matching against arendered component tree
querySelectorAll()
traverses a react element or instance tree searching for nodes that match the provided selector.As the name suggests it's analogous todocument.querySelectorAll
. The return valueis anarray of Nodes.
letmatches;letelements=(<div><List><liclassName='foo'>John</li><li>Betty</li></List></div>)// find elements in the above element descriptionmatches=bill.querySelectorAll('div li.foo',elements)// "John"lettextContent=matches.reduce((str,node)=>str+node.element.props.children,'')// or search a rendered hierarchymatches=bill.querySelectorAll('div li.foo',ReactDOM.render(elements))letdomNodes=matches.map(node=>ReactDOM.findDOMNode(node.instance))
Analogous to the DOMelement.matches
method,matches
returns true if a give element, instance or node is matchedby the providedselector
.
letmatches;letelements=(<div><List><liclassName='foo'>John</li><li>Betty</li></List></div>)letjohnItem=bill.querySelectorAll('div li',elements).filter(node=>bill.matches('.foo',node))// or search a rendered hierarchyletbettyItem=bill.querySelectorAll('div li.foo',ReactDOM.render(elements)).filter(node=>bill.matches(':not(.foo)',node))
A function used for tagged template strings,
You really only need to use theselector
function when you want to write a selector matching exact prop values or acomposite type.
selector`div >${List}[length=${5}]`
A tree traversal utility function for finding nodes that returntrue
from thetestFunction
. findAllis similar toReactTestUtils.findAllInRenderedTree
, but more robust and works on both elements and instance trees.
import{findAll,NODE_TYPES}from'bill';letfound=findAll(elements,function(node){returnnode.nodeType===NODE_TYPES.COMPOSITE})
Compiles a selector string into a function that matches nodes.
Registers a new pseudo selector with the compiler. The second parameter is a function that will be calledwith the pseudo selector's argument (if it exists). The handler function should return a function that matchesa node.
// A simple `:text(foo)` pseudo selectorbill.registerPseudo('text',function(value){returnfunction(node){returnnode.children.filter(n=>n.nodeType===NODE_TYPES.TEXT).every(node=>node.element===value)}})letmatches=bill.querySelectorAll('li:text(john)',<ul><li>betsy</li><li>john</li><li>yolanda</li></ul>)matches[0].instance// <li class='bar'>john</li>
For pseudoSelectors whose inner argument is a selector, you can compile itto a test function withbill.compile
.
// We want to test if an element has a sibling that matches// a selector e.g. :nextSibling(.foo)bill.registerPseudo('nextSibling',function(selector){letmatcher=bill.compile(selector);returnfunction(node){node=node.nextSiblingreturn!!node&&matcher(node)}})letmatches=bill.querySelectorAll('li:nextSibling(li.baz)',<ul><liclassName='foo'>1</li><liclassName='bar'>2</li><liclassName='baz'>3</li></ul>)matches[0].instance// <li class='bar'>2</li>
Similar toregisterPseudo
you can also register new combinator selectors (*, >, ~, +) using the same pattern.The handler function is called with thecompiled selector segment.
Note: remember that selectors are matchedright-to-left so the logic is generally reversed from what youmight expect.
// lets implement the same previous sibling selector as above// but with a nesting selector.bill.registerNesting('!',test=>node=>{node=node.nextSiblingreturn!!(node&&test(node))})letmatches=bill.querySelectorAll('li.baz ! li',<ul><liclassName='foo'>1</li><liclassName='bar'>2</li><liclassName='baz'>3</li></ul>)matches[0].instance// <li class='bar'>2</li>
Set of constants that correspond toNode.nodeType
. Useful for filtering out types of nodes while traversing a tree.
NODE_TYPES.COMPOSITE
NODE_TYPES.DOM
NODE_TYPES.TEXT
Determine if an object is a Node object.
About
css selector engine for React elements and components