Skip to content

Instantly share code, notes, and snippets.

@matthieuprat
Last active September 23, 2021 16:29
Show Gist options
  • Star 20 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save matthieuprat/5fd37abbd4a4002e6cfe0c73ae54cda8 to your computer and use it in GitHub Desktop.
Save matthieuprat/5fd37abbd4a4002e6cfe0c73ae54cda8 to your computer and use it in GitHub Desktop.
Until operator for Enzyme's shallow wrapper

Usage

import until from 'path/to/until'
import { shallow } from 'enzyme'

const EnhancedFoo = compose(
  connect(...),
  withHandlers(...),
  withContext(...)
)(Foo)
shallow(<EnhancedFoo />)::until(Foo)

If you want to avoid importing until altogether and/or if you're not keen on the bind operator, you can register until on ShallowWrapper's prototype. To do so, you may drop the following lines in a test setup file:

import ShallowWrapper from 'enzyme/ShallowWrapper'
import until from 'path/to/until'

ShallowWrapper.prototype.until = until

Now, in your tests, you can do:

import { shallow } from 'enzyme'

shallow(<EnhancedFoo />).until(Foo)
function shallowRecursively(wrapper, selector, { context }) {
// Do not try to shallow render empty nodes and host elements
// (a.k.a primitives). Simply return the wrapper in that case.
if (wrapper.isEmptyRender() || typeof wrapper.node.type === 'string')
return wrapper
// Retrieve the context so that we can pass it down to the next wrapper.
const instance = wrapper.root.instance()
if (instance.getChildContext)
context = { ...context, ...instance.getChildContext() }
const nextWrapper = wrapper.shallow({ context })
return selector && wrapper.is(selector)
? nextWrapper
: shallowRecursively(nextWrapper, selector, { context })
}
export default function until(selector, { context } = this.options) {
return this.single('until', () => shallowRecursively(this, selector, { context }))
}
import PropTypes from 'prop-types'
import React from 'react'
import { shallow } from 'enzyme'
import until from './until'
describe('until', () => {
const Div = () => <div />
const Foo = () => <Div />
const hoc = Component => () => <Component />
it('shallow renders the current wrapper one level deep', () => {
const EnhancedFoo = hoc(Foo)
const wrapper = shallow(<EnhancedFoo />)::until(Foo)
expect(wrapper.contains(<Div />)).toBe(true)
})
it('shallow renders the current wrapper several levels deep', () => {
const EnhancedFoo = hoc(hoc(hoc(Foo)))
const wrapper = shallow(<EnhancedFoo />)::until(Foo)
expect(wrapper.contains(<Div />)).toBe(true)
})
it('supports string selectors', () => {
const EnhancedFoo = hoc(Foo)
const wrapper = shallow(<EnhancedFoo />)::until('Foo')
expect(wrapper.contains(<Div />)).toBe(true)
})
it('shallow renders as much as possible when no selector is provided', () => {
const EnhancedFoo = hoc(hoc(Foo))
const wrapper = shallow(<EnhancedFoo />)::until()
expect(wrapper.contains(<div />)).toBe(true)
})
it('stops shallow rendering when the wrapper is empty', () => {
const NullComponent = () => null
const wrapper = shallow(<NullComponent />)::until()
expect(wrapper.isEmptyRender()).toBe(true)
})
it('shallow renders the current wrapper even if the selector never matches', () => {
const EnhancedFoo = hoc(Foo)
const wrapper = shallow(<EnhancedFoo />)::until('Whatever')
expect(wrapper.contains(<div />)).toBe(true)
})
it('stops shallow rendering when it encounters a DOM element', () => {
const Container = () => <div><Foo /></div>
let wrapper = shallow(<Container />)::until(Foo)
expect(wrapper.contains(<div><Foo /></div>)).toBe(true)
const EnhancedContainer = hoc(hoc(Container))
wrapper = shallow(<EnhancedContainer />)::until(Foo)
expect(wrapper.contains(<div><Foo /></div>)).toBe(true)
})
it('throws when it is called on an empty wrapper', () => {
expect(() => shallow(<Foo />).find('Whatever')::until()).toThrow(
Error,
'Method “until” is only meant to be run on a single node. 0 found instead.',
)
})
it('shallow renders non-root wrappers', () => {
const Container = () => <div><Foo /></div>
const wrapper = shallow(<Container />).find(Foo)::until()
expect(wrapper.contains(<div />)).toBe(true)
})
describe('with context', () => {
const Foo = () => <Div />
Foo.contextTypes = { quux: PropTypes.string }
class Bar extends React.Component {
static childContextTypes = { quux: PropTypes.string }
getChildContext = () => ({ quux: 'quux' })
render = () => <Foo />
}
it('passes down context from the root component', () => {
const EnhancedFoo = hoc(Foo)
const wrapper = shallow(<EnhancedFoo />, {
context: { quux: 'quux' },
})::until(Foo)
expect(wrapper.context('quux')).toBe('quux')
expect(wrapper.contains(<Div />)).toBe(true)
})
it('passes down context from an intermediary component', () => {
const EnhancedBar = hoc(Bar)
const wrapper = shallow(<EnhancedBar />)::until(Foo)
expect(wrapper.context('quux')).toBe('quux')
expect(wrapper.contains(<Div />)).toBe(true)
})
})
})
@morgs32
Copy link

morgs32 commented Jul 24, 2017

@matthieuprat you are the man. This was immensely helpful

@Stupidism
Copy link

Errors with jest@21 enzyme@3 react@16

Attempted to access ShallowWrapper::options, which was previously a private property on
            Enzyme ShallowWrapper instances, but is no longer and should not be relied upon.

@Stupidism
Copy link

I couldn't modify context correctly. However, since I don't need to use context at all, I just simplified your code with newest jest, enzyme and react:


function shallowRecursively(wrapper, selector) {
  // Do not try to shallow render empty nodes and host elements
  // (a.k.a primitives). Simply return the wrapper in that case.
  if (wrapper.isEmptyRender() || typeof wrapper.getElement() .type === 'string')
    return wrapper

  const nextWrapper = wrapper.dive();

  return selector && wrapper.is(selector)
    ? nextWrapper
    : shallowRecursively(nextWrapper, selector)
}

export default function until(selector) {
  return this.single('until', () => shallowRecursively(this, selector))
}

@MartinDawson
Copy link

Enzyme 3 doesn't allow extending shallow wrapper anymore.

@Stupidism
Copy link

@matthieuprat Do you have any thoughts how to keep compatible with enzyme@3?

@Stupidism
Copy link

I put my simplified until on npm. Welcome to add context function.

enzyme-shallow-until

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment