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:
authorGitLab Bot <gitlab-bot@gitlab.com>2020-08-20 21:42:06 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2020-08-20 21:42:06 +0300
commit6e4e1050d9dba2b7b2523fdd1768823ab85feef4 (patch)
tree78be5963ec075d80116a932011d695dd33910b4e /spec/frontend_integration
parent1ce776de4ae122aba3f349c02c17cebeaa8ecf07 (diff)
Add latest changes from gitlab-org/gitlab@13-3-stable-ee
Diffstat (limited to 'spec/frontend_integration')
-rw-r--r--spec/frontend_integration/ide/ide_integration_spec.js88
-rw-r--r--spec/frontend_integration/test_helpers/factories/commit.js15
-rw-r--r--spec/frontend_integration/test_helpers/factories/commit_id.js21
-rw-r--r--spec/frontend_integration/test_helpers/factories/index.js2
-rw-r--r--spec/frontend_integration/test_helpers/fixtures.js10
-rw-r--r--spec/frontend_integration/test_helpers/mock_server/graphql.js21
-rw-r--r--spec/frontend_integration/test_helpers/mock_server/index.js45
-rw-r--r--spec/frontend_integration/test_helpers/mock_server/routes/404.js7
-rw-r--r--spec/frontend_integration/test_helpers/mock_server/routes/ci.js11
-rw-r--r--spec/frontend_integration/test_helpers/mock_server/routes/graphql.js11
-rw-r--r--spec/frontend_integration/test_helpers/mock_server/routes/index.js12
-rw-r--r--spec/frontend_integration/test_helpers/mock_server/routes/projects.js23
-rw-r--r--spec/frontend_integration/test_helpers/mock_server/routes/repository.js38
-rw-r--r--spec/frontend_integration/test_helpers/mock_server/use.js5
-rw-r--r--spec/frontend_integration/test_helpers/setup/index.js5
-rw-r--r--spec/frontend_integration/test_helpers/setup/setup_axios.js5
-rw-r--r--spec/frontend_integration/test_helpers/setup/setup_globals.js15
-rw-r--r--spec/frontend_integration/test_helpers/setup/setup_mock_server.js13
-rw-r--r--spec/frontend_integration/test_helpers/setup/setup_serializers.js3
-rw-r--r--spec/frontend_integration/test_helpers/snapshot_serializer.js18
-rw-r--r--spec/frontend_integration/test_helpers/utils/obj.js36
-rw-r--r--spec/frontend_integration/test_helpers/utils/obj_spec.js23
-rw-r--r--spec/frontend_integration/test_helpers/utils/overclock_timers.js65
-rw-r--r--spec/frontend_integration/test_setup.js1
24 files changed, 430 insertions, 63 deletions
diff --git a/spec/frontend_integration/ide/ide_integration_spec.js b/spec/frontend_integration/ide/ide_integration_spec.js
index 7e8fb3a32ee..91d89c26ec1 100644
--- a/spec/frontend_integration/ide/ide_integration_spec.js
+++ b/spec/frontend_integration/ide/ide_integration_spec.js
@@ -8,93 +8,55 @@
*
* See https://gitlab.com/gitlab-org/gitlab/-/issues/208800 for more information.
*/
-import MockAdapter from 'axios-mock-adapter';
-import axios from '~/lib/utils/axios_utils';
+import { TEST_HOST } from 'helpers/test_constants';
+import { useOverclockTimers } from 'test_helpers/utils/overclock_timers';
import { initIde } from '~/ide';
-
-jest.mock('~/api', () => {
- return {
- project: jest.fn().mockImplementation(() => new Promise(() => {})),
- };
-});
-
-jest.mock('~/ide/services/gql', () => {
- return {
- query: jest.fn().mockImplementation(() => new Promise(() => {})),
- };
-});
+import extendStore from '~/ide/stores/extend';
+
+const TEST_DATASET = {
+ emptyStateSvgPath: '/test/empty_state.svg',
+ noChangesStateSvgPath: '/test/no_changes_state.svg',
+ committedStateSvgPath: '/test/committed_state.svg',
+ pipelinesEmptyStateSvgPath: '/test/pipelines_empty_state.svg',
+ promotionSvgPath: '/test/promotion.svg',
+ ciHelpPagePath: '/test/ci_help_page',
+ webIDEHelpPagePath: '/test/web_ide_help_page',
+ clientsidePreviewEnabled: 'true',
+ renderWhitespaceInCode: 'false',
+ codesandboxBundlerUrl: 'test/codesandbox_bundler',
+};
describe('WebIDE', () => {
+ useOverclockTimers();
+
let vm;
let root;
- let mock;
- let initData;
- let location;
beforeEach(() => {
root = document.createElement('div');
- initData = {
- emptyStateSvgPath: '/test/empty_state.svg',
- noChangesStateSvgPath: '/test/no_changes_state.svg',
- committedStateSvgPath: '/test/committed_state.svg',
- pipelinesEmptyStateSvgPath: '/test/pipelines_empty_state.svg',
- promotionSvgPath: '/test/promotion.svg',
- ciHelpPagePath: '/test/ci_help_page',
- webIDEHelpPagePath: '/test/web_ide_help_page',
- clientsidePreviewEnabled: 'true',
- renderWhitespaceInCode: 'false',
- codesandboxBundlerUrl: 'test/codesandbox_bundler',
- };
+ document.body.appendChild(root);
- mock = new MockAdapter(axios);
- mock.onAny('*').reply(() => new Promise(() => {}));
-
- location = { pathname: '/-/ide/project/gitlab-test/test', search: '', hash: '' };
- Object.defineProperty(window, 'location', {
- get() {
- return location;
- },
+ global.jsdom.reconfigure({
+ url: `${TEST_HOST}/-/ide/project/gitlab-test/lorem-ipsum`,
});
});
afterEach(() => {
vm.$destroy();
vm = null;
-
- mock.restore();
+ root.remove();
});
const createComponent = () => {
const el = document.createElement('div');
- Object.assign(el.dataset, initData);
+ Object.assign(el.dataset, TEST_DATASET);
root.appendChild(el);
- vm = initIde(el);
+ vm = initIde(el, { extendStore });
};
- expect.addSnapshotSerializer({
- test(value) {
- return value instanceof HTMLElement && !value.$_hit;
- },
- print(element, serialize) {
- element.$_hit = true;
- element.querySelectorAll('[style]').forEach(el => {
- el.$_hit = true;
- if (el.style.display === 'none') {
- el.textContent = '(jest: contents hidden)';
- }
- });
-
- return serialize(element)
- .replace(/^\s*<!---->$/gm, '')
- .replace(/\n\s*\n/gm, '\n');
- },
- });
-
it('runs', () => {
createComponent();
- return vm.$nextTick().then(() => {
- expect(root).toMatchSnapshot();
- });
+ expect(root).toMatchSnapshot();
});
});
diff --git a/spec/frontend_integration/test_helpers/factories/commit.js b/spec/frontend_integration/test_helpers/factories/commit.js
new file mode 100644
index 00000000000..1ee82e74ffe
--- /dev/null
+++ b/spec/frontend_integration/test_helpers/factories/commit.js
@@ -0,0 +1,15 @@
+import { withValues } from '../utils/obj';
+import { getCommit } from '../fixtures';
+import { createCommitId } from './commit_id';
+
+// eslint-disable-next-line import/prefer-default-export
+export const createNewCommit = ({ id = createCommitId(), message }, orig = getCommit()) => {
+ return withValues(orig, {
+ id,
+ short_id: id.substr(0, 8),
+ message,
+ title: message,
+ web_url: orig.web_url.replace(orig.id, id),
+ parent_ids: [orig.id],
+ });
+};
diff --git a/spec/frontend_integration/test_helpers/factories/commit_id.js b/spec/frontend_integration/test_helpers/factories/commit_id.js
new file mode 100644
index 00000000000..9fa278c9dde
--- /dev/null
+++ b/spec/frontend_integration/test_helpers/factories/commit_id.js
@@ -0,0 +1,21 @@
+const COMMIT_ID_LENGTH = 40;
+const DEFAULT_COMMIT_ID = Array(COMMIT_ID_LENGTH)
+ .fill('0')
+ .join('');
+
+export const createCommitId = (index = 0) =>
+ `${index}${DEFAULT_COMMIT_ID}`.substr(0, COMMIT_ID_LENGTH);
+
+export const createCommitIdGenerator = () => {
+ let prevCommitId = 0;
+
+ const next = () => {
+ prevCommitId += 1;
+
+ return createCommitId(prevCommitId);
+ };
+
+ return {
+ next,
+ };
+};
diff --git a/spec/frontend_integration/test_helpers/factories/index.js b/spec/frontend_integration/test_helpers/factories/index.js
new file mode 100644
index 00000000000..0f28830b236
--- /dev/null
+++ b/spec/frontend_integration/test_helpers/factories/index.js
@@ -0,0 +1,2 @@
+export * from './commit';
+export * from './commit_id';
diff --git a/spec/frontend_integration/test_helpers/fixtures.js b/spec/frontend_integration/test_helpers/fixtures.js
new file mode 100644
index 00000000000..5f9c0e8dcba
--- /dev/null
+++ b/spec/frontend_integration/test_helpers/fixtures.js
@@ -0,0 +1,10 @@
+/* eslint-disable global-require */
+import { memoize } from 'lodash';
+
+export const getProject = () => require('test_fixtures/api/projects/get.json');
+export const getBranch = () => require('test_fixtures/api/projects/branches/get.json');
+export const getMergeRequests = () => require('test_fixtures/api/merge_requests/get.json');
+export const getRepositoryFiles = () => require('test_fixtures/projects_json/files.json');
+export const getPipelinesEmptyResponse = () =>
+ require('test_fixtures/projects_json/pipelines_empty.json');
+export const getCommit = memoize(() => getBranch().commit);
diff --git a/spec/frontend_integration/test_helpers/mock_server/graphql.js b/spec/frontend_integration/test_helpers/mock_server/graphql.js
new file mode 100644
index 00000000000..6dcc4798378
--- /dev/null
+++ b/spec/frontend_integration/test_helpers/mock_server/graphql.js
@@ -0,0 +1,21 @@
+import { buildSchema, graphql } from 'graphql';
+import gitlabSchemaStr from '../../../../doc/api/graphql/reference/gitlab_schema.graphql';
+
+const graphqlSchema = buildSchema(gitlabSchemaStr.loc.source.body);
+const graphqlResolvers = {
+ project({ fullPath }, schema) {
+ const result = schema.projects.findBy({ path_with_namespace: fullPath });
+ const userPermission = schema.db.userPermissions[0];
+
+ return {
+ ...result.attrs,
+ userPermissions: {
+ ...userPermission,
+ },
+ };
+ },
+};
+
+// eslint-disable-next-line import/prefer-default-export
+export const graphqlQuery = (query, variables, schema) =>
+ graphql(graphqlSchema, query, graphqlResolvers, schema, variables);
diff --git a/spec/frontend_integration/test_helpers/mock_server/index.js b/spec/frontend_integration/test_helpers/mock_server/index.js
new file mode 100644
index 00000000000..b3979d05ea5
--- /dev/null
+++ b/spec/frontend_integration/test_helpers/mock_server/index.js
@@ -0,0 +1,45 @@
+import { Server, Model, RestSerializer } from 'miragejs';
+import { getProject, getBranch, getMergeRequests, getRepositoryFiles } from 'test_helpers/fixtures';
+import setupRoutes from './routes';
+
+export const createMockServerOptions = () => ({
+ models: {
+ project: Model,
+ branch: Model,
+ mergeRequest: Model,
+ file: Model,
+ userPermission: Model,
+ },
+ serializers: {
+ application: RestSerializer.extend({
+ root: false,
+ }),
+ },
+ seeds(schema) {
+ schema.db.loadData({
+ files: getRepositoryFiles().map(path => ({ path })),
+ projects: [getProject()],
+ branches: [getBranch()],
+ mergeRequests: getMergeRequests(),
+ userPermissions: [
+ {
+ createMergeRequestIn: true,
+ readMergeRequest: true,
+ pushCode: true,
+ },
+ ],
+ });
+ },
+ routes() {
+ this.namespace = '';
+ this.urlPrefix = '/';
+
+ setupRoutes(this);
+ },
+});
+
+export const createMockServer = () => {
+ const server = new Server(createMockServerOptions());
+
+ return server;
+};
diff --git a/spec/frontend_integration/test_helpers/mock_server/routes/404.js b/spec/frontend_integration/test_helpers/mock_server/routes/404.js
new file mode 100644
index 00000000000..9e08016577b
--- /dev/null
+++ b/spec/frontend_integration/test_helpers/mock_server/routes/404.js
@@ -0,0 +1,7 @@
+export default server => {
+ ['get', 'post', 'put', 'delete', 'patch'].forEach(method => {
+ server[method]('*', () => {
+ return new Response(404);
+ });
+ });
+};
diff --git a/spec/frontend_integration/test_helpers/mock_server/routes/ci.js b/spec/frontend_integration/test_helpers/mock_server/routes/ci.js
new file mode 100644
index 00000000000..83951f09c56
--- /dev/null
+++ b/spec/frontend_integration/test_helpers/mock_server/routes/ci.js
@@ -0,0 +1,11 @@
+import { getPipelinesEmptyResponse } from 'test_helpers/fixtures';
+
+export default server => {
+ server.get('*/commit/:id/pipelines', () => {
+ return getPipelinesEmptyResponse();
+ });
+
+ server.get('/api/v4/projects/:id/runners', () => {
+ return [];
+ });
+};
diff --git a/spec/frontend_integration/test_helpers/mock_server/routes/graphql.js b/spec/frontend_integration/test_helpers/mock_server/routes/graphql.js
new file mode 100644
index 00000000000..ebb5415ba97
--- /dev/null
+++ b/spec/frontend_integration/test_helpers/mock_server/routes/graphql.js
@@ -0,0 +1,11 @@
+import { graphqlQuery } from '../graphql';
+
+export default server => {
+ server.post('/api/graphql', (schema, request) => {
+ const batches = JSON.parse(request.requestBody);
+
+ return Promise.all(
+ batches.map(({ query, variables }) => graphqlQuery(query, variables, schema)),
+ );
+ });
+};
diff --git a/spec/frontend_integration/test_helpers/mock_server/routes/index.js b/spec/frontend_integration/test_helpers/mock_server/routes/index.js
new file mode 100644
index 00000000000..eea196b5158
--- /dev/null
+++ b/spec/frontend_integration/test_helpers/mock_server/routes/index.js
@@ -0,0 +1,12 @@
+/* eslint-disable global-require */
+export default server => {
+ [
+ require('./graphql'),
+ require('./projects'),
+ require('./repository'),
+ require('./ci'),
+ require('./404'),
+ ].forEach(({ default: setup }) => {
+ setup(server);
+ });
+};
diff --git a/spec/frontend_integration/test_helpers/mock_server/routes/projects.js b/spec/frontend_integration/test_helpers/mock_server/routes/projects.js
new file mode 100644
index 00000000000..f4d8ce4b23d
--- /dev/null
+++ b/spec/frontend_integration/test_helpers/mock_server/routes/projects.js
@@ -0,0 +1,23 @@
+import { withKeys } from 'test_helpers/utils/obj';
+
+export default server => {
+ server.get('/api/v4/projects/:id', (schema, request) => {
+ const { id } = request.params;
+
+ const proj =
+ schema.projects.findBy({ id }) ?? schema.projects.findBy({ path_with_namespace: id });
+
+ return proj.attrs;
+ });
+
+ server.get('/api/v4/projects/:id/merge_requests', (schema, request) => {
+ const result = schema.mergeRequests.where(
+ withKeys(request.queryParams, {
+ source_project_id: 'project_id',
+ source_branch: 'source_branch',
+ }),
+ );
+
+ return result.models;
+ });
+};
diff --git a/spec/frontend_integration/test_helpers/mock_server/routes/repository.js b/spec/frontend_integration/test_helpers/mock_server/routes/repository.js
new file mode 100644
index 00000000000..c5e91c9e87e
--- /dev/null
+++ b/spec/frontend_integration/test_helpers/mock_server/routes/repository.js
@@ -0,0 +1,38 @@
+import { createNewCommit, createCommitIdGenerator } from 'test_helpers/factories';
+
+export default server => {
+ const commitIdGenerator = createCommitIdGenerator();
+
+ server.get('/api/v4/projects/:id/repository/branches', schema => {
+ return schema.db.branches;
+ });
+
+ server.get('/api/v4/projects/:id/repository/branches/:name', (schema, request) => {
+ const { name } = request.params;
+
+ const branch = schema.branches.findBy({ name });
+
+ return branch.attrs;
+ });
+
+ server.get('*/-/files/:id', schema => {
+ return schema.db.files.map(({ path }) => path);
+ });
+
+ server.post('/api/v4/projects/:id/repository/commits', (schema, request) => {
+ const { branch: branchName, commit_message: message, actions } = JSON.parse(
+ request.requestBody,
+ );
+
+ const branch = schema.branches.findBy({ name: branchName });
+
+ const commit = {
+ ...createNewCommit({ id: commitIdGenerator.next(), message }, branch.attrs.commit),
+ __actions: actions,
+ };
+
+ branch.update({ commit });
+
+ return commit;
+ });
+};
diff --git a/spec/frontend_integration/test_helpers/mock_server/use.js b/spec/frontend_integration/test_helpers/mock_server/use.js
new file mode 100644
index 00000000000..84597d57584
--- /dev/null
+++ b/spec/frontend_integration/test_helpers/mock_server/use.js
@@ -0,0 +1,5 @@
+import { createMockServer } from './index';
+
+if (process.env.NODE_ENV === 'development') {
+ window.mockServer = createMockServer();
+}
diff --git a/spec/frontend_integration/test_helpers/setup/index.js b/spec/frontend_integration/test_helpers/setup/index.js
new file mode 100644
index 00000000000..ba1d256e16e
--- /dev/null
+++ b/spec/frontend_integration/test_helpers/setup/index.js
@@ -0,0 +1,5 @@
+import '../../../frontend/test_setup';
+import './setup_globals';
+import './setup_axios';
+import './setup_serializers';
+import './setup_mock_server';
diff --git a/spec/frontend_integration/test_helpers/setup/setup_axios.js b/spec/frontend_integration/test_helpers/setup/setup_axios.js
new file mode 100644
index 00000000000..efdaf8016f2
--- /dev/null
+++ b/spec/frontend_integration/test_helpers/setup/setup_axios.js
@@ -0,0 +1,5 @@
+import adapter from 'axios/lib/adapters/xhr';
+import axios from '~/lib/utils/axios_utils';
+
+// We're removing our default axios adapter because this is handled by our mock server now
+axios.defaults.adapter = adapter;
diff --git a/spec/frontend_integration/test_helpers/setup/setup_globals.js b/spec/frontend_integration/test_helpers/setup/setup_globals.js
new file mode 100644
index 00000000000..2b0e8f76c3c
--- /dev/null
+++ b/spec/frontend_integration/test_helpers/setup/setup_globals.js
@@ -0,0 +1,15 @@
+import { setTestTimeout } from 'helpers/timeout';
+
+beforeEach(() => {
+ window.gon = {
+ api_version: 'v4',
+ relative_url_root: '',
+ };
+
+ setTestTimeout(5000);
+ jest.useRealTimers();
+});
+
+afterEach(() => {
+ jest.useFakeTimers();
+});
diff --git a/spec/frontend_integration/test_helpers/setup/setup_mock_server.js b/spec/frontend_integration/test_helpers/setup/setup_mock_server.js
new file mode 100644
index 00000000000..343aeebf88e
--- /dev/null
+++ b/spec/frontend_integration/test_helpers/setup/setup_mock_server.js
@@ -0,0 +1,13 @@
+import { createMockServer } from '../mock_server';
+
+beforeEach(() => {
+ const server = createMockServer();
+ server.logging = false;
+
+ global.mockServer = server;
+});
+
+afterEach(() => {
+ global.mockServer.shutdown();
+ global.mockServer = null;
+});
diff --git a/spec/frontend_integration/test_helpers/setup/setup_serializers.js b/spec/frontend_integration/test_helpers/setup/setup_serializers.js
new file mode 100644
index 00000000000..6c1de853129
--- /dev/null
+++ b/spec/frontend_integration/test_helpers/setup/setup_serializers.js
@@ -0,0 +1,3 @@
+import defaultSerializer from '../snapshot_serializer';
+
+expect.addSnapshotSerializer(defaultSerializer);
diff --git a/spec/frontend_integration/test_helpers/snapshot_serializer.js b/spec/frontend_integration/test_helpers/snapshot_serializer.js
new file mode 100644
index 00000000000..8c4f95a9156
--- /dev/null
+++ b/spec/frontend_integration/test_helpers/snapshot_serializer.js
@@ -0,0 +1,18 @@
+export default {
+ test(value) {
+ return value instanceof HTMLElement && !value.$_hit;
+ },
+ print(element, serialize) {
+ element.$_hit = true;
+ element.querySelectorAll('[style]').forEach(el => {
+ el.$_hit = true;
+ if (el.style.display === 'none') {
+ el.textContent = '(jest: contents hidden)';
+ }
+ });
+
+ return serialize(element)
+ .replace(/^\s*<!---->$/gm, '')
+ .replace(/\n\s*\n/gm, '\n');
+ },
+};
diff --git a/spec/frontend_integration/test_helpers/utils/obj.js b/spec/frontend_integration/test_helpers/utils/obj.js
new file mode 100644
index 00000000000..6c301798489
--- /dev/null
+++ b/spec/frontend_integration/test_helpers/utils/obj.js
@@ -0,0 +1,36 @@
+import { has, mapKeys, pick } from 'lodash';
+
+/**
+ * This method is used to type-safely set values on the given object
+ *
+ * @template T
+ * @returns {T} A shallow copy of `obj`, with the values from `values`
+ * @throws {Error} If `values` contains a key that isn't already on `obj`
+ * @param {T} source
+ * @param {Object} values
+ */
+export const withValues = (source, values) =>
+ Object.entries(values).reduce(
+ (acc, [key, value]) => {
+ if (!has(acc, key)) {
+ throw new Error(
+ `[mock_server] Cannot write property that does not exist on object '${key}'`,
+ );
+ }
+
+ return {
+ ...acc,
+ [key]: value,
+ };
+ },
+ { ...source },
+ );
+
+/**
+ * This method returns a subset of the given object and maps the key names based on the
+ * given `keys`.
+ *
+ * @param {Object} obj The source object.
+ * @param {Object} map The object which contains the keys to use and mapped key names.
+ */
+export const withKeys = (obj, map) => mapKeys(pick(obj, Object.keys(map)), (val, key) => map[key]);
diff --git a/spec/frontend_integration/test_helpers/utils/obj_spec.js b/spec/frontend_integration/test_helpers/utils/obj_spec.js
new file mode 100644
index 00000000000..0ad7b4a1a4c
--- /dev/null
+++ b/spec/frontend_integration/test_helpers/utils/obj_spec.js
@@ -0,0 +1,23 @@
+import { withKeys, withValues } from './obj';
+
+describe('frontend_integration/test_helpers/utils/obj', () => {
+ describe('withKeys', () => {
+ it('picks and maps keys', () => {
+ expect(withKeys({ a: '123', b: 456, c: 'd' }, { b: 'lorem', c: 'ipsum', z: 'zed ' })).toEqual(
+ { lorem: 456, ipsum: 'd' },
+ );
+ });
+ });
+
+ describe('withValues', () => {
+ it('sets values', () => {
+ expect(withValues({ a: '123', b: 456 }, { b: 789 })).toEqual({ a: '123', b: 789 });
+ });
+
+ it('throws if values has non-existent key', () => {
+ expect(() => withValues({ a: '123', b: 456 }, { b: 789, bogus: 'throws' })).toThrow(
+ `[mock_server] Cannot write property that does not exist on object 'bogus'`,
+ );
+ });
+ });
+});
diff --git a/spec/frontend_integration/test_helpers/utils/overclock_timers.js b/spec/frontend_integration/test_helpers/utils/overclock_timers.js
new file mode 100644
index 00000000000..046c7f8e527
--- /dev/null
+++ b/spec/frontend_integration/test_helpers/utils/overclock_timers.js
@@ -0,0 +1,65 @@
+/**
+ * This function replaces the existing `setTimeout` and `setInterval` with wrappers that
+ * discount the `ms` passed in by `boost`.
+ *
+ * For example, if a module has:
+ *
+ * ```
+ * setTimeout(cb, 100);
+ * ```
+ *
+ * But a test has:
+ *
+ * ```
+ * useOverclockTimers(25);
+ * ```
+ *
+ * Then the module's call to `setTimeout` effectively becomes:
+ *
+ * ```
+ * setTimeout(cb, 4);
+ * ```
+ *
+ * It's important to note that the timing for `setTimeout` and order of execution is non-deterministic
+ * and discounting the `ms` passed could make this very obvious and expose some underlying issues
+ * with flaky failures.
+ *
+ * WARNING: If flaky spec failures show up in a spec that is using this helper, please consider either:
+ *
+ * - Refactoring the production code so that it's reactive to state changes, not dependent on timers.
+ * - Removing the call to this helper from the spec.
+ *
+ * @param {Number} boost
+ */
+// eslint-disable-next-line import/prefer-default-export
+export const useOverclockTimers = (boost = 50) => {
+ if (boost <= 0) {
+ throw new Error(`[overclock_timers] boost (${boost}) cannot be <= 0`);
+ }
+
+ let origSetTimeout;
+ let origSetInterval;
+ const newSetTimeout = (fn, msParam = 0) => {
+ const ms = msParam > 0 ? Math.floor(msParam / boost) : msParam;
+
+ return origSetTimeout(fn, ms);
+ };
+ const newSetInterval = (fn, msParam = 0) => {
+ const ms = msParam > 0 ? Math.floor(msParam / boost) : msParam;
+
+ return origSetInterval(fn, ms);
+ };
+
+ beforeEach(() => {
+ origSetTimeout = global.setTimeout;
+ origSetInterval = global.setInterval;
+
+ global.setTimeout = newSetTimeout;
+ global.setInterval = newSetInterval;
+ });
+
+ afterEach(() => {
+ global.setTimeout = origSetTimeout;
+ global.setInterval = origSetInterval;
+ });
+};
diff --git a/spec/frontend_integration/test_setup.js b/spec/frontend_integration/test_setup.js
new file mode 100644
index 00000000000..8db22c56245
--- /dev/null
+++ b/spec/frontend_integration/test_setup.js
@@ -0,0 +1 @@
+import './test_helpers/setup';