4

I tried to test the hook, that used the ResizeObserver. Also, I need to check if the element was overflow or wasn't after resizing. So I wrote such a decision:

import { useLayoutEffect, useState } from 'react';
import ResizeObserver from 'resize-observer-polyfill';

export default function useOverflowCheck(ref) {
  const [isOverflowing, setIsOverflowing] = useState(false);

  const isOverflow = current => {
    if (current) {
      const hasOverflowX = current.offsetWidth < current.scrollWidth;
      const hasOverflowY = current.offsetHeight < current.scrollHeight;

      setIsOverflowing(hasOverflowX || hasOverflowY);
    }
  };

  // eslint-disable-next-line react-hooks/exhaustive-deps
  useLayoutEffect(() => {
    const element = ref.current;

    const resizeObserver = new ResizeObserver(entries => {
      entries && entries.length && isOverflow(entries[0].target);
    });

    if (element) {
      resizeObserver.observe(element);
      return () => {
        resizeObserver.disconnect();
      };
    }
  });

  return isOverflowing;
}

And I tried to unit test this code, but my tests didn't cover the resizeObserver callback. Test:

import { renderHook } from '@testing-library/react-hooks';
import useOverflowCheck from './index';

describe('useOverflowCheck', () => {
  it('should return true for an overflowing component', () => {
    const el: HTMLDivElement = document.createElement('div');
    Object.defineProperties(el, {
      offsetWidth: { value: 30, configurable: true },
      offsetHeight: { value: 30, configurable: true },
    });

    const ref = {
      current: el,
    };

    expect(renderHook(() => useOverflowCheck(ref)).result.current).toBeTruthy();
  });
});
nik7
  • 806
  • 3
  • 12
  • 20
Yumie
  • 174
  • 12

2 Answers2

6

Should define Resize Observer constructor and add a listener to test Resize Observer callback.

let listener: ((rect: any) => void) | undefined = undefined;
    (global as any).ResizeObserver = class ResizeObserver {
      constructor(ls) {
        listener = ls;
      }
      observe() {}
      unobserve() {}
      disconnect() {}
    };

Then should specify needed properties:

act(() => {
      listener!([
        {
          target: {
            clientWidth: 100,
            scrollWidth: 200,
            clientHeight: 100,
            scrollHeight: 200,
          },
        },
      ]);
    });

Inspired by https://github.com/streamich/react-use/blob/master/tests/useMeasure.test.ts

Yumie
  • 174
  • 12
0

You don't want to use LayoutEffect like that, especially with no dependency array. The right approach is to use callback references to measure dom node dimensions. See my implementation here, including tests based on the accepted answer above but with cleaned-up typings. https://stackoverflow.com/a/76275373/265877

Alex
  • 9,250
  • 11
  • 70
  • 81