Sorry, I imagine that this is a simple question, but I couldn't find anything on Stack Overflow. How do you access the children of a context provider like IntlProvider from react-intl?
class Foo extends Component {
render() {
return (
<div data-test="foo-test">
...
</div>
)
}
}
const c = mount(
<Wrapper store={store}>
<Foo />
</Wrapper>
);
const res = c.find('[data-test="foo-test"]');
console.log(res.debug());
Wrapper contains the IntlProvider component and I simply cannot access foo-test. Apologies for the simplistic example, but it seems like such an obvious problem that others have come across, that I hoped someone might put me out of my misery :)
what's c.debug() print out?
Are you sure Wrapper renders its children?
c.debug() prints out the mounted component tree. No children are shown for IntlProvider, which is inside Wrapper - as if IntlProvider is not rendering its children for some reason, because if I removeIntlProvider it shows the full component tree.
That suggests it's an issue with IntlProvider; the code for it is here, it renders its children.
Can you straight copy and paste the full output of c.debug(), as well as the full contents of Wrapper? It's hard to debug without the exact code you're using in your codebase.
Thank you for having a look at this. The output for c.debug() is:
<Wrapper store={{...}}>
<Provider store={{...}}>
<Connect(LocaleProviderWrapper)>
<LocaleProviderWrapper locale="en-GB" dispatch={[Function]}>
<IntlProvider locale="en-GB" messages={{...}} defaultLocale="en-GB" />
</LocaleProviderWrapper>
</Connect(LocaleProviderWrapper)>
</Provider>
</Wrapper>
Contents of Wrapper is:
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { Provider } from 'react-redux';
import ThemeProvider from "../../theme/Theme.jsx";
import LocaleProviderWrapper from "./locale-provider-wrapper.jsx";
const propTypes = {
children: PropTypes.element.isRequired,
store: PropTypes.object.isRequired,
};
class Wrapper extends Component {
getStore() {
return this.props.store;
}
render() {
return (
<Provider store={this.props.store}>
<LocaleProviderWrapper>
<ThemeProvider>{this.props.children}</ThemeProvider>
</LocaleProviderWrapper>
</Provider>
);
}
}
Wrapper.propTypes = propTypes;
export default Wrapper;
Thanks!
OK, so. if you're mounting <Wrapper>{children}</Wrapper>, then i'd expect to see the following debug:
<Wrapper store={{...}}>
<Provider store={{...}}>
<Connect(LocaleProviderWrapper)>
<LocaleProviderWrapper locale="en-GB" dispatch={[Function]}>
<ThemeProvider>{children}</ThemeProvider>
</LocaleProviderWrapper>
</Connect(LocaleProviderWrapper)>
</Provider>
</Wrapper>
for whatever value of children you provided.
What are you seeing that's different?
Well, inside LocaleProviderWrapper is:
import React from 'react';
import PropTypes from "prop-types";
import { IntlProvider } from "react-intl";
import { connect } from "react-redux";
const config = require('tidee-life-config');
const core = require('tidee-life-core');
import { getLoggedInUserLocale } from "../../reducers/logged-in-user-reducer";
import { loadTranslatedMessages } from "../../helpers/loadTranslation";
const LocaleProviderWrapper = ({ children, locale }) => {
const reactIntlLocale = core.func.reactIntlLocaleConverter(locale || config.defaultLocale);
const messages = loadTranslatedMessages({ locale: reactIntlLocale });
return (
<IntlProvider locale={reactIntlLocale} messages={messages} defaultLocale={config.defaultLocale}>
{children}
</IntlProvider>
)
};
const propTypes = {
children: PropTypes.element.isRequired,
locale: PropTypes.string.isRequired,
};
LocaleProviderWrapper.propTypes = propTypes;
const mapStateToProps = state => ({
locale: getLoggedInUserLocale(state),
});
export default connect(mapStateToProps)(LocaleProviderWrapper);
So, given the output I am getting from c.debug(), it is stopping at <IntlProvider /> and not continuing to render/output the children.
Hmm, interesting. If you c.find(LocaleProviderWrapper).debug(), what do you get?
If I do c.find(LocaleProviderWrapper).debug() I get the following output:
<Connect(LocaleProviderWrapper)>
<LocaleProviderWrapper locale="en-GB" dispatch={[Function]}>
<IntlProvider locale="en-GB" messages={{...}} defaultLocale="en-GB" />
</LocaleProviderWrapper>
</Connect(LocaleProviderWrapper)>
If I do c.find('IntlProvider').debug() I get the following output:
<IntlProvider locale="en-GB" messages={{...}} defaultLocale="en-GB" />
That looks to me like IntlProvider isn't rendering its children, or isn't receiving any :-/
It is definitely receiving children, just not rendering them.
The strange thing is that i have IntlProvider running fine in the application. It's just when I use it in tests with Jest + Enzyme that it fails to render the children.
It would be interesting if anyone could reproduce this, or not, using the code above.
Is there any chance that that component is doing something different in node, or that you’re somehow mocking it out in jest?
No, I am using IntlProvider as it is being used in the application. I can only think that perhaps enzyme handles the component differently - perhaps something context related?
Given that the code for it linked above unconditionally renders its children, i'm really not sure how that'd be possible. For mount, specifically, enzyme just uses ReactDOM.render.
If I run this code, then debug() does not show the div child:
import React from 'react';
import { mount } from 'enzyme';
import { IntlProvider } from "react-intl";
const messages = { 'account.activation.error': 'This activation link is not valid.' };
describe('IntlProvider', () => {
test('Not rendering children', async () => {
const component = mount(
<IntlProvider locale="en-GB" messages={messages}>
<div data-test="foo-test">hello</div>
</IntlProvider>
);
console.log(component.find('IntlProvider').debug());
});
});
Do you get the same?
@joetidee I didn't see any concern while I tried to test
https://codesandbox.io/s/6zn4lmpmnw
Please change expect statements and show us what is not working (by forking code sandbox, and send over)
@pgangwani Hey, thanks for setting up the sandbox. I forked it, paired it down to remove unnecessary code/packages, then copied it back to my environment. I have installed all the packages so that everything is identical - it works in the sandbox, but doesn't work when run locally using npx jest foobar.test.js !! What on earth is going on :/ ?
@joetidee :
Relax !!
Follow me:
Hello, ok, so I copied this codesandbox locally https://codesandbox.io/s/z6pvmo927p. When I run it, i get:
tidee-air:tidee-life-web joe$ npx jest foobar.test.js
FAIL src/pages/login/__tests__/foobar.test.js
IntlProvider
✕ I see foo-test with mount (107ms)
● IntlProvider › I see foo-test with mount
Method “text” is meant to be run on 1 node. 0 found instead.
18 | );
19 | console.log("Mount Debug:", component.debug());
> 20 | expect(component.find('[data-test="foo-test"]').text()).toBe("hello");
| ^
21 | });
22 | });
23 |
at ReactWrapper.single (node_modules/enzyme/build/ReactWrapper.js:1718:17)
at ReactWrapper.text (node_modules/enzyme/build/ReactWrapper.js:862:21)
at Object.text (src/pages/login/__tests__/foobar.test.js:20:57)
console.log src/pages/login/__tests__/foobar.test.js:19
Mount Debug: <IntlProvider locale="en-GB" messages={{...}} />
Test Suites: 1 failed, 1 total
Tests: 1 failed, 1 total
Snapshots: 0 total
Time: 6.711s
My jest.setup.js contains this:
import { configure } from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';
configure({ adapter: new Adapter() });
Do you need any other information?
Found the issue. I had a manual mock for react-intl in a __mocks__ directory. Once I deleted this, the mount gave the correct output. Thank you for your help :)
Most helpful comment
@joetidee I didn't see any concern while I tried to test
https://codesandbox.io/s/6zn4lmpmnw
Please change expect statements and show us what is not working (by forking code sandbox, and send over)