{"id":15645524,"url":"https://github.com/jquense/bill","last_synced_at":"2025-04-15T12:19:10.510Z","repository":{"id":57151727,"uuid":"43266030","full_name":"jquense/bill","owner":"jquense","description":"css selector engine for React elements and components","archived":false,"fork":false,"pushed_at":"2017-09-15T09:44:04.000Z","size":115,"stargazers_count":53,"open_issues_count":3,"forks_count":6,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-04-15T12:18:52.986Z","etag":null,"topics":["css-selectors","dom-node","pseudo-selectors","react","react-elements","testing"],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/jquense.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2015-09-27T21:33:51.000Z","updated_at":"2023-03-01T07:44:48.000Z","dependencies_parsed_at":"2022-09-03T18:11:30.030Z","dependency_job_id":null,"html_url":"https://github.com/jquense/bill","commit_stats":null,"previous_names":[],"tags_count":34,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jquense%2Fbill","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jquense%2Fbill/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jquense%2Fbill/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jquense%2Fbill/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/jquense","download_url":"https://codeload.github.com/jquense/bill/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":249067787,"owners_count":21207396,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["css-selectors","dom-node","pseudo-selectors","react","react-elements","testing"],"created_at":"2024-10-03T12:08:27.551Z","updated_at":"2025-04-15T12:19:10.490Z","avatar_url":"https://github.com/jquense.png","language":"JavaScript","readme":"bill\n=======\n\nSort of like [Sizzle](https://github.com/jquery/sizzle/tree/master#sizzle) for React, `bill` is\na set of tools for matching React Element and Component trees against CSS selectors. `bill` is meant to be a\nsubstrate library for building more interesting and user friendly testing utilities. It probably shouldn't\nbe used as a standalone tool.\n\n```js\nimport { querySelectorAll } from 'bill';\n\nlet matches = querySelectorAll('div li.foo',\n  \u003cdiv\u003e\n    \u003cList\u003e\n      \u003cli className='foo'\u003eJohn\u003c/li\u003e\n      \u003cli\u003eBetty\u003c/li\u003e\n    \u003c/List\u003e\n  \u003c/div\u003e\n)\n\nmatches.length     // 1\nmatches[0].element // ReactElement{ type: 'li', props: { className: 'foo' } }\n```\n\nFor selecting non string values, like custom Component types, you can use a [tagged template strings](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/template_strings)\n\n```js\nimport { querySelectorAll, selector as s } from 'bill';\n\nlet min = 5;\n\nlet matches = querySelectorAll(s`div \u003e ${List}, li[min=${min}]`,\n  \u003cdiv\u003e\n    \u003cList\u003e\n      \u003cli min={min}\u003eJohn\u003c/li\u003e\n      \u003cli\u003eBetty\u003c/li\u003e\n    \u003c/List\u003e\n  \u003c/div\u003e\n)\n\nmatches.length     // 2\nmatches[0].element // { type: List, props }\n```\n\n### React Compatibility\n\n\u003e WARNING: mising module 'react/lib/ReactDOMComponentTree'\n\nbill 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.\n\nDon'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\ndo its work on each supported version.\n\n### Supported\n\n- id: `#foo`\n- classes: `.foo`\n- attributes: `div[propName=\"hi\"]` or `div[boolProp]`\n- `\u003e`: direct descendent `div \u003e .foo`\n- `+`: adjacent sibling selector\n- `~`: general sibling selector\n- `:has()`: parent selector `div:has(a.foo)`\n- `:not()`: negation\n- `:first-child`\n- `:last-child`\n- `:text` matches \"text\" (renderable) nodes, which may be a non string value (like a number)\n- `:dom` matches only DOM components\n- `:composite` matches composite (user defined) components\n\n### Not supported\n\n- other pseudo selectors\n- non string interpolations for anything other than \"tag\" or prop values\n\n## API\n\n#### Node\n\nNodes are a light object abstraction over both instances and elements that allow for a common\nmatching and traversal API between the distinct types of React objects.\nThe interface is similar to a traditional DOM node.\n\nMost `bill` methods that accept elements or instances will also accept a node,\nallowing you to use the return values of the methods directly with other methods.\n\n```js\nNode : {\n  nodeType: NODE_TYPE,\n  element: ReactElement,\n  instance: ReactComponent | HTMLElement,\n  privateInstance: ReactPrivateInstance,\n  nextSibling: Node,\n  prevSibling: Node,\n  parentNode: Node,\n  children: Array\u003cNode\u003e,\n  findAll: (test (node) =\u003e bool, includeSelf? : bool) =\u003e array\u003cNode\u003e\n}\n```\n\nTheir is a caveat to the `publicInstance` property, when it comes to stateless functional components. Instead\nof returning `null` as React would, `bill` returns the instance of the internal wrapper component. This is to allow,\npotential chaining and also retrieval of the underlying DOM node if necessary (as in the example above).\n\n__Note:__ Nodes only have instances when matching against a _rendered_ component tree\n\n#### `querySelectorAll(selector, subject: Element|Instance|Node) -\u003e Array\u003cNode\u003e`\n\n`querySelectorAll()` traverses a react element or instance tree searching for nodes that match the provided selector.\nAs the name suggests it's analogous to `document.querySelectorAll`. The return value\nis an __array of Nodes__.\n\n```js\nlet matches;\nlet elements = (\n  \u003cdiv\u003e\n    \u003cList\u003e\n      \u003cli className='foo'\u003eJohn\u003c/li\u003e\n      \u003cli\u003eBetty\u003c/li\u003e\n    \u003c/List\u003e\n  \u003c/div\u003e\n)\n\n// find elements in the above element description\nmatches = bill.querySelectorAll('div li.foo', elements)\n\n// \"John\"\nlet textContent = matches.reduce(\n  (str, node) =\u003e str + node.element.props.children, '')\n\n// or search a rendered hierarchy\nmatches = bill.querySelectorAll('div li.foo', ReactDOM.render(elements))\n\nlet domNodes = matches.map(\n  node =\u003e ReactDOM.findDOMNode(node.instance))\n```\n\n#### `matches(selector, subject: Element|Instance|Node) -\u003e bool`\n\nAnalogous to the DOM `element.matches` method, `matches` returns true if a give element, instance or node is matched\nby the provided `selector`.\n\n```js\nlet matches;\nlet elements = (\n  \u003cdiv\u003e\n    \u003cList\u003e\n      \u003cli className='foo'\u003eJohn\u003c/li\u003e\n      \u003cli\u003eBetty\u003c/li\u003e\n    \u003c/List\u003e\n  \u003c/div\u003e\n)\n\nlet johnItem = bill\n  .querySelectorAll('div li', elements)\n  .filter(node =\u003e bill.matches('.foo', node))\n\n\n// or search a rendered hierarchy\nlet bettyItem = bill\n  .querySelectorAll('div li.foo', ReactDOM.render(elements))\n  .filter(node =\u003e bill.matches(':not(.foo)', node))\n```\n\n#### `selector() -\u003e Selector`\n\nA function used for tagged template strings,\n\nYou really only need to use the `selector` function when you want to write a selector matching exact prop values or a\ncomposite type.\n\n```js\nselector`div \u003e ${List}[length=${5}]`\n```\n\n#### findAll(subject: Element|Instance|Node, test: (node: Node)=\u003e bool, includeSelf? : bool) -\u003e Array\u003cNode\u003e\n\nA tree traversal utility function for finding nodes that return `true` from the `testFunction`. findAll\nis similar to `ReactTestUtils.findAllInRenderedTree`, but more robust and works on both elements and instance trees.\n\n```js\nimport { findAll, NODE_TYPES } from 'bill';\n\nlet found = findAll(elements, function (node) {\n  return node.nodeType === NODE_TYPES.COMPOSITE\n})\n```\n\n#### compile(selector) =\u003e (node: Node) =\u003e bool\n\nCompiles a selector string into a function that matches nodes.\n\n#### registerPseudo(pseudoSelector, handlePseudo: (selector) =\u003e (node: Node) =\u003e bool)\n\nRegisters a new pseudo selector with the compiler. The second parameter is a function that will be called\nwith the pseudo selector's argument (if it exists). The handler function should return a function that matches\na node.\n\n```js\n// A simple `:text(foo)` pseudo selector\nbill.registerPseudo('text', function(value) {\n  return function (node) {\n    return node.children\n      .filter(n =\u003e n.nodeType === NODE_TYPES.TEXT)\n      .every(node =\u003e node.element === value)\n  }\n})\n\nlet matches = bill.querySelectorAll('li:text(john)',\n  \u003cul\u003e\n    \u003cli\u003ebetsy\u003c/li\u003e\n    \u003cli\u003ejohn\u003c/li\u003e\n    \u003cli\u003eyolanda\u003c/li\u003e\n  \u003c/ul\u003e\n)\n\nmatches[0].instance // \u003cli class='bar'\u003ejohn\u003c/li\u003e\n```\n\nFor pseudoSelectors whose inner argument is a selector, you can compile it\nto a test function with `bill.compile`.\n\n```js\n// We want to test if an element has a sibling that matches\n// a selector e.g. :nextSibling(.foo)\nbill.registerPseudo('nextSibling', function (selector) {\n  let matcher = bill.compile(selector);\n  return function (node) {\n    node = node.nextSibling\n    return !!node \u0026\u0026 matcher(node)\n  }\n})\n\nlet matches = bill.querySelectorAll('li:nextSibling(li.baz)',\n  \u003cul\u003e\n    \u003cli className='foo'\u003e1\u003c/li\u003e\n    \u003cli className='bar'\u003e2\u003c/li\u003e\n    \u003cli className='baz'\u003e3\u003c/li\u003e\n  \u003c/ul\u003e\n)\n\nmatches[0].instance // \u003cli class='bar'\u003e2\u003c/li\u003e\n```\n\n#### registerNesting(nestingCombinator, handleNesting: (matcher: function) =\u003e (node: Node) =\u003e bool)\n\nSimilar to `registerPseudo` you can also register new combinator selectors (\\*, \u003e, ~, +) using the same pattern.\nThe handler function is called with the _compiled_ selector segment.\n\n__Note:__ remember that selectors are matched _right-to-left_ so the logic is generally reversed from what you\nmight expect.\n\n```js\n// lets implement the same previous sibling selector as above\n// but with a nesting selector.\nbill.registerNesting('!', test =\u003e node =\u003e {\n  node = node.nextSibling\n  return !!(node \u0026\u0026 test(node))\n})\n\nlet matches = bill.querySelectorAll('li.baz ! li',\n  \u003cul\u003e\n    \u003cli className='foo'\u003e1\u003c/li\u003e\n    \u003cli className='bar'\u003e2\u003c/li\u003e\n    \u003cli className='baz'\u003e3\u003c/li\u003e\n  \u003c/ul\u003e\n)\n\nmatches[0].instance // \u003cli class='bar'\u003e2\u003c/li\u003e\n```\n\n\n#### `NODE_TYPES` Object\n\nSet of constants that correspond to `Node.nodeType`. Useful for filtering out types of nodes while traversing a tree.\n\n- `NODE_TYPES.COMPOSITE`\n- `NODE_TYPES.DOM`\n- `NODE_TYPES.TEXT`\n\n#### `isNode() -\u003e boolean`\n\nDetermine if an object is a Node object.\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjquense%2Fbill","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjquense%2Fbill","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjquense%2Fbill/lists"}