summaryrefslogtreecommitdiff
path: root/spec/frontend/helpers
diff options
context:
space:
mode:
Diffstat (limited to 'spec/frontend/helpers')
-rw-r--r--spec/frontend/helpers/dom_shims/element_scroll_to.js6
-rw-r--r--spec/frontend/helpers/dom_shims/image_element_properties.js2
-rw-r--r--spec/frontend/helpers/dom_shims/index.js2
-rw-r--r--spec/frontend/helpers/dom_shims/mutation_observer.js7
-rw-r--r--spec/frontend/helpers/local_storage_helper.js20
-rw-r--r--spec/frontend/helpers/local_storage_helper_spec.js21
-rw-r--r--spec/frontend/helpers/mock_dom_observer.js94
-rw-r--r--spec/frontend/helpers/mock_window_location_helper.js43
-rw-r--r--spec/frontend/helpers/scroll_into_view_promise.js28
-rw-r--r--spec/frontend/helpers/set_window_location_helper_spec.js2
-rw-r--r--spec/frontend/helpers/vue_mock_directive.js17
-rw-r--r--spec/frontend/helpers/wait_for_attribute_change.js16
12 files changed, 206 insertions, 52 deletions
diff --git a/spec/frontend/helpers/dom_shims/element_scroll_to.js b/spec/frontend/helpers/dom_shims/element_scroll_to.js
new file mode 100644
index 00000000000..68f8a115865
--- /dev/null
+++ b/spec/frontend/helpers/dom_shims/element_scroll_to.js
@@ -0,0 +1,6 @@
+Element.prototype.scrollTo = jest.fn().mockImplementation(function scrollTo(x, y) {
+ this.scrollLeft = x;
+ this.scrollTop = y;
+
+ this.dispatchEvent(new Event('scroll'));
+});
diff --git a/spec/frontend/helpers/dom_shims/image_element_properties.js b/spec/frontend/helpers/dom_shims/image_element_properties.js
index 525246e6ade..d94c157e44d 100644
--- a/spec/frontend/helpers/dom_shims/image_element_properties.js
+++ b/spec/frontend/helpers/dom_shims/image_element_properties.js
@@ -1,6 +1,6 @@
Object.defineProperty(global.HTMLImageElement.prototype, 'src', {
get() {
- return this.$_jest_src;
+ return this.$_jest_src || this.getAttribute('src');
},
set(val) {
this.$_jest_src = val;
diff --git a/spec/frontend/helpers/dom_shims/index.js b/spec/frontend/helpers/dom_shims/index.js
index 17a2090d2f1..d18bb94c107 100644
--- a/spec/frontend/helpers/dom_shims/index.js
+++ b/spec/frontend/helpers/dom_shims/index.js
@@ -1,8 +1,10 @@
import './element_scroll_into_view';
import './element_scroll_by';
+import './element_scroll_to';
import './form_element';
import './get_client_rects';
import './inner_text';
+import './mutation_observer';
import './window_scroll_to';
import './scroll_by';
import './size_properties';
diff --git a/spec/frontend/helpers/dom_shims/mutation_observer.js b/spec/frontend/helpers/dom_shims/mutation_observer.js
new file mode 100644
index 00000000000..68c494f19ea
--- /dev/null
+++ b/spec/frontend/helpers/dom_shims/mutation_observer.js
@@ -0,0 +1,7 @@
+/* eslint-disable class-methods-use-this */
+class MutationObserverStub {
+ disconnect() {}
+ observe() {}
+}
+
+global.MutationObserver = MutationObserverStub;
diff --git a/spec/frontend/helpers/local_storage_helper.js b/spec/frontend/helpers/local_storage_helper.js
index 48e66b11767..a66c31d1353 100644
--- a/spec/frontend/helpers/local_storage_helper.js
+++ b/spec/frontend/helpers/local_storage_helper.js
@@ -28,12 +28,20 @@ const useLocalStorage = fn => {
/**
* Create an object with the localStorage interface but `jest.fn()` implementations.
*/
-export const createLocalStorageSpy = () => ({
- clear: jest.fn(),
- getItem: jest.fn(),
- setItem: jest.fn(),
- removeItem: jest.fn(),
-});
+export const createLocalStorageSpy = () => {
+ let storage = {};
+
+ return {
+ clear: jest.fn(() => {
+ storage = {};
+ }),
+ getItem: jest.fn(key => storage[key]),
+ setItem: jest.fn((key, value) => {
+ storage[key] = value;
+ }),
+ removeItem: jest.fn(key => delete storage[key]),
+ };
+};
/**
* Before each test, overwrite `window.localStorage` with a spy implementation.
diff --git a/spec/frontend/helpers/local_storage_helper_spec.js b/spec/frontend/helpers/local_storage_helper_spec.js
new file mode 100644
index 00000000000..18aec0f329a
--- /dev/null
+++ b/spec/frontend/helpers/local_storage_helper_spec.js
@@ -0,0 +1,21 @@
+import { useLocalStorageSpy } from './local_storage_helper';
+
+useLocalStorageSpy();
+
+describe('localStorage helper', () => {
+ it('mocks localStorage but works exactly like original localStorage', () => {
+ localStorage.setItem('test', 'testing');
+ localStorage.setItem('test2', 'testing');
+
+ expect(localStorage.getItem('test')).toBe('testing');
+
+ localStorage.removeItem('test', 'testing');
+
+ expect(localStorage.getItem('test')).toBeUndefined();
+ expect(localStorage.getItem('test2')).toBe('testing');
+
+ localStorage.clear();
+
+ expect(localStorage.getItem('test2')).toBeUndefined();
+ });
+});
diff --git a/spec/frontend/helpers/mock_dom_observer.js b/spec/frontend/helpers/mock_dom_observer.js
new file mode 100644
index 00000000000..7aac51f6264
--- /dev/null
+++ b/spec/frontend/helpers/mock_dom_observer.js
@@ -0,0 +1,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 babel/camelcase
+ $_triggerObserve(node, { entry = {}, options = {} } = {}) {
+ if (this.$_hasObserver(node, options)) {
+ this.$_cb([{ target: node, ...entry }]);
+ }
+ }
+
+ // eslint-disable-next-line babel/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', () => {
+ * trigger(el, { options: { childList: true }, entry: { } });
+ * });
+ * })
+ * ```
+ *
+ * @param {String} key
+ */
+const useMockObserver = (key, createMock) => {
+ let mockObserver;
+ let origObserver;
+
+ beforeEach(() => {
+ origObserver = global[key];
+ global[key] = jest.fn().mockImplementation((...args) => {
+ mockObserver = createMock(...args);
+ return mockObserver;
+ });
+ });
+
+ afterEach(() => {
+ mockObserver = null;
+ global[key] = origObserver;
+ });
+
+ const trigger = (...args) => {
+ if (!mockObserver) {
+ return;
+ }
+
+ mockObserver.$_triggerObserve(...args);
+ };
+
+ return { trigger };
+};
+
+export const useMockIntersectionObserver = () =>
+ useMockObserver('IntersectionObserver', (...args) => new MockIntersectionObserver(...args));
+
+export const useMockMutationObserver = () =>
+ useMockObserver('MutationObserver', (...args) => new MockObserver(...args));
diff --git a/spec/frontend/helpers/mock_window_location_helper.js b/spec/frontend/helpers/mock_window_location_helper.js
new file mode 100644
index 00000000000..175044d1fce
--- /dev/null
+++ b/spec/frontend/helpers/mock_window_location_helper.js
@@ -0,0 +1,43 @@
+/**
+ * Manage the instance of a custom `window.location`
+ *
+ * This only encapsulates the setup / teardown logic so that it can easily be
+ * reused with different implementations (i.e. a spy or a [fake][1])
+ *
+ * [1]: https://stackoverflow.com/a/41434763/1708147
+ *
+ * @param {() => any} fn Function that returns the object to use for window.location
+ */
+const useMockLocation = fn => {
+ const origWindowLocation = window.location;
+ let currentWindowLocation;
+
+ Object.defineProperty(window, 'location', {
+ get: () => currentWindowLocation,
+ });
+
+ beforeEach(() => {
+ currentWindowLocation = fn();
+ });
+
+ afterEach(() => {
+ currentWindowLocation = origWindowLocation;
+ });
+};
+
+/**
+ * Create an object with the location interface but `jest.fn()` implementations.
+ */
+export const createWindowLocationSpy = () => {
+ return {
+ assign: jest.fn(),
+ reload: jest.fn(),
+ replace: jest.fn(),
+ toString: jest.fn(),
+ };
+};
+
+/**
+ * Before each test, overwrite `window.location` with a spy implementation.
+ */
+export const useMockLocationHelper = () => useMockLocation(createWindowLocationSpy);
diff --git a/spec/frontend/helpers/scroll_into_view_promise.js b/spec/frontend/helpers/scroll_into_view_promise.js
deleted file mode 100644
index 0edea2103da..00000000000
--- a/spec/frontend/helpers/scroll_into_view_promise.js
+++ /dev/null
@@ -1,28 +0,0 @@
-export default function scrollIntoViewPromise(intersectionTarget, timeout = 100, maxTries = 5) {
- return new Promise((resolve, reject) => {
- let intersectionObserver;
- let retry = 0;
-
- const intervalId = setInterval(() => {
- if (retry >= maxTries) {
- intersectionObserver.disconnect();
- clearInterval(intervalId);
- reject(new Error(`Could not scroll target into viewPort within ${timeout * maxTries} ms`));
- }
- retry += 1;
- intersectionTarget.scrollIntoView();
- }, timeout);
-
- intersectionObserver = new IntersectionObserver(entries => {
- if (entries[0].isIntersecting) {
- intersectionObserver.disconnect();
- clearInterval(intervalId);
- resolve();
- }
- });
-
- intersectionObserver.observe(intersectionTarget);
-
- intersectionTarget.scrollIntoView();
- });
-}
diff --git a/spec/frontend/helpers/set_window_location_helper_spec.js b/spec/frontend/helpers/set_window_location_helper_spec.js
index 2a2c024c824..da609b6bbf0 100644
--- a/spec/frontend/helpers/set_window_location_helper_spec.js
+++ b/spec/frontend/helpers/set_window_location_helper_spec.js
@@ -33,7 +33,7 @@ describe('setWindowLocation', () => {
it.each([null, 1, undefined, false, '', 'gitlab.com'])(
'throws an error when called with an invalid url: "%s"',
invalidUrl => {
- expect(() => setWindowLocation(invalidUrl)).toThrow(new TypeError('Invalid URL'));
+ expect(() => setWindowLocation(invalidUrl)).toThrow(/Invalid URL/);
expect(window.location).toBe(originalLocation);
},
);
diff --git a/spec/frontend/helpers/vue_mock_directive.js b/spec/frontend/helpers/vue_mock_directive.js
new file mode 100644
index 00000000000..699fe3eab26
--- /dev/null
+++ b/spec/frontend/helpers/vue_mock_directive.js
@@ -0,0 +1,17 @@
+export const getKey = name => `$_gl_jest_${name}`;
+
+export const getBinding = (el, name) => el[getKey(name)];
+
+export const createMockDirective = () => ({
+ bind(el, { name, value, arg, modifiers }) {
+ el[getKey(name)] = {
+ value,
+ arg,
+ modifiers,
+ };
+ },
+
+ unbind(el, { name }) {
+ delete el[getKey(name)];
+ },
+});
diff --git a/spec/frontend/helpers/wait_for_attribute_change.js b/spec/frontend/helpers/wait_for_attribute_change.js
deleted file mode 100644
index 8f22d569222..00000000000
--- a/spec/frontend/helpers/wait_for_attribute_change.js
+++ /dev/null
@@ -1,16 +0,0 @@
-export default (domElement, attributes, timeout = 1500) =>
- new Promise((resolve, reject) => {
- let observer;
- const timeoutId = setTimeout(() => {
- observer.disconnect();
- reject(new Error(`Could not see an attribute update within ${timeout} ms`));
- }, timeout);
-
- observer = new MutationObserver(() => {
- clearTimeout(timeoutId);
- observer.disconnect();
- resolve();
- });
-
- observer.observe(domElement, { attributes: true, attributeFilter: attributes });
- });