I am testing a component that renders and input field with onChange function that does event.target.checkValidity(). Now If I simulate change event with enzyme, I get TypeError: event.currentTarget.checkValidity is not a function. Is there any way around it? Maybe some advice on how to mock checkValidity ?
Thanks
If you're using mount you should be getting an instance of SyntheticEvent. You can access the native event at event.nativeEvent. You can pass in event data as the second argument to simulate, so something like this might work:
wrapper.simulate('click', {currentTarget: {checkValiditiy: () => true }})
edit: it looks like the ReactWrapper.simulate can't override native values like that, see https://github.com/airbnb/enzyme/issues/218. You might have better luck using shallow here as mentioned in that issue.
edit-2: It also seems that jsdom doesn't support checkValidity https://github.com/tmpvar/jsdom/issues/544 so this would also be dependant on that.
Thanks @Aweary for a quick response.
So I tried event.nativeEvent.currentTarget.checkValidity() in my component but got Cannot read property 'checkValidity' of undefined
I also tried event.nativeEvent.target.checkValidity() and got event.nativeEvent.target.checkValidity is not a function
I think I will get better results by mocking onChange somehow to write my own implementation of DOM checkValidity
So event.currentTarget should return the current target, but if you're using mount then you're using jsdom which doesn't currently support checkValidity which is the main problem here.
Ah ok that makes more sense. I'll visit jsdom github page to see if this is in a roadmap. Meanwhile will try to work around it somehow. Thanks. I will close this issue now.
Just for reference: https://github.com/tmpvar/jsdom/issues/544
So, there is no workaround for this?
For anyone having issues when testing React components using Jest and Enzyme I was able to polyfill JSDOM by using the hyperform package.
In your test file you can do the following after installing hyperform.
import * as hyperform from 'hyperform';
var global = global;
const defineValidity = {
get() {
return hyperform.ValidityState(this);
},
configurable: true
};
global.HTMLFormElement.prototype.checkValidity = function() {
return hyperform.checkValidity(this);
};
Object.defineProperty(global.HTMLFormElement.prototype, 'validity', defineValidity);
Object.defineProperty(global.HTMLInputElement.prototype, 'validity', defineValidity);
Object.defineProperty(global.HTMLSelectElement.prototype, 'validity', defineValidity);
Object.defineProperty(global.HTMLTextAreaElement.prototype, 'validity', defineValidity);
you can follow the same method for any other HTMLElements you may need to modify or any other attributes you need to polyfill.
This was our solution also using hyperform:
// Jest Config
{
"testEnvironment": "./jestSuperJSDOMEnvironment.js"
}
// jestSuperJSDOMEnvironment.js
const JSDOMEnvironment = require('jest-environment-jsdom-latest');
// Extends JSDOM to include polyfills
class SuperJSDOMEnvironment extends JSDOMEnvironment {
constructor(config) {
super(config);
global.window = this.global;
global.document = this.document;
// Add Form Validation polyfill
// `require` down here because package has side-effects that change `global.window`/`global.document`
const hyperform = require('hyperform');
hyperform(this.global);
}
}
module.exports = SuperJSDOMEnvironment;
The Jest docs suggests mocking methods which are not implemented in JSDOM. I think this is a cleaner solution than polluting your environment because of tests.
In my specific case, I needed to validate a form and then inspect the classList of the form for the presence of a certain class, the presence of which would inform me about the form's validity.
My solution was to mock the form's properties e.g mock the native implementation of DOMTokenList for classList property inside
beforeAll(() => {
window.DOMTokenList = jest.fn().mockImplementation(() => {
return {
list: [],
remove: jest.fn().mockImplementation(function (item) {
const idx = this.list.indexOf(item);
if (idx > -1) {
this.list.splice(idx, 1)
}
}),
add: jest.fn().mockImplementation(function (item) {
this.list.push(item);
}),
contains: jest.fn().mockImplementation(function (item) {
return this.list.indexOf(item) > -1;
})
};
});
})
Then I used that to pass properties to the event handler. I'm able to access the form in my react component from the submit button with event.currentTarget.form
let mockClassList = new DOMTokenList();
submitBtn.simulate('click', {
currentTarget: {
form: {
checkValidity: () => false,
classList: mockClassList
}
},
preventDefault: jest.fn(),
stopPropagation: jest.fn()
})
This allows me to set the validity of the form to false and true, and inspect the mockClassList for the presence of the was-validated class each time
submitBtn.simulate('click', {
currentTarget: {
form: {
checkValidity: () => false,
classList: mockClassList
}
},
preventDefault: jest.fn(),
stopPropagation: jest.fn()
});
expect(mockClassList.contains('was-validated')).toBeTruthy();
submitBtn.simulate('click', {
currentTarget: {
form: {
checkValidity: () => true,
classList: mockClassList
}
},
preventDefault: jest.fn(),
stopPropagation: jest.fn()
});
expect(mockClassList.contains('was-validated')).toBeFalsy();
FYI: I'm using Enzyme's shallow rendering for this, mount doesn't seem to work with this solution. However, I'm inclined to think that the other suggestion for mocking methods which are not implemented in JSDOM, when the method is executed directly in the tested file statement might just work with mount rendering
I had success with @hjylewis's approach, with the one caveat that I had to change one line to global.document = this.global.document; using jest-environment-jsdom: 23.4.0.
I took a slightly different approach to this problem.
I created a helper method like so
export const checkValidityOnElement =
/* istanbul ignore next */
(element) => element.checkValidity();
Now in my component I import this helper function to perform the validation. So now in my unit test I can simply stub out this helper method in the usual way.
This does mean the actual checkValidity is untested in the helper file, but it's just a simple one-liner it's probably safe to be left untested (hence the ignore next statement)
Most helpful comment
For anyone having issues when testing React components using Jest and Enzyme I was able to polyfill JSDOM by using the hyperform package.
In your test file you can do the following after installing hyperform.
you can follow the same method for any other HTMLElements you may need to modify or any other attributes you need to polyfill.