5

I have a component using intersection observer and want to test in a jest test the effects if the element is intersecting. I already managed to mock the Intersection Observer like in this answer: https://stackoverflow.com/a/58651649/4716214

Now I want to "fake callback" the isIntersecting trigger at a specific element. Is it possible to mock this in the test?

 const observe = jest.fn();
 const disconnect = jest.fn();
 setupIntersectionObserverMock({ observe: observe, disconnect: disconnect });
 const page = await newSpecPage({
   components: [TestComponent],
   html: `<div></div>`, // This is the element I want to trigger an "isIntersecting" on 
 });

export const setupIntersectionObserverMock = ({
  root = null,
  rootMargin = '',
  thresholds = [],
  disconnect = () => null,
  observe = () => null,
  takeRecords = () => null,
  unobserve = () => null,
} = {}): void => {
  class MockIntersectionObserver implements IntersectionObserver {
    readonly root: Element | null = root;
    readonly rootMargin: string = rootMargin;
    readonly thresholds: ReadonlyArray<number> = thresholds;
    disconnect: () => void = disconnect;
    observe: (target: Element) => void = observe;
    takeRecords: () => IntersectionObserverEntry[] = takeRecords;
    unobserve: (target: Element) => void = unobserve;
  }

  Object.defineProperty(window, 'IntersectionObserver', {
    writable: true,
    configurable: true,
    value: MockIntersectionObserver,
  });

  Object.defineProperty(global, 'IntersectionObserver', {
    writable: true,
    configurable: true,
    value: MockIntersectionObserver,
  });
};
jonrsharpe
  • 115,751
  • 26
  • 228
  • 437
maidi
  • 3,219
  • 6
  • 27
  • 55

1 Answers1

3

Yes it is possible. You need to do 2 changes:

takeRecords returns the values for each entry when an observe is triggered.

This means you need to change takeRecords either by giving it into setupIntersectionObserverMock or by default prop:

export function setupIntersectionObserverMock({
  root = null,
  rootMargin = '',
  thresholds = [],
  disconnect = () => null,
  observe = () => null,
  takeRecords = () => [
    {
      boundingClientRect: {} as DOMRectReadOnly,
      intersectionRatio: 1,
      intersectionRect: {} as DOMRectReadOnly,
      isIntersecting: true,
      rootBounds: null,
      target: {} as Element,
      time: 1,
    },
  ],
  unobserve = () => null,
} = {}): void {

The second step is to handle the IntersectionObserver constructor callback:

constructor(callback: (entries: IntersectionObserverEntry[], observer: IntersectionObserver) => void) {
  callback(takeRecords(), this);
}

The whole code looks like:

export function setupIntersectionObserverMock({
  root = null,
  rootMargin = '',
  thresholds = [],
  disconnect = () => null,
  observe = () => null,
  takeRecords = () => [
    {
      boundingClientRect: {} as DOMRectReadOnly,
      intersectionRatio: 1,
      intersectionRect: {} as DOMRectReadOnly,
      isIntersecting: true,
      rootBounds: null,
      target: {} as Element,
      time: 1,
    },
  ],
  unobserve = () => null,
} = {}): void {
  class MockIntersectionObserver implements IntersectionObserver {
    readonly root: Element | null = root;
    readonly rootMargin: string = rootMargin;
    readonly thresholds: ReadonlyArray<number> = thresholds;
    disconnect: () => void = disconnect;
    observe: (target: Element) => void = observe;
    takeRecords: () => IntersectionObserverEntry[] = takeRecords;
    unobserve: (target: Element) => void = unobserve;

    constructor(callback: (entries: IntersectionObserverEntry[], observer: IntersectionObserver) => void) {
      callback(takeRecords(), this);
    }
  }

  Object.defineProperty(window, 'IntersectionObserver', {
    writable: true,
    configurable: true,
    value: MockIntersectionObserver,
  });

  Object.defineProperty(global, 'IntersectionObserver', {
    writable: true,
    configurable: true,
    value: MockIntersectionObserver,
  });
}
Lotus
  • 442
  • 1
  • 3
  • 15