Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'spec/frontend/__helpers__')
-rw-r--r--spec/frontend/__helpers__/experimentation_helper.js13
-rw-r--r--spec/frontend/__helpers__/mock_apollo_helper.js12
-rw-r--r--spec/frontend/__helpers__/vue_test_utils_helper.js73
-rw-r--r--spec/frontend/__helpers__/vue_test_utils_helper_spec.js208
-rw-r--r--spec/frontend/__helpers__/web_worker_fake.js71
-rw-r--r--spec/frontend/__helpers__/web_worker_mock.js10
-rw-r--r--spec/frontend/__helpers__/web_worker_transformer.js18
7 files changed, 387 insertions, 18 deletions
diff --git a/spec/frontend/__helpers__/experimentation_helper.js b/spec/frontend/__helpers__/experimentation_helper.js
index c08c25155e8..7a2ef61216a 100644
--- a/spec/frontend/__helpers__/experimentation_helper.js
+++ b/spec/frontend/__helpers__/experimentation_helper.js
@@ -12,3 +12,16 @@ export function withGonExperiment(experimentKey, value = true) {
window.gon = origGon;
});
}
+// This helper is for specs that use `gitlab-experiment` utilities, which have a different schema that gets pushed via Gon compared to `Experimentation Module`
+export function assignGitlabExperiment(experimentKey, variant) {
+ let origGon;
+
+ beforeEach(() => {
+ origGon = window.gon;
+ window.gon = { experiment: { [experimentKey]: { variant } } };
+ });
+
+ afterEach(() => {
+ window.gon = origGon;
+ });
+}
diff --git a/spec/frontend/__helpers__/mock_apollo_helper.js b/spec/frontend/__helpers__/mock_apollo_helper.js
index 914cce1d662..bd97a06071a 100644
--- a/spec/frontend/__helpers__/mock_apollo_helper.js
+++ b/spec/frontend/__helpers__/mock_apollo_helper.js
@@ -2,11 +2,15 @@ import { InMemoryCache } from 'apollo-cache-inmemory';
import { createMockClient } from 'mock-apollo-client';
import VueApollo from 'vue-apollo';
-export default (handlers = [], resolvers = {}) => {
- const fragmentMatcher = { match: () => true };
+const defaultCacheOptions = {
+ fragmentMatcher: { match: () => true },
+ addTypename: false,
+};
+
+export default (handlers = [], resolvers = {}, cacheOptions = {}) => {
const cache = new InMemoryCache({
- fragmentMatcher,
- addTypename: false,
+ ...defaultCacheOptions,
+ ...cacheOptions,
});
const mockClient = createMockClient({ cache, resolvers });
diff --git a/spec/frontend/__helpers__/vue_test_utils_helper.js b/spec/frontend/__helpers__/vue_test_utils_helper.js
index d6132ef84ac..a94cee84f74 100644
--- a/spec/frontend/__helpers__/vue_test_utils_helper.js
+++ b/spec/frontend/__helpers__/vue_test_utils_helper.js
@@ -1,4 +1,6 @@
-import { isArray } from 'lodash';
+import * as testingLibrary from '@testing-library/dom';
+import { createWrapper, WrapperArray, mount, shallowMount } from '@vue/test-utils';
+import { isArray, upperFirst } from 'lodash';
const vNodeContainsText = (vnode, text) =>
(vnode.text && vnode.text.includes(text)) ||
@@ -37,6 +39,17 @@ export const waitForMutation = (store, expectedMutationType) =>
});
export const extendedWrapper = (wrapper) => {
+ // https://testing-library.com/docs/queries/about
+ const AVAILABLE_QUERIES = [
+ 'byRole',
+ 'byLabelText',
+ 'byPlaceholderText',
+ 'byText',
+ 'byDisplayValue',
+ 'byAltText',
+ 'byTitle',
+ ];
+
if (isArray(wrapper) || !wrapper?.find) {
// eslint-disable-next-line no-console
console.warn(
@@ -56,5 +69,63 @@ export const extendedWrapper = (wrapper) => {
return this.findAll(`[data-testid="${id}"]`);
},
},
+ // `findBy`
+ ...AVAILABLE_QUERIES.reduce((accumulator, query) => {
+ return {
+ ...accumulator,
+ [`find${upperFirst(query)}`]: {
+ value(text, options = {}) {
+ const elements = testingLibrary[`queryAll${upperFirst(query)}`](
+ wrapper.element,
+ text,
+ options,
+ );
+
+ // Return VTU `ErrorWrapper` if element is not found
+ // https://github.com/vuejs/vue-test-utils/blob/dev/packages/test-utils/src/error-wrapper.js
+ // VTU does not expose `ErrorWrapper` so, as of now, this is the best way to
+ // create an `ErrorWrapper`
+ if (!elements.length) {
+ const emptyElement = document.createElement('div');
+
+ return createWrapper(emptyElement).find('testing-library-element-not-found');
+ }
+
+ return createWrapper(elements[0], this.options || {});
+ },
+ },
+ };
+ }, {}),
+ // `findAllBy`
+ ...AVAILABLE_QUERIES.reduce((accumulator, query) => {
+ return {
+ ...accumulator,
+ [`findAll${upperFirst(query)}`]: {
+ value(text, options = {}) {
+ const elements = testingLibrary[`queryAll${upperFirst(query)}`](
+ wrapper.element,
+ text,
+ options,
+ );
+
+ const wrappers = elements.map((element) => {
+ const elementWrapper = createWrapper(element, this.options || {});
+ elementWrapper.selector = text;
+
+ return elementWrapper;
+ });
+
+ const wrapperArray = new WrapperArray(wrappers);
+ wrapperArray.selector = text;
+
+ return wrapperArray;
+ },
+ },
+ };
+ }, {}),
});
};
+
+export const shallowMountExtended = (...args) => extendedWrapper(shallowMount(...args));
+
+export const mountExtended = (...args) => extendedWrapper(mount(...args));
diff --git a/spec/frontend/__helpers__/vue_test_utils_helper_spec.js b/spec/frontend/__helpers__/vue_test_utils_helper_spec.js
index d4f8e36c169..dfe5a483223 100644
--- a/spec/frontend/__helpers__/vue_test_utils_helper_spec.js
+++ b/spec/frontend/__helpers__/vue_test_utils_helper_spec.js
@@ -1,7 +1,27 @@
-import { shallowMount } from '@vue/test-utils';
-import { extendedWrapper, shallowWrapperContainsSlotText } from './vue_test_utils_helper';
+import * as testingLibrary from '@testing-library/dom';
+import * as vtu from '@vue/test-utils';
+import {
+ shallowMount,
+ Wrapper as VTUWrapper,
+ WrapperArray as VTUWrapperArray,
+} from '@vue/test-utils';
+import {
+ extendedWrapper,
+ shallowMountExtended,
+ mountExtended,
+ shallowWrapperContainsSlotText,
+} from './vue_test_utils_helper';
+
+jest.mock('@testing-library/dom', () => ({
+ __esModule: true,
+ ...jest.requireActual('@testing-library/dom'),
+}));
describe('Vue test utils helpers', () => {
+ afterAll(() => {
+ jest.unmock('@testing-library/dom');
+ });
+
describe('shallowWrapperContainsSlotText', () => {
const mockText = 'text';
const mockSlot = `<div>${mockText}</div>`;
@@ -84,7 +104,7 @@ describe('Vue test utils helpers', () => {
);
});
- it('should find the component by test id', () => {
+ it('should find the element by test id', () => {
expect(mockComponent.findByTestId(testId).exists()).toBe(true);
});
});
@@ -105,5 +125,187 @@ describe('Vue test utils helpers', () => {
expect(mockComponent.findAllByTestId(testId)).toHaveLength(2);
});
});
+
+ describe.each`
+ findMethod | expectedQuery
+ ${'findByRole'} | ${'queryAllByRole'}
+ ${'findByLabelText'} | ${'queryAllByLabelText'}
+ ${'findByPlaceholderText'} | ${'queryAllByPlaceholderText'}
+ ${'findByText'} | ${'queryAllByText'}
+ ${'findByDisplayValue'} | ${'queryAllByDisplayValue'}
+ ${'findByAltText'} | ${'queryAllByAltText'}
+ `('$findMethod', ({ findMethod, expectedQuery }) => {
+ const text = 'foo bar';
+ const options = { selector: 'div' };
+ const mockDiv = document.createElement('div');
+
+ let wrapper;
+ beforeEach(() => {
+ wrapper = extendedWrapper(
+ shallowMount({
+ template: `<div>foo bar</div>`,
+ }),
+ );
+ });
+
+ it(`calls Testing Library \`${expectedQuery}\` function with correct parameters`, () => {
+ jest.spyOn(testingLibrary, expectedQuery).mockImplementation(() => [mockDiv]);
+
+ wrapper[findMethod](text, options);
+
+ expect(testingLibrary[expectedQuery]).toHaveBeenLastCalledWith(
+ wrapper.element,
+ text,
+ options,
+ );
+ });
+
+ describe('when element is found', () => {
+ beforeEach(() => {
+ jest.spyOn(testingLibrary, expectedQuery).mockImplementation(() => [mockDiv]);
+ jest.spyOn(vtu, 'createWrapper');
+ });
+
+ it('returns a VTU wrapper', () => {
+ const result = wrapper[findMethod](text, options);
+
+ expect(vtu.createWrapper).toHaveBeenCalledWith(mockDiv, wrapper.options);
+ expect(result).toBeInstanceOf(VTUWrapper);
+ });
+ });
+
+ describe('when multiple elements are found', () => {
+ beforeEach(() => {
+ const mockSpan = document.createElement('span');
+ jest.spyOn(testingLibrary, expectedQuery).mockImplementation(() => [mockDiv, mockSpan]);
+ jest.spyOn(vtu, 'createWrapper');
+ });
+
+ it('returns the first element as a VTU wrapper', () => {
+ const result = wrapper[findMethod](text, options);
+
+ expect(vtu.createWrapper).toHaveBeenCalledWith(mockDiv, wrapper.options);
+ expect(result).toBeInstanceOf(VTUWrapper);
+ });
+ });
+
+ describe('when element is not found', () => {
+ beforeEach(() => {
+ jest.spyOn(testingLibrary, expectedQuery).mockImplementation(() => []);
+ });
+
+ it('returns a VTU error wrapper', () => {
+ expect(wrapper[findMethod](text, options).exists()).toBe(false);
+ });
+ });
+ });
+
+ describe.each`
+ findMethod | expectedQuery
+ ${'findAllByRole'} | ${'queryAllByRole'}
+ ${'findAllByLabelText'} | ${'queryAllByLabelText'}
+ ${'findAllByPlaceholderText'} | ${'queryAllByPlaceholderText'}
+ ${'findAllByText'} | ${'queryAllByText'}
+ ${'findAllByDisplayValue'} | ${'queryAllByDisplayValue'}
+ ${'findAllByAltText'} | ${'queryAllByAltText'}
+ `('$findMethod', ({ findMethod, expectedQuery }) => {
+ const text = 'foo bar';
+ const options = { selector: 'div' };
+ const mockElements = [
+ document.createElement('li'),
+ document.createElement('li'),
+ document.createElement('li'),
+ ];
+
+ let wrapper;
+ beforeEach(() => {
+ wrapper = extendedWrapper(
+ shallowMount({
+ template: `
+ <ul>
+ <li>foo</li>
+ <li>bar</li>
+ <li>baz</li>
+ </ul>
+ `,
+ }),
+ );
+ });
+
+ it(`calls Testing Library \`${expectedQuery}\` function with correct parameters`, () => {
+ jest.spyOn(testingLibrary, expectedQuery).mockImplementation(() => mockElements);
+
+ wrapper[findMethod](text, options);
+
+ expect(testingLibrary[expectedQuery]).toHaveBeenLastCalledWith(
+ wrapper.element,
+ text,
+ options,
+ );
+ });
+
+ describe('when elements are found', () => {
+ beforeEach(() => {
+ jest.spyOn(testingLibrary, expectedQuery).mockImplementation(() => mockElements);
+ });
+
+ it('returns a VTU wrapper array', () => {
+ const result = wrapper[findMethod](text, options);
+
+ expect(result).toBeInstanceOf(VTUWrapperArray);
+ expect(
+ result.wrappers.every(
+ (resultWrapper) =>
+ resultWrapper instanceof VTUWrapper && resultWrapper.options === wrapper.options,
+ ),
+ ).toBe(true);
+ expect(result.length).toBe(3);
+ });
+ });
+
+ describe('when elements are not found', () => {
+ beforeEach(() => {
+ jest.spyOn(testingLibrary, expectedQuery).mockImplementation(() => []);
+ });
+
+ it('returns an empty VTU wrapper array', () => {
+ const result = wrapper[findMethod](text, options);
+
+ expect(result).toBeInstanceOf(VTUWrapperArray);
+ expect(result.length).toBe(0);
+ });
+ });
+ });
+ });
+
+ describe.each`
+ mountExtendedFunction | expectedMountFunction
+ ${shallowMountExtended} | ${'shallowMount'}
+ ${mountExtended} | ${'mount'}
+ `('$mountExtendedFunction', ({ mountExtendedFunction, expectedMountFunction }) => {
+ const FakeComponent = jest.fn();
+ const options = {
+ propsData: {
+ foo: 'bar',
+ },
+ };
+
+ beforeEach(() => {
+ const mockWrapper = { find: jest.fn() };
+ jest.spyOn(vtu, expectedMountFunction).mockImplementation(() => mockWrapper);
+ });
+
+ it(`calls \`${expectedMountFunction}\` with passed arguments`, () => {
+ mountExtendedFunction(FakeComponent, options);
+
+ expect(vtu[expectedMountFunction]).toHaveBeenCalledWith(FakeComponent, options);
+ });
+
+ it('returns extended wrapper', () => {
+ const result = mountExtendedFunction(FakeComponent, options);
+
+ expect(result).toHaveProperty('find');
+ expect(result).toHaveProperty('findByTestId');
+ });
});
});
diff --git a/spec/frontend/__helpers__/web_worker_fake.js b/spec/frontend/__helpers__/web_worker_fake.js
new file mode 100644
index 00000000000..041a9bd8540
--- /dev/null
+++ b/spec/frontend/__helpers__/web_worker_fake.js
@@ -0,0 +1,71 @@
+import path from 'path';
+
+const isRelative = (pathArg) => pathArg.startsWith('.');
+
+const transformRequirePath = (base, pathArg) => {
+ if (!isRelative(pathArg)) {
+ return pathArg;
+ }
+
+ return path.resolve(base, pathArg);
+};
+
+const createRelativeRequire = (filename) => {
+ const rel = path.relative(__dirname, path.dirname(filename));
+ const base = path.resolve(__dirname, rel);
+
+ // reason: Dynamic require should be fine here since the code is dynamically evaluated anyways.
+ // eslint-disable-next-line import/no-dynamic-require, global-require
+ return (pathArg) => require(transformRequirePath(base, pathArg));
+};
+
+/**
+ * Simulates a WebWorker module similar to the kind created by Webpack's [`worker-loader`][1]
+ *
+ * [1]: https://webpack.js.org/loaders/worker-loader/
+ */
+export class FakeWebWorker {
+ /**
+ * Constructs a new FakeWebWorker instance
+ *
+ * @param {String} filename is the full path of the code, which is used to resolve relative imports.
+ * @param {String} code is the raw code of the web worker, which is dynamically evaluated on construction.
+ */
+ constructor(filename, code) {
+ let isAlive = true;
+
+ const clientTarget = new EventTarget();
+ const workerTarget = new EventTarget();
+
+ this.addEventListener = (...args) => clientTarget.addEventListener(...args);
+ this.removeEventListener = (...args) => clientTarget.removeEventListener(...args);
+ this.postMessage = (message) => {
+ if (!isAlive) {
+ return;
+ }
+
+ workerTarget.dispatchEvent(new MessageEvent('message', { data: message }));
+ };
+ this.terminate = () => {
+ isAlive = false;
+ };
+
+ const workerScope = {
+ addEventListener: (...args) => workerTarget.addEventListener(...args),
+ removeEventListener: (...args) => workerTarget.removeEventListener(...args),
+ postMessage: (message) => {
+ if (!isAlive) {
+ return;
+ }
+
+ clientTarget.dispatchEvent(new MessageEvent('message', { data: message }));
+ },
+ };
+
+ // reason: `no-new-func` is like `eval` except it only executed on global scope and it's easy
+ // to pass in local references. `eval` is very unsafe in production, but in our test environment
+ // we shold be fine.
+ // eslint-disable-next-line no-new-func
+ Function('self', 'require', code)(workerScope, createRelativeRequire(filename));
+ }
+}
diff --git a/spec/frontend/__helpers__/web_worker_mock.js b/spec/frontend/__helpers__/web_worker_mock.js
deleted file mode 100644
index 2b4a391e1d2..00000000000
--- a/spec/frontend/__helpers__/web_worker_mock.js
+++ /dev/null
@@ -1,10 +0,0 @@
-/* eslint-disable class-methods-use-this */
-export default class WebWorkerMock {
- addEventListener() {}
-
- removeEventListener() {}
-
- terminate() {}
-
- postMessage() {}
-}
diff --git a/spec/frontend/__helpers__/web_worker_transformer.js b/spec/frontend/__helpers__/web_worker_transformer.js
new file mode 100644
index 00000000000..5b2f7d77947
--- /dev/null
+++ b/spec/frontend/__helpers__/web_worker_transformer.js
@@ -0,0 +1,18 @@
+/* eslint-disable import/no-commonjs */
+const babelJestTransformer = require('babel-jest');
+
+// This Jest will transform the code of a WebWorker module into a FakeWebWorker subclass.
+// This is meant to mirror Webpack's [`worker-loader`][1].
+// [1]: https://webpack.js.org/loaders/worker-loader/
+module.exports = {
+ process: (contentArg, filename, ...args) => {
+ const { code: content } = babelJestTransformer.process(contentArg, filename, ...args);
+
+ return `const { FakeWebWorker } = require("helpers/web_worker_fake");
+ module.exports = class JestTransformedWorker extends FakeWebWorker {
+ constructor() {
+ super(${JSON.stringify(filename)}, ${JSON.stringify(content)});
+ }
+ };`;
+ },
+};