Describe the bug
A clear and concise description of what the bug is.
I've setup versions enzyme 3.5.0 and enzyme-adapter-react-16 1.3.0.
I've created an enzyme test in this commit https://github.com/polkadot-js/apps/pull/265/commits/0d048c094b91762ac6a7812f5d4c5899b71b5af5 that mounts the component that passes. If I run a test with expect(wrapper.get(0)).toBe(1); it will show me that the component includes the correct props along with provided fixtures <Signer queue={[{"id": "test", "rpc": {"isSigned": true}, "status": "test"}]} t={[Function mockT]} />.
So far the test I've written that checks expect(wrapper).toHaveLength(1); is passing successfully.
However, I want to run a test to check that the <Modal className='ui--signer-Signer' ... (see https://github.com/polkadot-js/apps/blob/master/packages/ui-signer/src/Modal.tsx#L89) renders correctly, but it only renders when this.state.currentItem and this.state.currentItem.rpc.isSigned (see https://github.com/polkadot-js/apps/blob/master/packages/ui-signer/src/Modal.tsx#L83) are defined and true. So in the commit I created fixtures for the the currentItem state value, and wrote the following test to set the state with setState, update the component, and then check that the state has changed. But it doesn't appear to work, because the test results report that currentItem is still undefined instead of being the value I set to the fixtureCurrentItemState variable.
it('sets the state of the component', () => {
wrapper.setState({ currentItem: fixtureCurrentItemState });
wrapper = wrapper.update(); // immutable usage
expect(wrapper.state('currentItem')).toEqual(fixtureCurrentItemState);
console.log(wrapper.debug());
});
Note that I tried debugging with console.log(wrapper.debug()); and console.log(wrapper.html());, which I've used in the past without any issues, but neither of them output anything, so as an alternative I was able to check the state by running expect(wrapper.state()).toEqual(1);, which returned {"currentItem": undefined, "password": "", "unlockError": null}
To Reproduce
Steps to reproduce the behavior:
yarn; yarn run build; yarn run test;.skip from the above mentioned testsExpected behavior
I expected setState to set the state
Screenshots
Code used screenshot:

Failing test screenshot:

Desktop (please complete the following information):
fwiw, wrapper = wrapper.update(); isn't required; "immutable" just means you have to re-find from the root.
setState is async; what happens if you do:
wrapper.setState({ currentItem: fixtureCurrentItemState }, () => {
wrapper.update();
expect(wrapper.state('currentItem')).toEqual(fixtureCurrentItemState);
console.log(wrapper.debug());
});
Thanks. I tried that before and I just tried it again but it doesn't resolve the issue. I'll keep trying...
@ljharb @jacogr I tried changing the code to the following, using callbacks, promises, and async await, but the state of currentItem never changes from being undefined:
it('set the state of the component using callbacks', (done) => {
wrapper.setState({ currentItem: fixtureCurrentItemState }, () => {
expect(wrapper.update().state('currentItem')).toEqual(fixtureCurrentItemState);
console.log(wrapper.debug());
done();
});
});
it('set the state of the component using promises', () => {
Promise.resolve(wrapper.setState({ currentItem: fixtureCurrentItemState }))
.then(_ => expect(wrapper.update().state('currentItem')).toEqual(fixtureCurrentItemState))
.catch((error) => console.log('error', error));
});
it('set the state of the component using async await', async () => {
try {
await wrapper.setState({ currentItem: fixtureCurrentItemState });
expect(wrapper.update().state('currentItem')).toEqual(fixtureCurrentItemState);
} catch (error) {
console.error(error);
}
});
Can you downgrade either, or both, of enzyme or the react 16 adapter, and see if this is a regression?
@ljharb I tried again and it doesn't work with the following versions. I tried using Callbacks, Promises, and Async Await, but cannot change the state using the below code.
it('set the state of the component using callbacks and anonymous function', (done) => {
wrapper.setState({ currentItem: fixtureCurrentItemState }, () => {
expect(wrapper.update().state('currentItem')).toEqual(fixtureCurrentItemState);
expect(wrapper.update().find('.ui--signer-Signer')).toHaveLength(1);
done();
});
});
it('set the state of the component using callbacks and named functions', (done) => {
const checkExpectation = (done) => {
expect(wrapper.update().state('currentItem')).toEqual(fixtureCurrentItemState);
done();
};
const doAsyncAction = (callback, done) => {
wrapper.setState({ currentItem: fixtureCurrentItemState }, () => {
callback(done);
});
};
doAsyncAction(checkExpectation, done);
});
it('set the state of the component using promises', () => {
Promise.resolve(wrapper.setState({ currentItem: fixtureCurrentItemState }))
.then(_ => {
expect(wrapper.update().state('currentItem')).toEqual(fixtureCurrentItemState);
expect(wrapper.update().find('.ui--signer-Signer')).toHaveLength(1);
})
.catch((error) => console.log('error', error));
});
it('set the state of the component using async await', async () => {
try {
await wrapper.setState({ currentItem: fixtureCurrentItemState });
expect(wrapper.update().state('currentItem')).toEqual(fixtureCurrentItemState);
expect(wrapper.update().find('.ui--signer-Signer')).toHaveLength(1);
} catch (error) {
console.error(error);
}
});
Yeah I seem to be running into this same issue. I have an instance method that sets state, so I run the instance method and ensure the the correct state gets set:
wrapper.instance().reviewApp({ uid: 'some_id' });
expect(History.push).toHaveBeenCalledWith('/mobile/apps/detail/some_id');
expect(wrapper.state('adcOpen')).toBeTruthy(); //is false
Enzyme 3.5.0 enzyme-adapter-react-16 1.3.1
I changed it to manually call setState on the wrapper, but the state still isn't set.
wrapper.setState({adcOpen: true});
expect(History.push).toHaveBeenCalledWith('/mobile/apps/detail/some_id');
expect(wrapper.state('adcOpen')).toBeTruthy(); //Still false
This still doesn't set the state correctly. I even tried it with the callback on setState and still no-go.
wrapper.setState({adcOpen: true}, () => {
expect(History.push).toHaveBeenCalledWith('/mobile/apps/detail/some_id');
expect(wrapper.state('adcOpen')).toBeTruthy(); //Still false
});
EDIT: Also, I'm on React 16.4.2
So downgrading back down to 3.3.0 works for me. So I think it's enzyme causing this?
@comfroels can you confirm which exact version it breaks on? does it work on v3.4.0?
@ltfschoen I'm trying to reproduce your error, I think what's happening in your test is that getDerivedStateFromProps is being called when state gets updated and is reseting the value of currentItem.
A shorter failing test would be
it('set the state of the component using callbacks and anonymous function', (done) => {
class TestComponent extends React.PureComponent<Props, State> {
constructor (props) {
super(props)
this.state = {};
}
static getDerivedStateFromProps(props, { currentItem }) {
return {
currentItem: null,
};
}
render() {
return (
<div>Rendered</div>
);
}
}
const wrapper = mount(<TestComponent />, {})
wrapper.setState({ currentItem: 'foo' }, () => {
expect(wrapper.update().state('currentItem')).to.equal('foo');
done();
});
});
I believe this makes sense to fail (cc @ljharb ), I think what the test should do is instead update props.queue so that getDerivedStateFromProps returns currentItem correctly
In the shorter example, getDerivedStateFromProps definitely forces currentItem to always be null - unless React itself opts not to call getDerivedStateFromProps when the props haven't changed?
React calls getDerivedStateFromProps when a component receives either new props or state. In the original example, Signer has a getDerivedStateFromProps that assigns state.currentItem from a prop value.
Then given that, is this still an issue, or should it be closed?
I believe this can be closed, not sure about @comfroels issue
@ljharb @jgzuke I made changes to my code in the following commit and then built and run the tests with yarn run build; yarn run test
However I still have the following problems:
console.log(wrapper.debug()); (which is included in my latest commit), it simply displays console.log packages/ui-signer/src/Modal.spec.js:45 and then a couple of blank lines




@ltfschoen thanks; in the future please post copypasted text and not screenshots of code, which are much harder to read.
Separately, await wrapper.setState() is meaningless, since it (and setProps) doesn't return a promise. You have to use the callback.
@ltfschoen in your linked commit console.log(wrapper.debug()); will output blank lines since the component is returning null and so has no output, this is expected.
if (!currentItem || currentItem.rpc.isSigned !== true) {
return null;
}
If you debug after setting the currentItem you instead get
<Modal className="ui--signer-Signer" dimmer="inverted" open={true} centered={true} closeOnDimmerClick={true} closeOnDocumentClick={false} eventPool="Modal">
<Translate(Extrinsic) value={{...}}>
<Translate(Unlock) autoFocus={true} error={{...}} onChange={[Function]} onKeyDown={[Function]} password="" value={{...}} tabIndex={1} />
</Translate(Extrinsic)>
<ModalActions>
<ButtonGroup>
<Button isNegative={true} onClick={[Function]} tabIndex={3} text="extrinsic.cancel" />
<Translate(ButtonOr) />
<div>
<Button className="ui--signer-Signer-Submit" isPrimary={true} onClick={[Function]} tabIndex={2} text="extrinsic.signedSend" />
</div>
</ButtonGroup>
</ModalActions>
</Modal>
Sorry, haven't responded to this yet, the component in question for me, doesn't use getDerivedStateFromProps at all. I'll be probably playing around more with this today. So I'll check enzyme 3.4 and see if I have the same issue. I was going to try to get us up to React 16.5 today
@jgzuke Thanks I've got debug working now too with the following code. This is a big step.
it('creates the element', async () => {
try {
await wrapper.setState({
currentItem: fixtureCurrentItemState,
password: '123',
unlockError: null
});
await wrapper.setProps({
queue: fixtureQueueProp
});
wrapper.update();
expect(wrapper).toHaveLength(1);
console.log(wrapper.debug());
} catch (error) {
console.error(error);
}
});
A few notes from what I've found so far today:
Upgrading to enzyme-adapter-react-16 from 1.1.1 to 1.5.0 worked fine (this was without updating enzyme)
upgrading to enzyme 3.4.0 and using enzyme-adapter-react-16 1.5.0 causes the same issue I had above.
upgrading to enzyme 3.4.0 and using enzyme-adapter-react-16 1.1.1 seems to also cause the same issue.
This is all on React 16.4.2
Also, sorry I haven't shared much component code. This is a private repo, with an NDA on the code. So I'm trying to give enough detail to you all. Let me know if there are any other things I can help with.
@ltfschoen again, await is useless on setState and setProps, since neither returns a promise.
@ltfschoen is this issue resolved then?
@comfroels I think you might be having a separate issue, it would be awesome if you could pinpoint the bug to a failing test similar to those found in https://github.com/airbnb/enzyme/blob/master/packages/enzyme-test-suite/test/ReactWrapper-spec.jsx, as it stands without seeing the component I don't have any other ideas what might be wrong
@jgzuke Yes, I've established why it wasn't setting state. I've changed my code to use callbacks with the following, but I'd like to refactor it to use promises instead.
it('testing', (done) => {
try {
wrapper.setState({ currentItem: fixtureCurrentItemState }, () => {
wrapper.setProps({
queue: fixtureQueueProp
}, () => {
wrapper.update();
expect(wrapper.state('currentItem')).toEqual(expectedNextCurrentItemState);
expect(wrapper.find('.ui--signer-Signer')).toHaveLength(1);
console.log(wrapper.debug());
done();
});
});
} catch (error) {
console.error(error);
}
});
I'm now trying to use mount instead of shallow, so I've mocked i18n with the following to overcome error TypeError: Cannot read property 'options' of undefined
jest.mock('react-i18next', () => ({
// this mock makes sure any components using the translate HoC receive the t function as a prop
translate: () => Component => props => <Component t={k => k} {...props} />
}));
But now it's giving error TypeError: Cannot read property 'Symbol(Symbol.iterator)' of undefined
Thanks for that link, it's awesome!
Yes, I do believe I have a bad pattern that I'm using, as upgrading to React 16.5 causes me a very similar issue. You can close this, thanks for all the help ladies/gents!
@ltfschoen don鈥檛 use try/catch on your code there, return a new Promise instead, that resolves on the callback:
return new Promise((resolve) => {
wrapper.setState({ currentItem: fixtureCurrentItemState }, () => {
wrapper.setProps({
queue: fixtureQueueProp
}, resolve);
});
}).then(() => {
wrapper.update();
expect(wrapper.state('currentItem')).toEqual(expectedNextCurrentItemState);
expect(wrapper.find('.ui--signer-Signer')).toHaveLength(1);
});
@comfroels thanks!
you can setState like this
it('sets the state of the component', () => {
const componentInstance = wrapper
.childAt(0)
.instance();
componentInstance.setState({ currentItem: fixtureCurrentItemState });
wrapper = wrapper.update();
expect(wrapper.state('currentItem')).toEqual(fixtureCurrentItemState);
});
will pass
see my gist
https://gist.github.com/elvisgiv/e9056bde1cfbf5bcfecdfb32c10577ea
it('checks state setting', async () => {
const wrapper = shallow(<Link title="Click"/>);
try {
await wrapper.setState({value: 'I need to do something...'});
const textInput = wrapper.find('TextInput').first();
expect(textInput.prop('value')).toStrictEqual('I need to do something...');
console.log(wrapper.debug());
} catch (error) {
console.error(error);
}
});
This works for me
Most helpful comment
Yeah I seem to be running into this same issue. I have an instance method that sets state, so I run the instance method and ensure the the correct state gets set:
Enzyme 3.5.0 enzyme-adapter-react-16 1.3.1
I changed it to manually call
setStateon the wrapper, but the state still isn't set.This still doesn't set the state correctly. I even tried it with the callback on
setStateand still no-go.EDIT: Also, I'm on React 16.4.2