jsdom: Fix for TypeError: range(...).getBoundingClientRect is not a function
Basic info:
- Node.js version: 12.16.3
- jsdom version: 16.2.2
Minimal reproduction case
const { JSDOM } = require("jsdom");
const options = {
runScripts: 'dangerously',
};
const dom = new JSDOM(`
<!DOCTYPE html>
<html>
<body>
<p id="p0">Range: </p>
<p id="p1">Bounds: </p>
<p id="p2">Rects: </p>
<script>
let range = document.createRange();
let p0 = document.getElementById("p0");
p0.innerHTML += JSON.stringify(range);
// prevent crash
try {
let bounds = range.getBoundingClientRect();
let p1 = document.getElementById("p1");
p1.innerHTML += JSON.stringify(bounds);
}
catch(e) {
console.log(e.message);
}
// prevent crash
try {
let rects = range.getClientRects();
let p2 = document.getElementById("p2");
p2.innerHTML += JSON.stringify(rects);
}
catch(e) {
console.log(e.message);
}
</script>
</body>
</html>
`, options);
console.log(dom.window.document.getElementById("p0").innerHTML);
console.log(dom.window.document.getElementById("p1").innerHTML);
console.log(dom.window.document.getElementById("p2").innerHTML);
Outputs:
range.getBoundingClientRect is not a function
range.getClientRects is not a function
Range: {}
Bounds:
Rects:
How does similar code behave in browsers?
Shouldn’t crash. Should instead show the output:
Range: {}
Bounds: {"x":0,"y":0,"width":0,"height":0,"top":0,"right":0,"bottom":0,"left":0}
Rects: {}
JS Bin: https://jsbin.com/keconidona/edit?html,output
Suggestions for a fix:
Based on my own tests, simply adding these functions to the Range class should already fix the problem:
getBoundingClientRect() {
return {
bottom: 0,
height: 0,
left: 0,
right: 0,
top: 0,
width: 0
};
}
getClientRects() {
return [];
}
I used the same implementation defined here for consistency, although I just noticed this mock is missing the x, y and toJSON properties for getBoundingClientRect, and the item property for getClientRects – perhaps they should be updated as well.
I had to update both jsdom\lib\jsdom\living\range\Range-impl.js
and jsdom\lib\jsdom\living\generated\Range.js
to fix it in my node_modules. Not sure what else you’d need to change here in your source, but it should be an easy fix.
Workarounds:
If anyone else is facing this same problem, adding this to the top of your test file should prevent the crash while a fix isn’t available (thanks to @raspo for the simplified code):
document.createRange = () => {
const range = new Range();
range.getBoundingClientRect = jest.fn();
range.getClientRects = () => {
return {
item: () => null,
length: 0,
[Symbol.iterator]: jest.fn()
};
};
return range;
}
or this if you’re not using Jest:
document.createRange = () => {
const range = new Range();
range.getBoundingClientRect = () => {
return {
x: 0,
y: 0,
bottom: 0,
height: 0,
left: 0,
right: 0,
top: 0,
width: 0,
toJSON: () => {}
};
};
range.getClientRects = () => {
return {
item: (index) => null,
length: 0,
*[Symbol.iterator](){}
};
};
return range;
}
Additional info:
I actually came across this issue when trying to write automated tests for a React app that uses the CodeMirror editor. Using Jest and React Testing Library, the tests would crash whenever I called render() on my component, showing me the error TypeError: range(...).getBoundingClientRect is not a function
.
I looked further into it, and found out it was because of a call to a range() function that then tries to access getBoundingClientRect() here, where this range() is either document.createRange() or document.body.createTextRange() as defined here.
Hope this helps 😃
About this issue
- Original URL
- State: open
- Created 4 years ago
- Reactions: 41
- Comments: 15 (1 by maintainers)
Commits related to this issue
- test: add testing Issues: https://github.com/jsdom/jsdom/issues/3002 — committed to aboqasem/markdown-to-confluence-wiki-markup-editor by aboqasem 6 months ago
- test: add testing Issues: https://github.com/jsdom/jsdom/issues/3002 — committed to aboqasem/markdown-to-confluence-wiki-markup-editor by aboqasem 6 months ago
I just stumbled upon the same exact issue with a unit test for a component that uses CodeMirror. Thank you for the workaround @D-to-the-K!
Using
jest
, I ended up simplifying it down to this:I managed to resolve all the errors I was getting by putting this into
setupTests.js
:https://mobile.twitter.com/slicknet/status/782274190451671040
Hey, any update on this? Seems pretty clunky atm having to manually implement a mock
document.createRange
that containsgetBoundingClientRect
in our testsWith
vitest
:Thanks guys, I too was unit testing an Angular component that uses ngx-codemirror, and your solution helped resolve the error.
Awesome, @Raspo! Glad I could help.
Following your suggestion, I ended up using this:
Edit: I still needed the length, and TypeScript insisted on me having the Symbol.iterator there.