summaryrefslogtreecommitdiff
path: root/spec/frontend/__helpers__/mock_dom_observer.js
blob: bc2646be4c2f93786585124c6dcbb434b79ba799 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
/* eslint-disable class-methods-use-this, max-classes-per-file */
import { isMatch } from 'lodash';

/**
 * This class gives us a JSDom friendly DOM observer which we can manually trigger in tests
 *
 * Use this in place of MutationObserver or IntersectionObserver
 */
class MockObserver {
  constructor(cb) {
    this.$_cb = cb;
    this.$_observers = [];
  }

  observe(node, options = {}) {
    this.$_observers.push([node, options]);
  }

  disconnect() {
    this.$_observers = [];
  }

  takeRecords() {}

  // eslint-disable-next-line camelcase
  $_triggerObserve(node, { entry = {}, options = {} } = {}) {
    if (this.$_hasObserver(node, options)) {
      this.$_cb([{ target: node, ...entry }]);
    }
  }

  // eslint-disable-next-line camelcase
  $_hasObserver(node, options = {}) {
    return this.$_observers.some(
      ([obvNode, obvOptions]) => node === obvNode && isMatch(options, obvOptions),
    );
  }
}

class MockIntersectionObserver extends MockObserver {
  unobserve(node) {
    this.$_observers = this.$_observers.filter(([obvNode]) => node === obvNode);
  }
}

/**
 * Use this function to setup a mock observer instance in place of the given DOM Observer
 *
 * Example:
 * ```
 * describe('', () => {
 *   const { trigger: triggerMutate } = useMockMutationObserver();
 *
 *   it('test', () => {
 *     triggerMutate(el, { options: { childList: true }, entry: { } });
 *   });
 * })
 * ```
 *
 * @param {String} key
 */
const useMockObserver = (key, createMock) => {
  let mockObservers = [];
  let origObserver;

  beforeEach(() => {
    origObserver = global[key];
    global[key] = jest.fn().mockImplementation((...args) => {
      const mockObserver = createMock(...args);
      mockObservers.push(mockObserver);
      return mockObserver;
    });
  });

  afterEach(() => {
    mockObservers.forEach((x) => x.disconnect());
    mockObservers = [];
    global[key] = origObserver;
  });

  const trigger = (...args) => {
    mockObservers.forEach((observer) => {
      observer.$_triggerObserve(...args);
    });
  };

  return { trigger };
};

export const useMockIntersectionObserver = () =>
  useMockObserver('IntersectionObserver', (...args) => new MockIntersectionObserver(...args));

export const useMockMutationObserver = () =>
  useMockObserver('MutationObserver', (...args) => new MockObserver(...args));