Commit ae0a4c9e authored by Lukas Eipert's avatar Lukas Eipert

Mock observers globally in Jest

As JSDom is missing global mocks for Observers, we currently need to
mock observers manually in any spec that might use them under the hood.

Our existing mocks are only working on _one_ instance of observers each,
so we have refactored them to work on multiple instances as well.

In order to improve the performance on tests where the Observers aren't
tested, we add a NoopObserver to JSDom.

As an aside, JSDom actually implements a MutationObserver, so no need to
mock it!
parent 9aa0cc42
......@@ -44,7 +44,7 @@ export default class LazyLoader {
startContentObserver() {
const contentNode = document.querySelector(this.observerNode) || document.querySelector('body');
if (contentNode) {
if (contentNode && !this.mutationObserver) {
this.mutationObserver = new MutationObserver(() => this.searchLazyImages());
this.mutationObserver.observe(contentNode, {
......
......@@ -2,7 +2,6 @@ import { mount } from '@vue/test-utils';
import Vue, { nextTick } from 'vue';
import Vuex from 'vuex';
import BoardScope from 'ee/boards/components/board_scope.vue';
import { useMockIntersectionObserver } from 'helpers/mock_dom_observer';
import { TEST_HOST } from 'helpers/test_constants';
import LabelsSelect from '~/vue_shared/components/sidebar/labels_select_vue/labels_select_root.vue';
......@@ -11,7 +10,6 @@ Vue.use(Vuex);
describe('BoardScope', () => {
let wrapper;
let store;
useMockIntersectionObserver();
const createStore = () => {
return new Vuex.Store({
......
......@@ -52,7 +52,7 @@ class MockIntersectionObserver extends MockObserver {
* const { trigger: triggerMutate } = useMockMutationObserver();
*
* it('test', () => {
* trigger(el, { options: { childList: true }, entry: { } });
* triggerMutate(el, { options: { childList: true }, entry: { } });
* });
* })
* ```
......@@ -60,33 +60,31 @@ class MockIntersectionObserver extends MockObserver {
* @param {String} key
*/
const useMockObserver = (key, createMock) => {
let mockObserver;
let mockObservers = [];
let origObserver;
beforeEach(() => {
origObserver = global[key];
global[key] = jest.fn().mockImplementation((...args) => {
mockObserver = createMock(...args);
const mockObserver = createMock(...args);
mockObservers.push(mockObserver);
return mockObserver;
});
});
afterEach(() => {
mockObserver = null;
mockObservers.forEach((x) => x.disconnect());
mockObservers = [];
global[key] = origObserver;
});
const trigger = (...args) => {
if (!mockObserver) {
return;
}
mockObserver.$_triggerObserve(...args);
mockObservers.forEach((observer) => {
observer.$_triggerObserve(...args);
});
};
const observersCount = () => mockObserver.$_observers.length;
return { trigger, observersCount };
return { trigger };
};
export const useMockIntersectionObserver = () =>
......
......@@ -6,7 +6,6 @@ import VueApollo from 'vue-apollo';
import createHttpIntegrationMutation from 'ee_else_ce/alerts_settings/graphql/mutations/create_http_integration.mutation.graphql';
import updateHttpIntegrationMutation from 'ee_else_ce/alerts_settings/graphql/mutations/update_http_integration.mutation.graphql';
import createMockApollo from 'helpers/mock_apollo_helper';
import { useMockIntersectionObserver } from 'helpers/mock_dom_observer';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import waitForPromises from 'helpers/wait_for_promises';
import IntegrationsList from '~/alerts_settings/components/alerts_integrations_list.vue';
......@@ -57,7 +56,6 @@ describe('AlertsSettingsWrapper', () => {
let wrapper;
let fakeApollo;
let destroyIntegrationHandler;
useMockIntersectionObserver();
const httpMappingData = {
payloadExample: '{"test: : "field"}',
......
......@@ -88,13 +88,32 @@ class CustomEnvironment extends JSDOMEnvironment {
}),
});
this.global.PerformanceObserver = class {
/**
* JSDom doesn't have an own observer implementation, so this a Noop Observer.
* If you are testing functionality, related to observers, have a look at __helpers__/mock_dom_observer.js
*
* JSDom actually implements a _proper_ MutationObserver, so no need to mock it!
*/
class NoopObserver {
/* eslint-disable no-useless-constructor, no-unused-vars, no-empty-function, class-methods-use-this */
constructor(callback) {}
disconnect() {}
observe(element, initObject) {}
unobserve(element) {}
takeRecords() {
return [];
}
/* eslint-enable no-useless-constructor, no-unused-vars, no-empty-function, class-methods-use-this */
};
}
['IntersectionObserver', 'PerformanceObserver', 'ResizeObserver'].forEach((observer) => {
if (this.global[observer]) {
throw new Error(
`We overwrite an existing Observer in jsdom (${observer}), are you sure you want to do that?`,
);
}
this.global[observer] = NoopObserver;
});
}
async teardown() {
......
......@@ -2,7 +2,6 @@ import { GlIntersectionObserver } from '@gitlab/ui';
import { mount } from '@vue/test-utils';
import MockAdapter from 'axios-mock-adapter';
import { nextTick } from 'vue';
import { useMockIntersectionObserver } from 'helpers/mock_dom_observer';
import '~/behaviors/markdown/render_gfm';
import IssuableApp from '~/issue_show/components/app.vue';
import DescriptionComponent from '~/issue_show/components/description.vue';
......@@ -30,8 +29,6 @@ jest.mock('~/issue_show/event_hub');
const REALTIME_REQUEST_STACK = [initialRequest, secondRequest];
describe('Issuable output', () => {
useMockIntersectionObserver();
let mock;
let realtimeRequestCount = 0;
let wrapper;
......
import MockAdapter from 'axios-mock-adapter';
import { useMockIntersectionObserver } from 'helpers/mock_dom_observer';
import waitForPromises from 'helpers/wait_for_promises';
import { initIssuableApp } from '~/issue_show/issue';
import * as parseData from '~/issue_show/utils/parse_data';
......@@ -10,8 +9,6 @@ import { appProps } from './mock_data/mock_data';
const mock = new MockAdapter(axios);
mock.onGet().reply(200);
useMockIntersectionObserver();
jest.mock('~/lib/utils/poll');
const setupHTML = (initialData) => {
......
......@@ -4,7 +4,7 @@ import { useMockMutationObserver } from 'helpers/mock_dom_observer';
import Popovers from '~/popovers/components/popovers.vue';
describe('popovers/components/popovers.vue', () => {
const { trigger: triggerMutate, observersCount } = useMockMutationObserver();
const { trigger: triggerMutate } = useMockMutationObserver();
let wrapper;
const buildWrapper = (...targets) => {
......@@ -120,10 +120,13 @@ describe('popovers/components/popovers.vue', () => {
it('disconnects mutation observer on beforeDestroy', async () => {
await buildWrapper(createPopoverTarget());
const { observer } = wrapper.vm;
jest.spyOn(observer, 'disconnect');
expect(observersCount()).toBe(1);
expect(observer.disconnect).toHaveBeenCalledTimes(0);
wrapper.destroy();
expect(observersCount()).toBe(0);
expect(observer.disconnect).toHaveBeenCalledTimes(1);
});
});
......@@ -4,7 +4,7 @@ import { useMockMutationObserver } from 'helpers/mock_dom_observer';
import Tooltips from '~/tooltips/components/tooltips.vue';
describe('tooltips/components/tooltips.vue', () => {
const { trigger: triggerMutate, observersCount } = useMockMutationObserver();
const { trigger: triggerMutate } = useMockMutationObserver();
let wrapper;
const buildWrapper = () => {
......@@ -211,11 +211,14 @@ describe('tooltips/components/tooltips.vue', () => {
it('disconnects mutation observer on beforeDestroy', () => {
buildWrapper();
wrapper.vm.addTooltips([createTooltipTarget()]);
const { observer } = wrapper.vm;
jest.spyOn(observer, 'disconnect');
expect(observersCount()).toBe(1);
expect(observer.disconnect).toHaveBeenCalledTimes(0);
wrapper.destroy();
expect(observersCount()).toBe(0);
expect(observer.disconnect).toHaveBeenCalledTimes(1);
});
it('exposes hidden event', async () => {
......
import { useMockIntersectionObserver } from 'helpers/mock_dom_observer';
import autofocusonshow from '~/vue_shared/directives/autofocusonshow';
/**
......@@ -7,8 +6,6 @@ import autofocusonshow from '~/vue_shared/directives/autofocusonshow';
* on underlying DOM methods.
*/
describe('AutofocusOnShow directive', () => {
useMockIntersectionObserver();
describe('with input invisible on component render', () => {
let el;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment