Matthew Gertner
Matthew Gertner

Reputation: 4537

Passing context to a dynamically added child in ReactJS

I'm trying to pass context to a React component, but because I am testing with Enzyme, I'd like to add the component to its parent dynamically so I can examine its state. The test looks like this:

describe('<BeaconConfig />', () => {
  it('inherits the config from BeaconConfig', () => {
    mount(<BeaconConfig persistent><div id="parent"></div></BeaconConfig>, { attachTo: document.body });
    const wrapper = mount(<Beacon/>, { attachTo: document.getElementById('parent') });
    expect(wrapper.state('persistent')).to.be(true);
  });
});

The test fails because the persistent property of the Beacon component's state is undefined, although it should be inherited from BeaconConfig via context.

When I try putting Beacon directly inside the JSX when I mount BeaconConfig then it works fine, but in this case Enzyme won't let me get at the Beacon component state since it isn't the root.

Is it normal that React isn't propagating the context to my component when I add it dynamically to its parent?

Upvotes: 1

Views: 722

Answers (2)

Matthew Gertner
Matthew Gertner

Reputation: 4537

Here is the complete test I ended up using:

describe('Context', () => {
  let wrapper;
  let parent;
  const open = stub().returns({});
  const mockIndexedDB = { open };
  const config = mount(<BeaconConfig persistent position="bottom" indexedDB={mockIndexedDB} />);

  beforeEach(() => {
    parent = document.createElement('div');
    document.body.appendChild(parent);
    wrapper = mount(<Beacon>some text</Beacon>, {
      attachTo: parent,
      context: config.instance().getChildContext()
    });
  });

  afterEach(() => {
    wrapper.detach();
    document.body.removeChild(document.body.firstChild);
  });

  it('overrides the default values with the context if any', () => {
    expect(wrapper.state('persistent')).to.be(true);
    expect(wrapper.state('position')).to.be('bottom');
    expect(open.calledOnce).to.equal(true);
  });
});

@STRML had a good suggestion but I don't think that it is possible to access the state of a non-root component.

Instead I instantiate BeaconConfig in isolation and grab it's child context, passing that manually to Beacon using the options parameter of mount. This tests that BeaconConfig creates the right child context and that Beacon uses the context correctly. It doesn't test that BeaconConfig passes the config down to Beacon when the latter is a descendant, but we can presumably take that for granted since it is basic React functionality.

Upvotes: 0

STRML
STRML

Reputation: 942

It is normal that React isn't propagating the context - it doesn't look at the DOM and diff it with its VDOM in that way.

You'll want make it a child in the initial mount, and use the .find() or .children() methods of the MountWrapper (docs) to dig through the children, find the Beacon and do your assertions.

Upvotes: 1

Related Questions