If testing a component that uses Formik using Enzyme's .simulate('change' method, Formik warns there is no name (and receives undefined as the element?)
The same code works in an actual browser. Not really sure that this is a Formik issue, it might be what Enzyme passes as the event that isn't correct? 🤷♂️
I think this is as simple a reproduction case as I can make.
import React from 'react';
import {mount} from 'enzyme';
import {Formik} from 'formik';
describe('Sign In View', () => { it('works?', () => {
const component = mount(
<Formik initialValues={{email: ''}}>
{({handleChange}) => (
<form noValidate>
<input id="email" type="email" name="email" onChange={handleChange} />
</form>
)}
</Formik>,
);
component
.find('#email')
.first()
.simulate('change', {target: {value: ''}});
expect(component.find('input')).toHaveLength(1);
});
});
The test itself passes because I'm not really testing anything interesting yet, but during the test I get:
console.warn node_modules/formik/dist/formik.cjs.development.js:543
Warning: Formik called `handleChange`, but you forgot to pass an `id` or `name` attribute to your input:
undefined
Formik cannot determine which value to update. For more info see https://github.com/jaredpalmer/formik#handlechange-e-reactchangeeventany--void
If I log what is passed to handleChange it seems to be:
SyntheticEvent {
dispatchConfig:
{ phasedRegistrationNames: { bubbled: 'onChange', captured: 'onChangeCapture' },
dependencies:
[ 'blur',
'change',
'click',
'focus',
'input',
'keydown',
'keyup',
'selectionchange' ] },
_targetInst:
FiberNode {
tag: 5,
key: null,
elementType: 'input',
type: 'input',
stateNode:
HTMLInputElement {
'__reactInternalInstance$j3zzgmq2kne': [Circular],
'__reactEventHandlers$j3zzgmq2kne': [Object],
_wrapperState: [Object],
value: [Getter/Setter],
_valueTracker: [Object] },
return:
FiberNode {
tag: 5,
key: null,
elementType: 'form',
type: 'form',
stateNode: [Object],
return: [Object],
child: [Circular],
sibling: null,
index: 0,
ref: null,
pendingProps: [Object],
memoizedProps: [Object],
updateQueue: null,
memoizedState: null,
firstContextDependency: null,
mode: 0,
effectTag: 0,
nextEffect: null,
firstEffect: null,
lastEffect: null,
expirationTime: 0,
childExpirationTime: 0,
alternate: null,
actualDuration: 0,
actualStartTime: -1,
selfBaseDuration: 0,
treeBaseDuration: 0,
_debugID: 55,
_debugSource: null,
_debugOwner: [Object],
_debugIsCurrentlyTiming: false },
child: null,
sibling: null,
index: 0,
ref: null,
pendingProps:
{ id: 'email',
type: 'email',
name: 'email',
onChange: [Function: logChange] },
memoizedProps:
{ id: 'email',
type: 'email',
name: 'email',
onChange: [Function: logChange] },
updateQueue: null,
memoizedState: null,
firstContextDependency: null,
mode: 0,
effectTag: 0,
nextEffect: null,
firstEffect: null,
lastEffect: null,
expirationTime: 0,
childExpirationTime: 0,
alternate: null,
actualDuration: 0,
actualStartTime: -1,
selfBaseDuration: 0,
treeBaseDuration: 0,
_debugID: 56,
_debugSource:
{ fileName: '/Users/jvalore/Projects/business_continuity/management_ui/src/views/sign-in.spec.js',
lineNumber: 73 },
_debugOwner:
FiberNode {
tag: 1,
key: null,
elementType: [Object],
type: [Object],
stateNode: [Object],
return: [Object],
child: [Object],
sibling: null,
index: 0,
ref: null,
pendingProps: [Object],
memoizedProps: [Object],
updateQueue: null,
memoizedState: [Object],
firstContextDependency: null,
mode: 0,
effectTag: 5,
nextEffect: [Object],
firstEffect: null,
lastEffect: null,
expirationTime: 0,
childExpirationTime: 0,
alternate: null,
actualDuration: 0,
actualStartTime: -1,
selfBaseDuration: 0,
treeBaseDuration: 0,
_debugID: 50,
_debugSource: null,
_debugOwner: [Object],
_debugIsCurrentlyTiming: false },
_debugIsCurrentlyTiming: false },
nativeEvent:
Event {
target:
HTMLInputElement {
'__reactInternalInstance$j3zzgmq2kne': [Object],
'__reactEventHandlers$j3zzgmq2kne': [Object],
_wrapperState: [Object],
value: [Getter/Setter],
_valueTracker: [Object] },
type: 'change' },
type: 'change',
target: { value: '' },
currentTarget:
HTMLInputElement {
'__reactInternalInstance$j3zzgmq2kne':
FiberNode {
tag: 5,
key: null,
elementType: 'input',
type: 'input',
stateNode: [Circular],
return: [Object],
child: null,
sibling: null,
index: 0,
ref: null,
pendingProps: [Object],
memoizedProps: [Object],
updateQueue: null,
memoizedState: null,
firstContextDependency: null,
mode: 0,
effectTag: 0,
nextEffect: null,
firstEffect: null,
lastEffect: null,
expirationTime: 0,
childExpirationTime: 0,
alternate: null,
actualDuration: 0,
actualStartTime: -1,
selfBaseDuration: 0,
treeBaseDuration: 0,
_debugID: 56,
_debugSource: [Object],
_debugOwner: [Object],
_debugIsCurrentlyTiming: false },
'__reactEventHandlers$j3zzgmq2kne':
{ id: 'email',
type: 'email',
name: 'email',
onChange: [Function: logChange] },
_wrapperState:
{ initialChecked: undefined,
initialValue: '',
controlled: false },
value: [Getter/Setter],
_valueTracker:
{ getValue: [Function: getValue],
setValue: [Function: setValue],
stopTracking: [Function: stopTracking] } },
eventPhase: undefined,
bubbles: undefined,
cancelable: undefined,
timeStamp: 1550263479021,
defaultPrevented: undefined,
isTrusted: undefined,
isDefaultPrevented: [Function: functionThatReturnsFalse],
isPropagationStopped: [Function: functionThatReturnsFalse],
isPersistent: [Function: functionThatReturnsTrue],
_dispatchListeners: [Function: logChange],
_dispatchInstances:
FiberNode {
tag: 5,
key: null,
elementType: 'input',
type: 'input',
stateNode:
HTMLInputElement {
'__reactInternalInstance$j3zzgmq2kne': [Circular],
'__reactEventHandlers$j3zzgmq2kne': [Object],
_wrapperState: [Object],
value: [Getter/Setter],
_valueTracker: [Object] },
return:
FiberNode {
tag: 5,
key: null,
elementType: 'form',
type: 'form',
stateNode: [Object],
return: [Object],
child: [Circular],
sibling: null,
index: 0,
ref: null,
pendingProps: [Object],
memoizedProps: [Object],
updateQueue: null,
memoizedState: null,
firstContextDependency: null,
mode: 0,
effectTag: 0,
nextEffect: null,
firstEffect: null,
lastEffect: null,
expirationTime: 0,
childExpirationTime: 0,
alternate: null,
actualDuration: 0,
actualStartTime: -1,
selfBaseDuration: 0,
treeBaseDuration: 0,
_debugID: 55,
_debugSource: null,
_debugOwner: [Object],
_debugIsCurrentlyTiming: false },
child: null,
sibling: null,
index: 0,
ref: null,
pendingProps:
{ id: 'email',
type: 'email',
name: 'email',
onChange: [Function: logChange] },
memoizedProps:
{ id: 'email',
type: 'email',
name: 'email',
onChange: [Function: logChange] },
updateQueue: null,
memoizedState: null,
firstContextDependency: null,
mode: 0,
effectTag: 0,
nextEffect: null,
firstEffect: null,
lastEffect: null,
expirationTime: 0,
childExpirationTime: 0,
alternate: null,
actualDuration: 0,
actualStartTime: -1,
selfBaseDuration: 0,
treeBaseDuration: 0,
FiberNode { ...
.. rest of output omitted
Not sure if what enzyme's .simulate passes is different compared to what react would normally send.
Relevant dependencies:
"formik": "1.5.0",
"react": "16.8.0-alpha.1",
"react-dom": "16.8.0-alpha.1",
"enzyme": "3.8.0",
"enzyme-adapter-react-16": "1.8.0",
"jest": "23.6.0",
"jest-enzyme": "7.0.1",
| Software | Version(s) |
| ---------------- | ---------- |
| Formik | 1.5.0
| React | 16.8.0-alpha.1
| TypeScript | no
| Browser | n/a
| npm/Yarn | Yarn 1.13.0
| Operating System | OSX
I noticed that I can resolve this by doing:
const input = component.find('#email').first();
input.instance().value = '[email protected]';
input.simulate('change');
So i'm guessing that Enzyme isn't merging the {target: ...} into the event correctly.
Edit: The above doesn't quite seem to work either. All validations now appear during the test, not just the 1 field that has been changed.
So if I have multiple input defined:
<Form.Group controlId="email">
<Form.Label>
<I18n t="email" />
</Form.Label>
<Form.Control
type="email"
autoFocus={true}
name="email"
value={values.email}
onChange={handleChange}
onBlur={handleBlur}
isInvalid={touched.email && !!errors.email}
/>
<Form.Control.Feedback type="invalid">{errors.email}</Form.Control.Feedback>
</Form.Group>
<Form.Group controlId="password">
<Form.Label>
<I18n t="password" />
</Form.Label>
<Form.Control
type="password"
className="form-control"
name="password"
value={values.password}
onChange={handleChange}
onBlur={handleBlur}
isInvalid={touched.password && !!errors.password}
/>
<Form.Control.Feedback type="invalid">
{errors.password}
</Form.Control.Feedback>
</Form.Group>
if I run the code for real in the browser it works fine.
In my test if I
const input = component.find('input[name="email"]').first();
input.instance().value = '[email protected]';
input.simulate('change');
then the "invalid" messages appear for both the email and password fields. 🤷♂️
Duplicate of #937 #1262
This is because the enzyme's change event is synchronous and Formik's handlers are asynchronous causing pain and sadness for all of us.
Thanks for the reply. This is unfortunate indeed. I guess I'll close this issue then since it's a duplicate.
We have on response you can use asynchrone test with enzyme and it's done (thanks @jaredpalmer)
We have on response you can use asynchrone test with enzyme and it's done (thanks @jaredpalmer)
@thomasrobin94 how can I use asynchronous test with enzyme? can you please provide more details?
Most helpful comment
I noticed that I can resolve this by doing:
So i'm guessing that Enzyme isn't merging the
{target: ...}into the event correctly.Edit: The above doesn't quite seem to work either. All validations now appear during the test, not just the 1 field that has been changed.
So if I have multiple input defined:
if I run the code for real in the browser it works fine.
In my test if I
then the "invalid" messages appear for both the
emailandpasswordfields. 🤷♂️