Commit 139c8fb9 authored by Natalia Tepluhina's avatar Natalia Tepluhina

Merge branch 'leipert-rethink-observer-mock-implementation' into 'master'

Mock observers globally in Jest

See merge request gitlab-org/gitlab!67247
parents 0bb76140 ae0a4c9e
...@@ -44,7 +44,7 @@ export default class LazyLoader { ...@@ -44,7 +44,7 @@ export default class LazyLoader {
startContentObserver() { startContentObserver() {
const contentNode = document.querySelector(this.observerNode) || document.querySelector('body'); const contentNode = document.querySelector(this.observerNode) || document.querySelector('body');
if (contentNode) { if (contentNode && !this.mutationObserver) {
this.mutationObserver = new MutationObserver(() => this.searchLazyImages()); this.mutationObserver = new MutationObserver(() => this.searchLazyImages());
this.mutationObserver.observe(contentNode, { this.mutationObserver.observe(contentNode, {
......
...@@ -2,7 +2,6 @@ import { mount } from '@vue/test-utils'; ...@@ -2,7 +2,6 @@ import { mount } from '@vue/test-utils';
import Vue, { nextTick } from 'vue'; import Vue, { nextTick } from 'vue';
import Vuex from 'vuex'; import Vuex from 'vuex';
import BoardScope from 'ee/boards/components/board_scope.vue'; import BoardScope from 'ee/boards/components/board_scope.vue';
import { useMockIntersectionObserver } from 'helpers/mock_dom_observer';
import { TEST_HOST } from 'helpers/test_constants'; import { TEST_HOST } from 'helpers/test_constants';
import LabelsSelect from '~/vue_shared/components/sidebar/labels_select_vue/labels_select_root.vue'; import LabelsSelect from '~/vue_shared/components/sidebar/labels_select_vue/labels_select_root.vue';
...@@ -11,7 +10,6 @@ Vue.use(Vuex); ...@@ -11,7 +10,6 @@ Vue.use(Vuex);
describe('BoardScope', () => { describe('BoardScope', () => {
let wrapper; let wrapper;
let store; let store;
useMockIntersectionObserver();
const createStore = () => { const createStore = () => {
return new Vuex.Store({ return new Vuex.Store({
......
...@@ -52,7 +52,7 @@ class MockIntersectionObserver extends MockObserver { ...@@ -52,7 +52,7 @@ class MockIntersectionObserver extends MockObserver {
* const { trigger: triggerMutate } = useMockMutationObserver(); * const { trigger: triggerMutate } = useMockMutationObserver();
* *
* it('test', () => { * it('test', () => {
* trigger(el, { options: { childList: true }, entry: { } }); * triggerMutate(el, { options: { childList: true }, entry: { } });
* }); * });
* }) * })
* ``` * ```
...@@ -60,33 +60,31 @@ class MockIntersectionObserver extends MockObserver { ...@@ -60,33 +60,31 @@ class MockIntersectionObserver extends MockObserver {
* @param {String} key * @param {String} key
*/ */
const useMockObserver = (key, createMock) => { const useMockObserver = (key, createMock) => {
let mockObserver; let mockObservers = [];
let origObserver; let origObserver;
beforeEach(() => { beforeEach(() => {
origObserver = global[key]; origObserver = global[key];
global[key] = jest.fn().mockImplementation((...args) => { global[key] = jest.fn().mockImplementation((...args) => {
mockObserver = createMock(...args); const mockObserver = createMock(...args);
mockObservers.push(mockObserver);
return mockObserver; return mockObserver;
}); });
}); });
afterEach(() => { afterEach(() => {
mockObserver = null; mockObservers.forEach((x) => x.disconnect());
mockObservers = [];
global[key] = origObserver; global[key] = origObserver;
}); });
const trigger = (...args) => { const trigger = (...args) => {
if (!mockObserver) { mockObservers.forEach((observer) => {
return; observer.$_triggerObserve(...args);
} });
mockObserver.$_triggerObserve(...args);
}; };
const observersCount = () => mockObserver.$_observers.length; return { trigger };
return { trigger, observersCount };
}; };
export const useMockIntersectionObserver = () => export const useMockIntersectionObserver = () =>
......
...@@ -6,7 +6,6 @@ import VueApollo from 'vue-apollo'; ...@@ -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 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 updateHttpIntegrationMutation from 'ee_else_ce/alerts_settings/graphql/mutations/update_http_integration.mutation.graphql';
import createMockApollo from 'helpers/mock_apollo_helper'; import createMockApollo from 'helpers/mock_apollo_helper';
import { useMockIntersectionObserver } from 'helpers/mock_dom_observer';
import { extendedWrapper } from 'helpers/vue_test_utils_helper'; import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import waitForPromises from 'helpers/wait_for_promises'; import waitForPromises from 'helpers/wait_for_promises';
import IntegrationsList from '~/alerts_settings/components/alerts_integrations_list.vue'; import IntegrationsList from '~/alerts_settings/components/alerts_integrations_list.vue';
...@@ -57,7 +56,6 @@ describe('AlertsSettingsWrapper', () => { ...@@ -57,7 +56,6 @@ describe('AlertsSettingsWrapper', () => {
let wrapper; let wrapper;
let fakeApollo; let fakeApollo;
let destroyIntegrationHandler; let destroyIntegrationHandler;
useMockIntersectionObserver();
const httpMappingData = { const httpMappingData = {
payloadExample: '{"test: : "field"}', payloadExample: '{"test: : "field"}',
......
...@@ -88,13 +88,32 @@ class CustomEnvironment extends JSDOMEnvironment { ...@@ -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 */ /* eslint-disable no-useless-constructor, no-unused-vars, no-empty-function, class-methods-use-this */
constructor(callback) {} constructor(callback) {}
disconnect() {} disconnect() {}
observe(element, initObject) {} observe(element, initObject) {}
unobserve(element) {}
takeRecords() {
return [];
}
/* eslint-enable no-useless-constructor, no-unused-vars, no-empty-function, class-methods-use-this */ /* 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() { async teardown() {
......
...@@ -2,7 +2,6 @@ import { GlIntersectionObserver } from '@gitlab/ui'; ...@@ -2,7 +2,6 @@ import { GlIntersectionObserver } from '@gitlab/ui';
import { mount } from '@vue/test-utils'; import { mount } from '@vue/test-utils';
import MockAdapter from 'axios-mock-adapter'; import MockAdapter from 'axios-mock-adapter';
import { nextTick } from 'vue'; import { nextTick } from 'vue';
import { useMockIntersectionObserver } from 'helpers/mock_dom_observer';
import '~/behaviors/markdown/render_gfm'; import '~/behaviors/markdown/render_gfm';
import IssuableApp from '~/issue_show/components/app.vue'; import IssuableApp from '~/issue_show/components/app.vue';
import DescriptionComponent from '~/issue_show/components/description.vue'; import DescriptionComponent from '~/issue_show/components/description.vue';
...@@ -30,8 +29,6 @@ jest.mock('~/issue_show/event_hub'); ...@@ -30,8 +29,6 @@ jest.mock('~/issue_show/event_hub');
const REALTIME_REQUEST_STACK = [initialRequest, secondRequest]; const REALTIME_REQUEST_STACK = [initialRequest, secondRequest];
describe('Issuable output', () => { describe('Issuable output', () => {
useMockIntersectionObserver();
let mock; let mock;
let realtimeRequestCount = 0; let realtimeRequestCount = 0;
let wrapper; let wrapper;
......
import MockAdapter from 'axios-mock-adapter'; import MockAdapter from 'axios-mock-adapter';
import { useMockIntersectionObserver } from 'helpers/mock_dom_observer';
import waitForPromises from 'helpers/wait_for_promises'; import waitForPromises from 'helpers/wait_for_promises';
import { initIssuableApp } from '~/issue_show/issue'; import { initIssuableApp } from '~/issue_show/issue';
import * as parseData from '~/issue_show/utils/parse_data'; import * as parseData from '~/issue_show/utils/parse_data';
...@@ -10,8 +9,6 @@ import { appProps } from './mock_data/mock_data'; ...@@ -10,8 +9,6 @@ import { appProps } from './mock_data/mock_data';
const mock = new MockAdapter(axios); const mock = new MockAdapter(axios);
mock.onGet().reply(200); mock.onGet().reply(200);
useMockIntersectionObserver();
jest.mock('~/lib/utils/poll'); jest.mock('~/lib/utils/poll');
const setupHTML = (initialData) => { const setupHTML = (initialData) => {
......
...@@ -4,7 +4,7 @@ import { useMockMutationObserver } from 'helpers/mock_dom_observer'; ...@@ -4,7 +4,7 @@ import { useMockMutationObserver } from 'helpers/mock_dom_observer';
import Popovers from '~/popovers/components/popovers.vue'; import Popovers from '~/popovers/components/popovers.vue';
describe('popovers/components/popovers.vue', () => { describe('popovers/components/popovers.vue', () => {
const { trigger: triggerMutate, observersCount } = useMockMutationObserver(); const { trigger: triggerMutate } = useMockMutationObserver();
let wrapper; let wrapper;
const buildWrapper = (...targets) => { const buildWrapper = (...targets) => {
...@@ -120,10 +120,13 @@ describe('popovers/components/popovers.vue', () => { ...@@ -120,10 +120,13 @@ describe('popovers/components/popovers.vue', () => {
it('disconnects mutation observer on beforeDestroy', async () => { it('disconnects mutation observer on beforeDestroy', async () => {
await buildWrapper(createPopoverTarget()); await buildWrapper(createPopoverTarget());
const { observer } = wrapper.vm;
jest.spyOn(observer, 'disconnect');
expect(observersCount()).toBe(1); expect(observer.disconnect).toHaveBeenCalledTimes(0);
wrapper.destroy(); wrapper.destroy();
expect(observersCount()).toBe(0);
expect(observer.disconnect).toHaveBeenCalledTimes(1);
}); });
}); });
...@@ -4,7 +4,7 @@ import { useMockMutationObserver } from 'helpers/mock_dom_observer'; ...@@ -4,7 +4,7 @@ import { useMockMutationObserver } from 'helpers/mock_dom_observer';
import Tooltips from '~/tooltips/components/tooltips.vue'; import Tooltips from '~/tooltips/components/tooltips.vue';
describe('tooltips/components/tooltips.vue', () => { describe('tooltips/components/tooltips.vue', () => {
const { trigger: triggerMutate, observersCount } = useMockMutationObserver(); const { trigger: triggerMutate } = useMockMutationObserver();
let wrapper; let wrapper;
const buildWrapper = () => { const buildWrapper = () => {
...@@ -211,11 +211,14 @@ describe('tooltips/components/tooltips.vue', () => { ...@@ -211,11 +211,14 @@ describe('tooltips/components/tooltips.vue', () => {
it('disconnects mutation observer on beforeDestroy', () => { it('disconnects mutation observer on beforeDestroy', () => {
buildWrapper(); buildWrapper();
wrapper.vm.addTooltips([createTooltipTarget()]); wrapper.vm.addTooltips([createTooltipTarget()]);
const { observer } = wrapper.vm;
jest.spyOn(observer, 'disconnect');
expect(observersCount()).toBe(1); expect(observer.disconnect).toHaveBeenCalledTimes(0);
wrapper.destroy(); wrapper.destroy();
expect(observersCount()).toBe(0);
expect(observer.disconnect).toHaveBeenCalledTimes(1);
}); });
it('exposes hidden event', async () => { it('exposes hidden event', async () => {
......
import { useMockIntersectionObserver } from 'helpers/mock_dom_observer';
import autofocusonshow from '~/vue_shared/directives/autofocusonshow'; import autofocusonshow from '~/vue_shared/directives/autofocusonshow';
/** /**
...@@ -7,8 +6,6 @@ import autofocusonshow from '~/vue_shared/directives/autofocusonshow'; ...@@ -7,8 +6,6 @@ import autofocusonshow from '~/vue_shared/directives/autofocusonshow';
* on underlying DOM methods. * on underlying DOM methods.
*/ */
describe('AutofocusOnShow directive', () => { describe('AutofocusOnShow directive', () => {
useMockIntersectionObserver();
describe('with input invisible on component render', () => { describe('with input invisible on component render', () => {
let el; 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