diff --git a/jest.config.base.js b/jest.config.base.js index 52e29339e5598578684adcb963a0b377084be32f..ef7802ff724ad22a853f3fc519948d07765ed23d 100644 --- a/jest.config.base.js +++ b/jest.config.base.js @@ -86,6 +86,8 @@ module.exports = (path, options = {}) => { collectCoverageFrom, coverageDirectory: coverageDirectory(), coverageReporters: ['json', 'lcov', 'text-summary', 'clover'], + // We need ignore _worker code coverage since we are manually transforming it + coveragePathIgnorePatterns: ['<rootDir>/node_modules/', '_worker\\.js$'], cacheDirectory: '<rootDir>/tmp/cache/jest', modulePathIgnorePatterns: ['<rootDir>/.yarn-cache/'], reporters, @@ -93,6 +95,7 @@ module.exports = (path, options = {}) => { restoreMocks: true, transform: { '^.+\\.(gql|graphql)$': 'jest-transform-graphql', + '^.+_worker\\.js$': './spec/frontend/__helpers__/web_worker_transformer.js', '^.+\\.js$': 'babel-jest', '^.+\\.vue$': 'vue-jest', '^.+\\.(md|zip|png)$': 'jest-raw-loader', diff --git a/spec/frontend/__helpers__/web_worker_fake.js b/spec/frontend/__helpers__/web_worker_fake.js new file mode 100644 index 0000000000000000000000000000000000000000..041a9bd8540fc27d6277f23a8fd5e579ae2fd3a0 --- /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 2b4a391e1d2c35dabf2e4798bd8f23b1c2989c61..0000000000000000000000000000000000000000 --- 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 0000000000000000000000000000000000000000..5b2f7d7794774f5108fc0794c345dcf29eff906c --- /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)}); + } + };`; + }, +}; diff --git a/spec/frontend/diffs/store/actions_spec.js b/spec/frontend/diffs/store/actions_spec.js index 0d3eaa15f1e508822058878816f2c9ac0923bb4b..822d73506c10c9785d0db2fbe1e033f8e05a5155 100644 --- a/spec/frontend/diffs/store/actions_spec.js +++ b/spec/frontend/diffs/store/actions_spec.js @@ -258,6 +258,8 @@ describe('DiffsStoreActions', () => { { type: types.SET_LOADING, payload: false }, { type: types.SET_MERGE_REQUEST_DIFFS, payload: diffMetadata.merge_request_diffs }, { type: types.SET_DIFF_METADATA, payload: noFilesData }, + // Workers are synchronous in Jest environment (see https://gitlab.com/gitlab-org/gitlab/-/merge_requests/58805) + { type: types.SET_TREE_DATA, payload: utils.generateTreeList(diffMetadata.diff_files) }, ], [], () => { diff --git a/spec/frontend/mocks/ce/diffs/workers/tree_worker.js b/spec/frontend/mocks/ce/diffs/workers/tree_worker.js deleted file mode 100644 index 5532a22f8e6fe87b7f1da6fa308b0ac42e74ac07..0000000000000000000000000000000000000000 --- a/spec/frontend/mocks/ce/diffs/workers/tree_worker.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from 'helpers/web_worker_mock'; diff --git a/spec/frontend/mocks/ce/ide/lib/diff/diff_worker.js b/spec/frontend/mocks/ce/ide/lib/diff/diff_worker.js deleted file mode 100644 index 5532a22f8e6fe87b7f1da6fa308b0ac42e74ac07..0000000000000000000000000000000000000000 --- a/spec/frontend/mocks/ce/ide/lib/diff/diff_worker.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from 'helpers/web_worker_mock';