progressbar.js
progressbar.js copied to clipboard
JSdom causing Error: Uncaught [TypeError: this.path.getTotalLength is not a function]
In a React project that uses Jest and Testing Library, after adding some ProgressBar circles, the tests started to fail. I believe it is due to JSDom not supporting SVG.
The error I'm receiving is:
console.error
Error: Uncaught [TypeError: this.path.getTotalLength is not a function]
at reportException (node_modules\jsdom\lib\jsdom\living\helpers\runtime-script-errors.js:66:24)
at innerInvokeEventListeners (node_modules\jsdom\lib\jsdom\living\events\EventTarget-impl.js:341:9)
at invokeEventListeners (node_modules\jsdom\lib\jsdom\living\events\EventTarget-impl.js:274:3)
at HTMLUnknownElementImpl._dispatch (node_modules\jsdom\lib\jsdom\living\events\EventTarget-impl.js:221:9)
at HTMLUnknownElementImpl.dispatchEvent (node_modules\jsdom\lib\jsdom\living\events\EventTarget-impl.js:94:17)
at HTMLUnknownElement.dispatchEvent (node_modules\jsdom\lib\jsdom\living\generated\EventTarget.js:231:34)
at Object.invokeGuardedCallbackDev (node_modules\react-dom\cjs\react-dom.development.js:3994:16)
at invokeGuardedCallback (node_modules\react-dom\cjs\react-dom.development.js:4056:31)
at beginWork$1 (node_modules\react-dom\cjs\react-dom.development.js:23964:7)
at performUnitOfWork (node_modules\react-dom\cjs\react-dom.development.js:22779:12)
at workLoopSync (node_modules\react-dom\cjs\react-dom.development.js:22707:5)
at renderRootSync (node_modules\react-dom\cjs\react-dom.development.js:22670:7)
at performSyncWorkOnRoot (node_modules\react-dom\cjs\react-dom.development.js:22293:18)
at node_modules\react-dom\cjs\react-dom.development.js:11327:26
at unstable_runWithPriority (node_modules\scheduler\cjs\scheduler.development.js:468:12)
at runWithPriority$1 (node_modules\react-dom\cjs\react-dom.development.js:11276:10)
at flushSyncCallbackQueueImpl (node_modules\react-dom\cjs\react-dom.development.js:11322:9)
at flushSyncCallbackQueue (node_modules\react-dom\cjs\react-dom.development.js:11309:3)
at scheduleUpdateOnFiber (node_modules\react-dom\cjs\react-dom.development.js:21893:9)
at setProgress (node_modules\react-dom\cjs\react-dom.development.js:16139:5)
at _callee$ (src\components\widget\IamProgressWidget\IamProgressWidget.js:28:7)
at tryCatch (node_modules\@babel\runtime\helpers\regeneratorRuntime.js:86:17)
at Generator._invoke (node_modules\@babel\runtime\helpers\regeneratorRuntime.js:66:24)
at Generator.next (node_modules\@babel\runtime\helpers\regeneratorRuntime.js:117:21)
at asyncGeneratorStep (node_modules\@babel\runtime\helpers\asyncToGenerator.js:3:24)
at _next (node_modules\@babel\runtime\helpers\asyncToGenerator.js:25:9)
at runNextTicks (node:internal/process/task_queues:61:5)
at listOnTimeout (node:internal/timers:526:9)
at processTimers (node:internal/timers:500:7) TypeError: this.path.getTotalLength is not a function
at new Path (node_modules\progressbar.js\src\path.js:43:28)
at Circle.Shape (node_modules\progressbar.js\src\shape.js:99:26)
at new Circle (node_modules\progressbar.js\src\circle.js:16:11)
at src\components\widget\IamProgressWidget\ProgressCircle.js:23:7
at mountMemo (node_modules\react-dom\cjs\react-dom.development.js:15846:19)
at Object.useMemo (node_modules\react-dom\cjs\react-dom.development.js:16219:16)
at useMemo (node_modules\react\cjs\react.development.js:1532:21)
at ProgressCircle (src\components\widget\IamProgressWidget\ProgressCircle.js:21:22)
at renderWithHooks (node_modules\react-dom\cjs\react-dom.development.js:14985:18)
at mountIndeterminateComponent (node_modules\react-dom\cjs\react-dom.development.js:17811:13)
at beginWork (node_modules\react-dom\cjs\react-dom.development.js:19049:16)
at HTMLUnknownElement.callCallback (node_modules\react-dom\cjs\react-dom.development.js:3945:14)
at HTMLUnknownElement.callTheUserObjectsOperation (node_modules\jsdom\lib\jsdom\living\generated\EventListener.js:26:30)
at innerInvokeEventListeners (node_modules\jsdom\lib\jsdom\living\events\EventTarget-impl.js:338:25)
at invokeEventListeners (node_modules\jsdom\lib\jsdom\living\events\EventTarget-impl.js:274:3)
at HTMLUnknownElementImpl._dispatch (node_modules\jsdom\lib\jsdom\living\events\EventTarget-impl.js:221:9)
at HTMLUnknownElementImpl.dispatchEvent (node_modules\jsdom\lib\jsdom\living\events\EventTarget-impl.js:94:17)
at HTMLUnknownElement.dispatchEvent (node_modules\jsdom\lib\jsdom\living\generated\EventTarget.js:231:34)
at Object.invokeGuardedCallbackDev (node_modules\react-dom\cjs\react-dom.development.js:3994:16)
at invokeGuardedCallback (node_modules\react-dom\cjs\react-dom.development.js:4056:31)
at beginWork$1 (node_modules\react-dom\cjs\react-dom.development.js:23964:7)
at performUnitOfWork (node_modules\react-dom\cjs\react-dom.development.js:22779:12)
at workLoopSync (node_modules\react-dom\cjs\react-dom.development.js:22707:5)
at renderRootSync (node_modules\react-dom\cjs\react-dom.development.js:22670:7)
at performSyncWorkOnRoot (node_modules\react-dom\cjs\react-dom.development.js:22293:18)
at node_modules\react-dom\cjs\react-dom.development.js:11327:26
at unstable_runWithPriority (node_modules\scheduler\cjs\scheduler.development.js:468:12)
at runWithPriority$1 (node_modules\react-dom\cjs\react-dom.development.js:11276:10)
at flushSyncCallbackQueueImpl (node_modules\react-dom\cjs\react-dom.development.js:11322:9)
at flushSyncCallbackQueue (node_modules\react-dom\cjs\react-dom.development.js:11309:3)
at scheduleUpdateOnFiber (node_modules\react-dom\cjs\react-dom.development.js:21893:9)
at setProgress (node_modules\react-dom\cjs\react-dom.development.js:16139:5)
at _callee$ (src\components\widget\IamProgressWidget\IamProgressWidget.js:28:7)
at tryCatch (node_modules\@babel\runtime\helpers\regeneratorRuntime.js:86:17)
at Generator._invoke (node_modules\@babel\runtime\helpers\regeneratorRuntime.js:66:24)
at Generator.next (node_modules\@babel\runtime\helpers\regeneratorRuntime.js:117:21)
at asyncGeneratorStep (node_modules\@babel\runtime\helpers\asyncToGenerator.js:3:24)
at _next (node_modules\@babel\runtime\helpers\asyncToGenerator.js:25:9)
at runNextTicks (node:internal/process/task_queues:61:5)
at listOnTimeout (node:internal/timers:526:9)
at processTimers (node:internal/timers:500:7)
The React widget code to draw the circle is below:
import React, { useEffect, useMemo, useCallback } from "react";
import { Circle } from "progressbar.js";
const ProgressCircle = ({ level, animate }) => {
var wrapper = document.createElement("div");
wrapper.className = "progressBarDiv";
wrapper.style = "height:50px;display:flex;flex-direction:column";
wrapper.id = level;
var percentage = Math.round(animate * 100);
var color = "#7F00FF";
if (percentage >= 80) {
color = "#00FF00";
} else if (percentage >= 50) {
color = "#003FFF";
}
const bar = useMemo(
() =>
new Circle(wrapper, {
strokeWidth: 6,
easing: "easeInOut",
duration: 1400,
color: color,
trailColor: "#888",
trailWidth: 1,
step: function (state, circle) {
circle.setText(percentage + "%");
},
}),
[]
);
const node = useCallback((node) => {
if (node) {
node.appendChild(wrapper);
}
}, []);
useEffect(() => {
bar.animate(animate);
}, [animate, bar]);
return <div ref={node} />;
};
export default ProgressCircle;
As a workaround, I added this code to check if the userAgent has "jsdom" in it, and just return a blank div instead of drawing.
// Don't try to draw anything if the user agent reports "jsdom",
// since Jest/Testing Library using jsdom cannot draw SVGs and this breaks the
// ProgressBar.js library.
var userAgent = window.navigator.userAgent;
if (userAgent !== undefined) {
if (userAgent.search("jsdom") >= 0) {
return <div />;
}
}
Would it be possible to add some validation on the path value. In this case, it was the in the Path function in path.js:
var Path = function Path(path, opts) {
// Throw a better error if not initialized with `new` keyword
if (!(this instanceof Path)) {
throw new Error('Constructor was called without new keyword');
}
// Default parameters for animation
opts = utils.extend({
delay: 0,
duration: 800,
easing: 'linear',
from: {},
to: {},
step: function() {}
}, opts);
var element;
if (utils.isString(path)) {
element = document.querySelector(path);
} else {
element = path;
}
// Reveal .path as public attribute
this.path = element;
this._opts = opts;
this._tweenable = null;
// Set up the starting positions
var length = this.path.getTotalLength();
this.path.style.strokeDasharray = length + ' ' + length;
this.set(0);
};