/reselector

Use React Components in css selectors

Primary LanguageJavaScriptOtherNOASSERTION

reselector

Travis branch Coverage Status branch npm version npm license Greenkeeper badge

Installation

npm install --save-dev reselector

Usage

You can use it as a babel-plugin or as the runtime function, or both.

babel-plugin

Add reselector to the plugin list in .babelrc for your client code. For example:

{
    presets: ['react'],
    env: {
        test: {
            plugins: [
                'reselector/babel',
            ],
        },
    },
}

Find Components in the DOM

Use select function to build any css selector by React Components.

Just a simple example with jest

import React from 'react'
import {render} from 'react-dom'
import {select} from 'reselector'

const Text = ({children}) => <p>{children}</p>

const Button = ({children}) => (
  <button>
    <Text>{children}</Text>
  </button>
)

describe('Button', () => {
  beforeEach(() => document.body.innerHTML = '<div id="app" />')

  it('should render a text', () => {
    const text = 'hello world!'
    render(<Button>{text}</Button>, window.app)

    const node = document.querySelector(select`${Button} > ${Text}`)
    expect(node.textContent).toBe(text)
  })
})

enzyme

It also works with libraries like enzyme out of the box.

import {render} from 'enzyme'

import Button from './Button'
import Text from './Text'

describe('Button', () => {
  it('should render a text', () => {
    const text = 'hello world!'
    const wrapper = render(<Button>{text}</Button>)

    expect(wrapper.find(select`${Button} > ${Text}`).text()).toBe(text)
  })
})

Babel

If you have a chanсe to transpile components with this plugin for your unit tests/autotests, you can import React Component as is.

import {select} from 'reselector'

import MyComponent from './MyComponent'
import MyButton from './MyButton'

/**
 * [data-tid="dadad"] [data-tid="czczx"]
 */
console.log(select`${MyComponent} ${MyButton}`)

/**
 * .myClassName > [data-tid="czczx"]
 */
console.log(select`.myClassName > ${MyButton}`)

Runtime (just node.js, without babel)

It may be useful for autotests (for example, with PageObjects) when you don't need to transpile code. Just use resolve or resolveBy functions to get Components' selector.

const {resolve, select} = require('reselector')

const {MyComponent} = resolve(require.resolve('./MyComponent'))
const {MyButton} = resolve(require.resolve('./MyButton'))

/**
 * [data-tid="dadad"] [data-tid="czczx"]
 */
console.log(select`${MyComponent} ${MyButton}`)

/**
 * .myClassName > [data-tid="czczx"]
 */
console.log(select`.myClassName > ${MyButton}`)

With resolveBy:

const {resolveBy, select} = require('reselector')

const resolve = resolveBy(require.resolve)

const {MyComponent} = resolve('./MyComponent')
const {MyButton} = resolve('./MyButton')

/**
 * [data-tid="dadad"] [data-tid="czczx"]
 */
console.log(select`${MyComponent} ${MyButton}`)

/**
 * .myClassName > [data-tid="czczx"]
 */
console.log(select`.myClassName > ${MyButton}`)

How it works

This plugin tries to find all React Component declarations and to add data-{hash} attribute with the uniq hash-id to the Component's root node. It also saves this hash as the static property for the Component, so get function uses this property to build a selector.

Configuration

You can provide some options via reselector.config.js, rc-files or in package.json.

name

{string = 'data-tid'} Test-attribute name, should not be empty.

You can define your own attribute name, for example

module.exports = {name: 'my-test-id'}

With that, you'll get attributes on nodes like <button my-test-id="c7b7156f" />.

env

{boolean = false} Just set it on true to control attributes appending by process.env.RESELECTOR. So it will no append hashes at runtime when process.env.RESELECTOR !== 'true'.

For example:

module.exports = {env: true}

envName

{string = process.env.BABEL_ENV || process.env.NODE_ENV || 'development'}

syntaxes

{string[]} By default, this plugin works with these syntax list:

@babel/plugin-syntax-async-generators
@babel/plugin-syntax-class-properties
@babel/plugin-syntax-decorators
@babel/plugin-syntax-dynamic-import
@babel/plugin-syntax-export-default-from
@babel/plugin-syntax-export-namespace-from
@babel/plugin-syntax-flow
@babel/plugin-syntax-function-bind
@babel/plugin-syntax-import-meta
@babel/plugin-syntax-jsx
@babel/plugin-syntax-nullish-coalescing-operator
@babel/plugin-syntax-numeric-separator
@babel/plugin-syntax-object-rest-spread
@babel/plugin-syntax-optional-catch-binding
@babel/plugin-syntax-optional-chaining
@babel/plugin-syntax-pipeline-operator
@babel/plugin-syntax-throw-expressions

But you can declare your own syntax list, for example:

// .reselectorrc.js

module.exports = {
  syntaxes: [
    '@babel/plugin-syntax-async-generators',
    '@babel/plugin-syntax-class-properties',
    '@babel/plugin-syntax-decorators',
    '@babel/plugin-syntax-dynamic-import',
    '@babel/plugin-syntax-export-default-from',
    '@babel/plugin-syntax-export-namespace-from',
    '@babel/plugin-syntax-flow',
    '@babel/plugin-syntax-function-bind',
    '@babel/plugin-syntax-import-meta',
    '@babel/plugin-syntax-jsx',
    '@babel/plugin-syntax-nullish-coalescing-operator',
    '@babel/plugin-syntax-numeric-separator',
    '@babel/plugin-syntax-object-rest-spread',
    '@babel/plugin-syntax-optional-catch-binding',
    '@babel/plugin-syntax-optional-chaining',
    '@babel/plugin-syntax-pipeline-operator',
    '@babel/plugin-syntax-throw-expressions',
  ],
}

###Custom configuration You also can change base configuration in your .reselectorrc.js. Example:

// .reselectorrc.js

module.exports = function configurate(baseConfig) {
    const tsxSyntax = [
      '@babel/plugin-syntax-typescrypt',
      {
        isTSX: true
      }
    ]

    return Object.assign(baseConfig, {
      syntaxes: baseConfig.syntaxes.concat([tsxSyntax])
    })
}