enzymejs / enzyme

JavaScript Testing utilities for React
https://enzymejs.github.io/enzyme/
MIT License
19.96k stars 2.01k forks source link

Unable to test getBoundingClientRect #2514

Open AlexanderVikenfalk opened 3 years ago

AlexanderVikenfalk commented 3 years ago

Current behavior

I have a scroller component which relies on refs. When trying to test it I get an error saying "Cannot read property 'getBoundingClientRect' of null]". Is there anyway to mock these values so that I can test if my functions work correctly?

const ProgressBarScroller = (props) => {
    const [progressbarWrapperRef, progressBarTaskRef, progressBarRef] = [
        useRef(null),
        useRef(null),
        useRef(null)
    ];
    const getElementWidth = (refName) =>
        refName.current.getBoundingClientRect().width;

    useEffect(() => {
        if (!progressBarTaskRef.current)     return;
        const taskWidth = getElementWidth(progressBarTaskRef);
        const progressBarWidth = getElementWidth(progressBarRef);
        const progressbarWrapperWidth = getElementWidth(progressbarWrapperRef);

        const middleOfActiveTask =
            (props.currentTaskIndex + 1) * taskWidth - taskWidth / 2;
        const requestedX = middleOfActiveTask - progressbarWrapperWidth / 2;
        const maxPossibleX = progressBarWidth - progressbarWrapperWidth;

        const scroller = new scrollBooster({
            viewport: progressbarWrapperRef.current,
            direction: 'horizontal',
            scrollMode: 'transform',
            friction: 0.2
        });

        /**
         * returns 0 if requested X position is lower than minimum.
         * returns the highest possible X position if requested X is higher than maximum.
         * Otherwise returns requested X position.
         * @returns {number}
         */
        const getValidXPosition = () => {
            return requestedX <= 0 ? 0
                : requestedX >= maxPossibleX ? maxPossibleX
                    : requestedX;
        };

        (function moveToDefaultPosition() {
            scroller.setPosition({
                x: getValidXPosition(),
                y: 0
            });
        })();

        //Cleans up ScrollBooster on unmount
        return () => {
            scroller.destroy();
        };
    }, []);

    return (
        <div className={'cs-progressbar'} ref={progressbarWrapperRef}>
            <ul className={`cs-progressbar__list`} ref={progressBarRef}>
                {props.tasks.map((task, index) => {
                    return (
                        <ProgressBarTask
                            {...task}
                            key={task.id}
                            innerRef={
                                props.currentTaskIndex === index
                                    ? progressBarTaskRef
                                    : null
                            }
                        />
                    );
                })}
            </ul>
        </div>
    );
};

Expected behavior

That the ref values are mockable so that getBoundingClientRect() doesn't throw an error.

Your environment

API

Version

library version
enzyme 3.11.0
react 16.13.1
react-dom 16.13.1
react-test-renderer 17.0.1
adapter (below)

Adapter

ljharb commented 3 years ago

I believe this is a jsdom limitation. #1233 may be related.

The only time getElementWidth is called is inside a useEffect, and it doesn't make much sense to me that the effect would run but the useRef wouldn't. However, I do notice that your code is checking for when progressBarTaskRef.current is falsy, but not when progressbarWrapperRef is falsy, so this may be a bug in your code - what happens when you check for both being truthy in the useEffect?

AlexanderVikenfalk commented 3 years ago

I believe this is a jsdom limitation. #1233 may be related.

The only time getElementWidth is called is inside a useEffect, and it doesn't make much sense to me that the effect would run but the useRef wouldn't. However, I do notice that your code is checking for when progressBarTaskRef.current is falsy, but not when progressbarWrapperRef is falsy, so this may be a bug in your code - what happens when you check for both being truthy in the useEffect?

Thanks for the help! Sorry. My code was a bit sloppy. I should ofc have checked so that any of my refs were falsey and just not progressBarTaskRef.current. I also found a typo in the test implementation which caused the original error. So now I'm instead getting a new error:

