Important: This documentation covers Yarn 1 (Classic).
For Yarn 2+ docs and migration guide, see yarnpkg.com.

Package detail

bill

jquense698MIT3.2.6

css selectors for React Elements

react, test, query, css, selector, react-component, tree, traversal, react element, node

readme

bill

Sort of like Sizzle for React, bill is a set of tools for matching React Element and Component trees 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.

import { querySelectorAll } from 'bill';

let matches = querySelectorAll('div li.foo',
  <div>
    <List>
      <li className='foo'>John</li>
      <li>Betty</li>
    </List>
  </div>
)

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

For selecting non string values, like custom Component types, you can use a tagged template strings

import { querySelectorAll, selector as s } from 'bill';

let min = 5;

let matches = querySelectorAll(s`div > ${List}, li[min=${min}]`,
  <div>
    <List>
      <li min={min}>John</li>
      <li>Betty</li>
    </List>
  </div>
)

matches.length     // 2
matches[0].element // { type: List, props }

React Compatibility

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 is harder 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 to do its work on each supported version.

Supported

  • id: #foo
  • classes: .foo
  • attributes: div[propName="hi"] or div[boolProp]
  • >: direct descendent div > .foo
  • +: adjacent sibling selector
  • ~: general sibling selector
  • :has(): parent selector div: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

Not supported

  • other pseudo selectors
  • non string interpolations for anything other than "tag" or prop values

API

Node

Nodes are a light object abstraction over both instances and elements that allow for a common matching and traversal API between the distinct types of React objects. The interface is similar to a traditional DOM node.

Most bill 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 the publicInstance property, when it comes to stateless functional components. Instead of returning null 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 a rendered component tree

querySelectorAll(selector, subject: Element|Instance|Node) -> Array<Node>

querySelectorAll() traverses a react element or instance tree searching for nodes that match the provided selector. As the name suggests it's analogous to document.querySelectorAll. The return value is an array of Nodes.

let matches;
let elements = (
  <div>
    <List>
      <li className='foo'>John</li>
      <li>Betty</li>
    </List>
  </div>
)

// find elements in the above element description
matches = bill.querySelectorAll('div li.foo', elements)

// "John"
let textContent = matches.reduce(
  (str, node) => str + node.element.props.children, '')

// or search a rendered hierarchy
matches = bill.querySelectorAll('div li.foo', ReactDOM.render(elements))

let domNodes = matches.map(
  node => ReactDOM.findDOMNode(node.instance))

matches(selector, subject: Element|Instance|Node) -> bool

Analogous to the DOM element.matches method, matches returns true if a give element, instance or node is matched by the provided selector.

let matches;
let elements = (
  <div>
    <List>
      <li className='foo'>John</li>
      <li>Betty</li>
    </List>
  </div>
)

let johnItem = bill
  .querySelectorAll('div li', elements)
  .filter(node => bill.matches('.foo', node))


// or search a rendered hierarchy
let bettyItem = bill
  .querySelectorAll('div li.foo', ReactDOM.render(elements))
  .filter(node => bill.matches(':not(.foo)', node))

selector() -> Selector

A function used for tagged template strings,

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

selector`div > ${List}[length=${5}]`

findAll(subject: Element|Instance|Node, test: (node: Node)=> bool, includeSelf? : bool) -> Array<Node>

A tree traversal utility function for finding nodes that return true from the testFunction. findAll is similar to ReactTestUtils.findAllInRenderedTree, but more robust and works on both elements and instance trees.

import { findAll, NODE_TYPES } from 'bill';

let found = findAll(elements, function (node) {
  return node.nodeType === NODE_TYPES.COMPOSITE
})

compile(selector) => (node: Node) => bool

Compiles a selector string into a function that matches nodes.

registerPseudo(pseudoSelector, handlePseudo: (selector) => (node: Node) => bool)

Registers a new pseudo selector with the compiler. The second parameter is a function that will be called with the pseudo selector's argument (if it exists). The handler function should return a function that matches a node.

// A simple `:text(foo)` pseudo selector
bill.registerPseudo('text', function(value) {
  return function (node) {
    return node.children
      .filter(n => n.nodeType === NODE_TYPES.TEXT)
      .every(node => node.element === value)
  }
})

let matches = 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 it to a test function with bill.compile.

// We want to test if an element has a sibling that matches
// a selector e.g. :nextSibling(.foo)
bill.registerPseudo('nextSibling', function (selector) {
  let matcher = bill.compile(selector);
  return function (node) {
    node = node.nextSibling
    return !!node && matcher(node)
  }
})

let matches = bill.querySelectorAll('li:nextSibling(li.baz)',
  <ul>
    <li className='foo'>1</li>
    <li className='bar'>2</li>
    <li className='baz'>3</li>
  </ul>
)

matches[0].instance // <li class='bar'>2</li>

