A jQuery like API for querying React elements and rendered components.
Like jQuery the exported function creates a collection of nodes, except in this case you select React elements instead of DOM nodes.
import $ from 'teaspoon';
let $div = $(<div/>);
$div.length // 1
$div[0] // ReactElement{ type: 'div', props: {} ... }
Since there is no globally accessible "document" of React elements like there is of DOM nodes, you need to start by selecting a tree. Once you have a tree you can query it with css selectors and jQuery-like methods.
let elements = (
<MyComponent>
<MyInput/>
<MyInput/>
<div className='fun-div'>
</MyComponent>
);
var $elements = $(elements);
$elements.find('div.fun-div').length // 1
$elements.find(MyInput).length // 2
teaspoon
actually supports two types of collections, we've already seen Element Collections,
but you can also work with Component instance collections as well for querying rendered components.
let instance = ReactDOM.render(<Component/>, mountNode)
let $instance = $(instance);
$instance.dom() // HTMLElement
There is even a quick way to switch between them.
let elements = (
<MyComponent>
<MyInput/>
<MyInput/>
<div className='fun-div'>
</MyComponent>
);
var $elements = $(elements).render(); // renders `<MyComponent/>` into the DOM and returns an InstanceCollection
$elements.find(MyInput).dom() // HTMLElement{ tagName: 'input' ... }
$elements.unmount() // removes the mounted component and returns an ElementCollection
The supported selector syntax is subset of standard 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. You can also use two React
specific pseudo selectors: ':dom'
and ':composite'
to select DOM and Composite Components respectively.
Unlike normal css selectors though, React Elements often have prop values, and element types that are not serializable
to a 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?
With Component names you can use function or displayName
of the component if you trust them.
$(<Component>).render().find('div > List.foo')
Alternatively, and more robustly, you use a tagged template string.
via the $.selector
(also aliased as $.s
) function for writing complex selectors like so:
//select all `<MyList/>`s that are children of divs
$.s`div > ${List}`
//select components with `start` props equal to `min`
let min = 10
$.s`[start=${min}]`
If you don't want to use the newer syntax you can also call the selector
function directly like:
$.s('div > ', List, '.foo') // equivalent to: $.s`div > ${List}.foo`
The methods are shared by both Element and Instance Collections.
Selector creation function.
Search all descendants of the current collection, matching against the provided selector.
$(<ul><li>item 1</li></ul>).find('ul > li')
Filter the current collection matching against the provided selector.
let $list = $([
<li>1</li>,
<li className='foo'>2</li>,
<li>3</li>,
]);
$list.filter('.foo').length // 1
Return the children of the current selection, optionally filtered by those matching a provided selector.
note: rendered "Composite" components will only ever have one child since Components can only return a single node.
let $list = $(
<ul>
<li>1</li>
<li className='foo'>2</li>
<li>3</li>
</ul>
);
$list.children().length // 3
$list.children('.foo').length // 1
Test if each item in the collection matches the provided selector.
return the first item in a collection, alternatively search all collection descendants matching the provided selector and return the first match.
return the last item in a collection, alternatively search all collection descendants matching the provided selector and return the last match.
Assert that the current collection as only one item.
let $list = $(
<ul>
<li>1</li>
<li className='foo'>2</li>
<li>3</li>
</ul>
);
$list.find('li').only('li') // Error! Matched more than one <li/>
$list.find('li').only('.foo').length // 1
Find and assert that only item matches the provided selector.
let $list = $(
<ul>
<li>1</li>
<li className='foo'>2</li>
<li>3</li>
</ul>
);
$list.single('li') // Error! Matched more than one <li/>
$list.single('.foo').length // 1
Return the text content of the matched Collection.
$(<div>Hello <strong>John</strong></div).text() // "Hello John"
Returns a real JavaScript array of the collection items.
An analog to [].forEach
; iterates over the collection calling the iteratorFn
with each item, idx, and collection
$(<MyComponent/>).render()
.find('div')
.each((node, index, collection)=>{
//do something
})
An analog to [].reduce
, returns a new reduced teaspoon Collection
$(<MyComponent/>).render()
.find('div')
.reduce((current, node, index, collection)=>{
return current + ', ' + node.textContent
}, '')
ElementCollections are created when selecting ReactElements. They also have all the above "common" methods
Create an ElementCollection from an Element or array of Elements.
Renders the first element of the ElementCollection into the DOM using ReactDom.render
. By default
the component won't be added to the page document
, you can pass true
as the first parameter to render into the
document.body. Additional you can provide your own DOM node to mount the component into.
render()
returns a new InstanceCollection
let elements = (
<MyComponent>
<div className='fun-div'>
</MyComponent>
);
var $elements = $(elements).render();
$elements = $(elements).render(true); //accessible by document.querySelectorAll
$elements = $(elements).render(true, document.createElement('span')); //mounts the component to the <span/>
Use the React shallow renderer utilities to shallowly render the first element of the collection.
let MyComponent ()=> <div>Hi there!</div>
$(<MyComponent/>).find('div').length // 0
$(<MyComponent/>).shallowRender().is('div') // true
InstanceCollections are created when selecting Component instances, such as
the result of a ReactDOM.render()
call.
The public "instances" for components differ. DOM component instances
are the DOM nodes themselves, and Stateless Components technically don't have any
(we use the DOM node though). One key advantage to over the normal React
test utils is that here you can continue to chain find
and filter
on
DOM and Stateless components.
Returns the DOM nodes for each item in the Collection, if the exist
Unmount the current tree and remove it from the DOM. unmount()
returns an
ElementCollection of the root component element.
Trigger a "synthetic" (React) event on the collection items.
$(<Component/>).render().trigger('click', { target: { value: 'hello ' } }).