Error: Uncaught [ReferenceError: Element is not defined]
    at reportException (/home/vikenfalk/third-party-client/tp-client/node_modules/jsdom/lib/jsdom/living/helpers/runtime-script-errors.js:62:24)
    at innerInvokeEventListeners (/home/vikenfalk/third-party-client/tp-client/node_modules/jsdom/lib/jsdom/living/events/EventTarget-impl.js:341:9)
    at invokeEventListeners (/home/vikenfalk/third-party-client/tp-client/node_modules/jsdom/lib/jsdom/living/events/EventTarget-impl.js:274:3)
    at HTMLUnknownElementImpl._dispatch (/home/vikenfalk/third-party-client/tp-client/node_modules/jsdom/lib/jsdom/living/events/EventTarget-impl.js:221:9)
    at HTMLUnknownElementImpl.dispatchEvent (/home/vikenfalk/third-party-client/tp-client/node_modules/jsdom/lib/jsdom/living/events/EventTarget-impl.js:94:17)
    at HTMLUnknownElement.dispatchEvent (/home/vikenfalk/third-party-client/tp-client/node_modules/jsdom/lib/jsdom/living/generated/EventTarget.js:231:34)
    at Object.invokeGuardedCallbackDev (/home/vikenfalk/third-party-client/tp-client/node_modules/react-dom/cjs/react-dom.development.js:237:16)
    at invokeGuardedCallback (/home/vikenfalk/third-party-client/tp-client/node_modules/react-dom/cjs/react-dom.development.js:292:31)
    at flushPassiveEffectsImpl (/home/vikenfalk/third-party-client/tp-client/node_modules/react-dom/cjs/react-dom.development.js:22853:9)
    at unstable_runWithPriority (/home/vikenfalk/third-party-client/tp-client/node_modules/react-dom/node_modules/scheduler/cjs/scheduler.development.js:653:12)
    at runWithPriority$1 (/home/vikenfalk/third-party-client/tp-client/node_modules/react-dom/cjs/react-dom.development.js:11039:10)
    at flushPassiveEffects (/home/vikenfalk/third-party-client/tp-client/node_modules/react-dom/cjs/react-dom.development.js:22820:12)
    at flushWork (/home/vikenfalk/third-party-client/tp-client/node_modules/react-dom/cjs/react-dom-test-utils.development.js:876:10)
    at Object.act (/home/vikenfalk/third-party-client/tp-client/node_modules/react-dom/cjs/react-dom-test-utils.development.js:994:9)
    at wrapAct (/home/vikenfalk/third-party-client/tp-client/node_modules/enzyme-adapter-react-16/src/ReactSixteenAdapter.js:405:13)
    at Object.render (/home/vikenfalk/third-party-client/tp-client/node_modules/enzyme-adapter-react-16/src/ReactSixteenAdapter.js:474:16)
    at new ReactWrapper (/home/vikenfalk/third-party-client/tp-client/node_modules/enzyme/src/ReactWrapper.js:115:16)
    at mount (/home/vikenfalk/third-party-client/tp-client/node_modules/enzyme/src/mount.js:10:10)
    at setup (/home/vikenfalk/third-party-client/tp-client/test/components/proposal/progressBar/ProgressBarScroller.test.js:43:25)
    at Context.<anonymous> (/home/vikenfalk/third-party-client/tp-client/test/components/proposal/progressBar/ProgressBarScroller.test.js:51:38)
    at callFn (/home/vikenfalk/third-party-client/tp-client/node_modules/mocha/lib/runnable.js:358:21)
    at Test.Runnable.run (/home/vikenfalk/third-party-client/tp-client/node_modules/mocha/lib/runnable.js:346:5)
    at Runner.runTest (/home/vikenfalk/third-party-client/tp-client/node_modules/mocha/lib/runner.js:621:10)
    at /home/vikenfalk/third-party-client/tp-client/node_modules/mocha/lib/runner.js:745:12
    at next (/home/vikenfalk/third-party-client/tp-client/node_modules/mocha/lib/runner.js:538:14)
    at /home/vikenfalk/third-party-client/tp-client/node_modules/mocha/lib/runner.js:548:7
    at next (/home/vikenfalk/third-party-client/tp-client/node_modules/mocha/lib/runner.js:430:14)
    at Immediate._onImmediate (/home/vikenfalk/third-party-client/tp-client/node_modules/mocha/lib/runner.js:516:5)
    at runCallback (timers.js:705:18)
    at tryOnImmediate (timers.js:676:5)
    at processImmediate (timers.js:658:5) ReferenceError: Element is not defined
    at new t (/home/vikenfalk/third-party-client/tp-client/node_modules/scrollbooster/dist/webpack:/ScrollBooster/src/index.js:76:70)
    at /home/vikenfalk/third-party-client/tp-client/src/components/proposal/progressBar/ProgressBarScroller.js:28:26
    at commitHookEffectListMount (/home/vikenfalk/third-party-client/tp-client/node_modules/react-dom/cjs/react-dom.development.js:19731:26)
    at commitPassiveHookEffects (/home/vikenfalk/third-party-client/tp-client/node_modules/react-dom/cjs/react-dom.development.js:19769:11)
    at HTMLUnknownElement.callCallback (/home/vikenfalk/third-party-client/tp-client/node_modules/react-dom/cjs/react-dom.development.js:188:14)
    at HTMLUnknownElement.callTheUserObjectsOperation (/home/vikenfalk/third-party-client/tp-client/node_modules/jsdom/lib/jsdom/living/generated/EventListener.js:26:30)
    at innerInvokeEventListeners (/home/vikenfalk/third-party-client/tp-client/node_modules/jsdom/lib/jsdom/living/events/EventTarget-impl.js:338:25)
    at invokeEventListeners (/home/vikenfalk/third-party-client/tp-client/node_modules/jsdom/lib/jsdom/living/events/EventTarget-impl.js:274:3)
    at HTMLUnknownElementImpl._dispatch (/home/vikenfalk/third-party-client/tp-client/node_modules/jsdom/lib/jsdom/living/events/EventTarget-impl.js:221:9)
    at HTMLUnknownElementImpl.dispatchEvent (/home/vikenfalk/third-party-client/tp-client/node_modules/jsdom/lib/jsdom/living/events/EventTarget-impl.js:94:17)
    at HTMLUnknownElement.dispatchEvent (/home/vikenfalk/third-party-client/tp-client/node_modules/jsdom/lib/jsdom/living/generated/EventTarget.js:231:34)
    at Object.invokeGuardedCallbackDev (/home/vikenfalk/third-party-client/tp-client/node_modules/react-dom/cjs/react-dom.development.js:237:16)
    at invokeGuardedCallback (/home/vikenfalk/third-party-client/tp-client/node_modules/react-dom/cjs/react-dom.development.js:292:31)
    at flushPassiveEffectsImpl (/home/vikenfalk/third-party-client/tp-client/node_modules/react-dom/cjs/react-dom.development.js:22853:9)
    at unstable_runWithPriority (/home/vikenfalk/third-party-client/tp-client/node_modules/react-dom/node_modules/scheduler/cjs/scheduler.development.js:653:12)
    at runWithPriority$1 (/home/vikenfalk/third-party-client/tp-client/node_modules/react-dom/cjs/react-dom.development.js:11039:10)
    at flushPassiveEffects (/home/vikenfalk/third-party-client/tp-client/node_modules/react-dom/cjs/react-dom.development.js:22820:12)
    at flushWork (/home/vikenfalk/third-party-client/tp-client/node_modules/react-dom/cjs/react-dom-test-utils.development.js:876:10)
    at Object.act (/home/vikenfalk/third-party-client/tp-client/node_modules/react-dom/cjs/react-dom-test-utils.development.js:994:9)
    at wrapAct (/home/vikenfalk/third-party-client/tp-client/node_modules/enzyme-adapter-react-16/src/ReactSixteenAdapter.js:405:13)
    at Object.render (/home/vikenfalk/third-party-client/tp-client/node_modules/enzyme-adapter-react-16/src/ReactSixteenAdapter.js:474:16)
    at new ReactWrapper (/home/vikenfalk/third-party-client/tp-client/node_modules/enzyme/src/ReactWrapper.js:115:16)
    at mount (/home/vikenfalk/third-party-client/tp-client/node_modules/enzyme/src/mount.js:10:10)
    at setup (/home/vikenfalk/third-party-client/tp-client/test/components/proposal/progressBar/ProgressBarScroller.test.js:43:25)
    at Context.<anonymous> (/home/vikenfalk/third-party-client/tp-client/test/components/proposal/progressBar/ProgressBarScroller.test.js:51:38)
    at callFn (/home/vikenfalk/third-party-client/tp-client/node_modules/mocha/lib/runnable.js:358:21)
    at Test.Runnable.run (/home/vikenfalk/third-party-client/tp-client/node_modules/mocha/lib/runnable.js:346:5)
    at Runner.runTest (/home/vikenfalk/third-party-client/tp-client/node_modules/mocha/lib/runner.js:621:10)
    at /home/vikenfalk/third-party-client/tp-client/node_modules/mocha/lib/runner.js:745:12
    at next (/home/vikenfalk/third-party-client/tp-client/node_modules/mocha/lib/runner.js:538:14)
    at /home/vikenfalk/third-party-client/tp-client/node_modules/mocha/lib/runner.js:548:7
    at next (/home/vikenfalk/third-party-client/tp-client/node_modules/mocha/lib/runner.js:430:14)
    at Immediate._onImmediate (/home/vikenfalk/third-party-client/tp-client/node_modules/mocha/lib/runner.js:516:5)
    at runCallback (timers.js:705:18)
    at tryOnImmediate (timers.js:676:5)
    at processImmediate (timers.js:658:5)