registerNesting(nestingCombinator, handleNesting: (matcher: function) => (node: Node) => bool)

Similar to registerPseudo you can also register new combinator selectors (*, >, ~, +) using the same pattern. The handler function is called with the compiled selector segment.

Note: remember that selectors are matched right-to-left so the logic is generally reversed from what you might expect.

// lets implement the same previous sibling selector as above
// but with a nesting selector.
bill.registerNesting('!', test => node => {
  node = node.nextSibling
  return !!(node && test(node))
})

let matches = bill.querySelectorAll('li.baz ! li',
  <ul>
    <li className='foo'>1</li>
    <li className='bar'>2</li>
    <li className='baz'>3</li>
  </ul>
)

matches[0].instance // <li class='bar'>2</li>

NODE_TYPES Object

Set of constants that correspond to Node.nodeType. Useful for filtering out types of nodes while traversing a tree.

  • NODE_TYPES.COMPOSITE
  • NODE_TYPES.DOM
  • NODE_TYPES.TEXT

isNode() -> boolean

Determine if an object is a Node object.

changelog

v3.2.6 - Mon, 21 Nov 2016 21:33:50 GMT

  • 1aaaee3 [fixed] internal instance check

v3.2.4 - Mon, 21 Nov 2016 20:55:19 GMT

v2.1.2 - Thu, 21 Jan 2016 15:56:24 GMT

  • 392fcc1 [fixed] don't conflict with React element typeof
  • 151c851 [fixed] don't conflict with React element typeof

v2.1.1 - Wed, 30 Dec 2015 03:54:35 GMT

  • b9e0db0 [fixed] tag name comparisons should be case sensative

v2.1.0 - Sat, 26 Dec 2015 20:40:21 GMT

  • 44b8c8b [fixed] correctly export NODE_TYPES
  • a03390d [changed] added isSelector option to registerPseduo to allow non selctor pseudo calls

v2.0.3 - Thu, 24 Dec 2015 15:27:22 GMT

  • ca1c9e0 [fixed] better nodeType element check includes falsy renderable elements

v2.0.2 - Thu, 24 Dec 2015 02:42:09 GMT

  • d820035 [fixed] node properties not using the correct element

v2.0.1 - Wed, 23 Dec 2015 23:07:36 GMT

  • 338706a [fixed] make node.element dynamic getter

v2.0.0 - Sun, 20 Dec 2015 21:47:06 GMT

  • 960138f [added] findAll and compiler methods to main export
  • f353cde [added] Node object abstraction used by the compiler

v1.5.2 - Sat, 19 Dec 2015 19:44:15 GMT

  • cbad23f [fixed] properly allow iterable children
  • 40a6df8 [changed] run local tests before nesting or pseudo selectors

v1.5.1 - Sat, 19 Dec 2015 04:50:13 GMT

  • a3d9d07 [fixed] regression with text nodes

v1.5.0 - Fri, 18 Dec 2015 19:59:14 GMT

  • 007dbdb [added] sibling combinators and psuedos

v1.4.2 - Sat, 12 Dec 2015 16:32:15 GMT

  • c46375f [fixed] better 'text' element check

v1.4.1 - Wed, 14 Oct 2015 11:50:03 GMT

  • a39a1f4 [fixed] handle empty (null) elements

v1.4.0 - Sun, 11 Oct 2015 17:14:39 GMT

  • 8227089 [added] support Component displayName and inferred functionName

v1.3.2 - Sat, 10 Oct 2015 16:57:35 GMT

  • 0e3d7d1 [fixed] proper element text node traversal

v1.3.1 - Sat, 10 Oct 2015 16:19:58 GMT

  • 4645192 [fixed] missed single text children

v1.3.0 - Sat, 10 Oct 2015 15:47:44 GMT

v1.2.1 - Sat, 10 Oct 2015 14:59:32 GMT

  • 6f80feb [fixed] :dom and :composite selectors

v1.2.0 - Sat, 10 Oct 2015 00:34:01 GMT

  • ecfc1de [added] instance selector

v1.1.0 - Mon, 28 Sep 2015 15:25:06 GMT

  • d0b4273 [changed] streamline psuedo api

v1.0.5 - Mon, 28 Sep 2015 14:56:23 GMT

  • c672b2b [changed] refactor internals to allow customizable selection

v1.0.4 - Mon, 28 Sep 2015 13:00:45 GMT

  • e60bc99 [fixed] substitutions in nested selectors

v1.0.3 - Mon, 28 Sep 2015 12:37:28 GMT

  • 209436d [fixed] need string check for tagName

v1.0.2 - Mon, 28 Sep 2015 12:22:52 GMT

  • f37b30c [fixed] attempt to check props of text nodes

v1.0.1 - Sun, 27 Sep 2015 23:46:41 GMT