The above error occurred in the <ProgressBarScroller> component:
    in ProgressBarScroller (created by WrapperComponent)
    in WrapperComponent

Consider adding an error boundary to your tree to customize error handling behavior.
Visit https://fb.me/react-error-boundaries to learn more about error boundaries.

ReferenceError: Element is not defined
    at new t (node_modules/scrollbooster/dist/webpack:/ScrollBooster/src/index.js:76:70)
    at /home/vikenfalk/third-party-client/tp-client/src/components/proposal/progressBar/ProgressBarScroller.js:28:26
    at commitHookEffectListMount (node_modules/react-dom/cjs/react-dom.development.js:19731:26)
    at commitPassiveHookEffects (node_modules/react-dom/cjs/react-dom.development.js:19769:11)
    at HTMLUnknownElement.callCallback (node_modules/react-dom/cjs/react-dom.development.js:188: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:237:16)
    at invokeGuardedCallback (node_modules/react-dom/cjs/react-dom.development.js:292:31)
    at flushPassiveEffectsImpl (node_modules/react-dom/cjs/react-dom.development.js:22853:9)
    at unstable_runWithPriority (node_modules/react-dom/node_modules/scheduler/cjs/scheduler.development.js:653:12)
    at runWithPriority$1 (node_modules/react-dom/cjs/react-dom.development.js:11039:10)
    at flushPassiveEffects (node_modules/react-dom/cjs/react-dom.development.js:22820:12)
    at flushWork (node_modules/react-dom/cjs/react-dom-test-utils.development.js:876:10)
    at Object.act (node_modules/react-dom/cjs/react-dom-test-utils.development.js:994:9)
    at wrapAct (node_modules/enzyme-adapter-react-16/src/ReactSixteenAdapter.js:405:13)
    at Object.render (node_modules/enzyme-adapter-react-16/src/ReactSixteenAdapter.js:474:16)
    at new ReactWrapper (node_modules/enzyme/src/ReactWrapper.js:115:16)
    at mount (node_modules/enzyme/src/mount.js:10:10)
    at setup (test/components/proposal/progressBar/ProgressBarScroller.test.js:43:25)
    at Context.<anonymous> (test/components/proposal/progressBar/ProgressBarScroller.test.js:51:38)

Any suggestion on what could be causing the issue or what I could do to work around it?