refactor(*): break up into individual modules (#474)
this should make it much easier to comprehend and write tests for karma-webpack. there is one change in how the KarmaWebpackController is managed, we now instantiate this in the preprocessor phase and propagate the value within the karma config object as a private variable. This allows for breaking the framework and preprocessor into separates modules and has the added benefit of being able to run multiple times in a given session without sharing mutable state. This allows integrations tests to be run in parallel as well as multiple times which was previously not possible. Fixes N/A
This commit is contained in:
parent
8ad09d1551
commit
53002008f9
20 changed files with 265 additions and 201 deletions
|
@ -1,9 +1,13 @@
|
|||
module.exports = {
|
||||
root: true,
|
||||
globals: {
|
||||
"jasmine": true,
|
||||
},
|
||||
plugins: ['prettier'],
|
||||
extends: ['@webpack-contrib/eslint-config-webpack'],
|
||||
rules: {
|
||||
"consistent-return": "off",
|
||||
"camelcase": "off",
|
||||
"no-console": "off",
|
||||
"no-param-reassign": "off",
|
||||
"no-underscore-dangle": "off",
|
||||
|
|
|
@ -1 +1 @@
|
|||
module.exports = require('./karma-webpack');
|
||||
module.exports = require('./karma/plugin');
|
||||
|
|
|
@ -1,75 +1,17 @@
|
|||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
const os = require('os');
|
||||
|
||||
const webpack = require('webpack');
|
||||
const merge = require('webpack-merge');
|
||||
|
||||
class KarmaSyncPlugin {
|
||||
constructor(options) {
|
||||
this.karmaEmitter = options.karmaEmitter;
|
||||
this.controller = options.controller;
|
||||
const KW_WebpackPlugin = require('../webpack/plugin');
|
||||
const DefaultWebpackOptionsFactory = require('../webpack/defaults');
|
||||
|
||||
class KW_Controller {
|
||||
constructor() {
|
||||
this.isActive = false;
|
||||
this.bundlesContent = {};
|
||||
this.hasBeenBuiltAtLeastOnce = false;
|
||||
this.webpackOptions = DefaultWebpackOptionsFactory.create();
|
||||
}
|
||||
|
||||
apply(compiler) {
|
||||
this.compiler = compiler;
|
||||
|
||||
// webpack bundles are finished
|
||||
compiler.hooks.done.tap('KarmaSyncPlugin', async (stats) => {
|
||||
// read generated file content and store for karma preprocessor
|
||||
this.controller.bundlesContent = {};
|
||||
stats.toJson().assets.forEach((webpackFileObj) => {
|
||||
const filePath = `${compiler.options.output.path}/${webpackFileObj.name}`;
|
||||
this.controller.bundlesContent[webpackFileObj.name] = fs.readFileSync(
|
||||
filePath,
|
||||
'utf-8'
|
||||
);
|
||||
});
|
||||
|
||||
// karma refresh
|
||||
this.karmaEmitter.refreshFiles();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const defaultWebpackOptions = {
|
||||
mode: 'development',
|
||||
output: {
|
||||
filename: '[name].js',
|
||||
// eslint-disable-next-line prettier/prettier
|
||||
path: path.join(os.tmpdir(), '_karma_webpack_') + Math.floor(Math.random() * 1000000),
|
||||
},
|
||||
stats: {
|
||||
modules: false,
|
||||
colors: true,
|
||||
},
|
||||
watch: false,
|
||||
optimization: {
|
||||
runtimeChunk: 'single',
|
||||
splitChunks: {
|
||||
chunks: 'all',
|
||||
minSize: 0,
|
||||
cacheGroups: {
|
||||
commons: {
|
||||
name: 'commons',
|
||||
chunks: 'all',
|
||||
minChunks: 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: [],
|
||||
// Something like this will be auto added by this.configure()
|
||||
// entry: {
|
||||
// 'foo-one.test.js': 'path/to/test/foo-one.test.js',
|
||||
// 'foo-two.test.js': 'path/to/test/foo-two.test.js',
|
||||
// },
|
||||
// plugins: [
|
||||
// new KarmaSyncPlugin()
|
||||
// ],
|
||||
};
|
||||
|
||||
class KarmaWebpackController {
|
||||
set webpackOptions(options) {
|
||||
this.__webpackOptions = options;
|
||||
}
|
||||
|
@ -78,11 +20,15 @@ class KarmaWebpackController {
|
|||
return this.__webpackOptions;
|
||||
}
|
||||
|
||||
updateWebpackOptions(newOptions) {
|
||||
this.webpackOptions = merge(this.webpackOptions, newOptions);
|
||||
}
|
||||
|
||||
set karmaEmitter(emitter) {
|
||||
this.__karmaEmitter = emitter;
|
||||
|
||||
this.__webpackOptions.plugins.push(
|
||||
new KarmaSyncPlugin({
|
||||
new KW_WebpackPlugin({
|
||||
karmaEmitter: emitter,
|
||||
controller: this,
|
||||
})
|
||||
|
@ -97,13 +43,6 @@ class KarmaWebpackController {
|
|||
return this.webpackOptions.output.path;
|
||||
}
|
||||
|
||||
constructor() {
|
||||
this.isActive = false;
|
||||
this.bundlesContent = {};
|
||||
this.hasBeenBuiltAtLeastOnce = false;
|
||||
this.webpackOptions = defaultWebpackOptions;
|
||||
}
|
||||
|
||||
setupExitHandler(compiler) {
|
||||
this.karmaEmitter.once('exit', (done) => {
|
||||
compiler.close(() => {
|
||||
|
@ -113,10 +52,6 @@ class KarmaWebpackController {
|
|||
});
|
||||
}
|
||||
|
||||
updateWebpackOptions(newOptions) {
|
||||
this.webpackOptions = merge(this.webpackOptions, newOptions);
|
||||
}
|
||||
|
||||
async bundle() {
|
||||
if (this.isActive === false && this.hasBeenBuiltAtLeastOnce === false) {
|
||||
console.log('Webpack bundling...');
|
||||
|
@ -169,8 +104,4 @@ class KarmaWebpackController {
|
|||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
KarmaSyncPlugin,
|
||||
KarmaWebpackController,
|
||||
defaultWebpackOptions,
|
||||
};
|
||||
module.exports = KW_Controller;
|
36
lib/karma-webpack/framework.js
Normal file
36
lib/karma-webpack/framework.js
Normal file
|
@ -0,0 +1,36 @@
|
|||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
function KW_Framework(config) {
|
||||
// This controller is instantiated and set during the preprocessor phase.
|
||||
const controller = config.__karmaWebpackController;
|
||||
const commonsPath = path.join(controller.outputPath, 'commons.js');
|
||||
const runtimePath = path.join(controller.outputPath, 'runtime.js');
|
||||
|
||||
// make sure tmp folder exists
|
||||
if (!fs.existsSync(controller.outputPath)) {
|
||||
fs.mkdirSync(controller.outputPath);
|
||||
}
|
||||
|
||||
// create dummy files for commons.js and runtime.js so they get included by karma
|
||||
fs.closeSync(fs.openSync(commonsPath, 'w'));
|
||||
fs.closeSync(fs.openSync(runtimePath, 'w'));
|
||||
|
||||
// register for karma
|
||||
config.files.unshift({
|
||||
pattern: commonsPath,
|
||||
included: true,
|
||||
served: true,
|
||||
watched: false,
|
||||
});
|
||||
config.files.unshift({
|
||||
pattern: runtimePath,
|
||||
included: true,
|
||||
served: true,
|
||||
watched: false,
|
||||
});
|
||||
}
|
||||
|
||||
KW_Framework.$inject = ['config'];
|
||||
|
||||
module.exports = KW_Framework;
|
|
@ -1,45 +1,12 @@
|
|||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
|
||||
const glob = require('glob');
|
||||
const minimatch = require('minimatch');
|
||||
|
||||
const { ensureWebpackFrameworkSet } = require('./karma/karmaConfigValidator');
|
||||
const { ensureWebpackFrameworkSet } = require('../karma/validation');
|
||||
const { hash } = require('../utils/hash');
|
||||
|
||||
const { hash } = require('./utils/hash');
|
||||
|
||||
const { KarmaWebpackController } = require('./KarmaWebpackController');
|
||||
|
||||
const controller = new KarmaWebpackController();
|
||||
|
||||
function registerExtraWebpackFiles(config, _controller) {
|
||||
const localController = _controller || controller;
|
||||
const commonsPath = path.join(localController.outputPath, 'commons.js');
|
||||
const runtimePath = path.join(localController.outputPath, 'runtime.js');
|
||||
|
||||
// make sure tmp folder exists
|
||||
if (!fs.existsSync(localController.outputPath)) {
|
||||
fs.mkdirSync(localController.outputPath);
|
||||
}
|
||||
|
||||
// create dummy files for commons.js and runtime.js so they get included by karma
|
||||
fs.closeSync(fs.openSync(commonsPath, 'w'));
|
||||
fs.closeSync(fs.openSync(runtimePath, 'w'));
|
||||
|
||||
// register for karma
|
||||
config.files.unshift({
|
||||
pattern: commonsPath,
|
||||
included: true,
|
||||
served: true,
|
||||
watched: false,
|
||||
});
|
||||
config.files.unshift({
|
||||
pattern: runtimePath,
|
||||
included: true,
|
||||
served: true,
|
||||
watched: false,
|
||||
});
|
||||
}
|
||||
const KW_Controller = require('./controller');
|
||||
|
||||
function getPathKey(filePath, withExtension = false) {
|
||||
const pathParts = path.parse(filePath);
|
||||
|
@ -81,7 +48,9 @@ function configToWebpackEntries(config) {
|
|||
return webpackEntries;
|
||||
}
|
||||
|
||||
function preprocessorFactory(config, emitter) {
|
||||
function KW_Preprocessor(config, emitter) {
|
||||
const controller = new KW_Controller();
|
||||
config.__karmaWebpackController = controller;
|
||||
ensureWebpackFrameworkSet(config);
|
||||
|
||||
// one time setup
|
||||
|
@ -118,12 +87,6 @@ function preprocessorFactory(config, emitter) {
|
|||
};
|
||||
}
|
||||
|
||||
registerExtraWebpackFiles.$inject = ['config'];
|
||||
preprocessorFactory.$inject = ['config', 'emitter'];
|
||||
KW_Preprocessor.$inject = ['config', 'emitter'];
|
||||
|
||||
module.exports = {
|
||||
'preprocessor:webpack': ['factory', preprocessorFactory],
|
||||
'framework:webpack': ['factory', registerExtraWebpackFiles],
|
||||
registerExtraWebpackFiles,
|
||||
configToWebpackEntries,
|
||||
};
|
||||
module.exports = KW_Preprocessor;
|
9
lib/karma/plugin.js
Normal file
9
lib/karma/plugin.js
Normal file
|
@ -0,0 +1,9 @@
|
|||
const KW_Framework = require('../karma-webpack/framework');
|
||||
const KW_Preprocessor = require('../karma-webpack/preprocessor');
|
||||
|
||||
const KW_KarmaPlugin = {
|
||||
'preprocessor:webpack': ['factory', KW_Preprocessor],
|
||||
'framework:webpack': ['factory', KW_Framework],
|
||||
};
|
||||
|
||||
module.exports = KW_KarmaPlugin;
|
35
lib/webpack/defaults.js
Normal file
35
lib/webpack/defaults.js
Normal file
|
@ -0,0 +1,35 @@
|
|||
const path = require('path');
|
||||
const os = require('os');
|
||||
|
||||
function create() {
|
||||
return {
|
||||
mode: 'development',
|
||||
output: {
|
||||
filename: '[name].js',
|
||||
// eslint-disable-next-line prettier/prettier
|
||||
path: path.join(os.tmpdir(), '_karma_webpack_') + Math.floor(Math.random() * 1000000),
|
||||
},
|
||||
stats: {
|
||||
modules: false,
|
||||
colors: true,
|
||||
},
|
||||
watch: false,
|
||||
optimization: {
|
||||
runtimeChunk: 'single',
|
||||
splitChunks: {
|
||||
chunks: 'all',
|
||||
minSize: 0,
|
||||
cacheGroups: {
|
||||
commons: {
|
||||
name: 'commons',
|
||||
chunks: 'all',
|
||||
minChunks: 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: [],
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = { create };
|
32
lib/webpack/plugin.js
Normal file
32
lib/webpack/plugin.js
Normal file
|
@ -0,0 +1,32 @@
|
|||
const fs = require('fs');
|
||||
|
||||
class KW_WebpackPlugin {
|
||||
constructor(options) {
|
||||
this.karmaEmitter = options.karmaEmitter;
|
||||
this.controller = options.controller;
|
||||
}
|
||||
|
||||
apply(compiler) {
|
||||
this.compiler = compiler;
|
||||
|
||||
// webpack bundles are finished
|
||||
compiler.hooks.done.tap('KW_WebpackPlugin', async (stats) => {
|
||||
// read generated file content and store for karma preprocessor
|
||||
this.controller.bundlesContent = {};
|
||||
stats.toJson().assets.forEach((webpackFileObj) => {
|
||||
const filePath = `${compiler.options.output.path}/${
|
||||
webpackFileObj.name
|
||||
}`;
|
||||
this.controller.bundlesContent[webpackFileObj.name] = fs.readFileSync(
|
||||
filePath,
|
||||
'utf-8'
|
||||
);
|
||||
});
|
||||
|
||||
// karma refresh
|
||||
this.karmaEmitter.refreshFiles();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = KW_WebpackPlugin;
|
|
@ -4,7 +4,7 @@ import karmaChromeLauncher from 'karma-chrome-launcher';
|
|||
import karmaMocha from 'karma-mocha';
|
||||
import karmaChai from 'karma-chai';
|
||||
|
||||
import ScenarioUtils from '../../utils/ScenarioUtils';
|
||||
import Scenario from '../../utils/scenario';
|
||||
|
||||
process.env.CHROME_BIN = require('puppeteer').executablePath();
|
||||
|
||||
|
@ -36,7 +36,7 @@ describe('A basic karma-webpack setup', () => {
|
|||
};
|
||||
|
||||
beforeAll((done) => {
|
||||
ScenarioUtils.run(config)
|
||||
Scenario.run(config)
|
||||
.then((res) => {
|
||||
scenario = res;
|
||||
})
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
const karma = require('karma');
|
||||
|
||||
const ScenarioUtils = { run };
|
||||
const Scenario = { run };
|
||||
|
||||
/**
|
||||
* This allows you to run karma with a given configuration and be returned.
|
||||
|
@ -20,4 +20,4 @@ function run(config) {
|
|||
});
|
||||
}
|
||||
|
||||
export default ScenarioUtils;
|
||||
export default Scenario;
|
|
@ -1,26 +0,0 @@
|
|||
const {
|
||||
KarmaWebpackController,
|
||||
defaultWebpackOptions,
|
||||
} = require('../../lib/KarmaWebpackController');
|
||||
|
||||
describe('KarmaWebpackController', () => {
|
||||
it('applies the default webpackOptions', () => {
|
||||
const controller = new KarmaWebpackController();
|
||||
expect(controller.webpackOptions).toEqual(defaultWebpackOptions);
|
||||
});
|
||||
|
||||
it('can provide custom nested webpackOptions', () => {
|
||||
const controller = new KarmaWebpackController();
|
||||
controller.updateWebpackOptions({
|
||||
output: {
|
||||
path: 'foo',
|
||||
publicPath: 'bar',
|
||||
},
|
||||
});
|
||||
expect(controller.webpackOptions.output.path).toBe('foo');
|
||||
expect(controller.webpackOptions.output.publicPath).toBe('bar');
|
||||
expect(controller.webpackOptions.output.filename).toBe(
|
||||
defaultWebpackOptions.output.filename
|
||||
);
|
||||
});
|
||||
});
|
|
@ -1,38 +0,0 @@
|
|||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const { registerExtraWebpackFiles } = require('../../lib/karma-webpack');
|
||||
|
||||
jest.mock('fs');
|
||||
|
||||
describe('karma-webpack', () => {
|
||||
describe('registerExtraWebpackFiles()', () => {
|
||||
test('Defaults', () => {
|
||||
const controller = { outputPath: 'foo/' };
|
||||
const config = { files: [] };
|
||||
fs.closeSync = jest.fn();
|
||||
fs.openSync = jest.fn();
|
||||
|
||||
registerExtraWebpackFiles(config, controller);
|
||||
|
||||
expect(fs.openSync).toBeCalledWith(path.join('foo', 'commons.js'), 'w');
|
||||
expect(fs.openSync).toBeCalledWith(path.join('foo', 'runtime.js'), 'w');
|
||||
|
||||
expect(config.files.length).toBe(2);
|
||||
expect(config.files).toEqual([
|
||||
{
|
||||
pattern: path.join('foo', 'runtime.js'),
|
||||
included: true,
|
||||
served: true,
|
||||
watched: false,
|
||||
},
|
||||
{
|
||||
pattern: path.join('foo', 'commons.js'),
|
||||
included: true,
|
||||
served: true,
|
||||
watched: false,
|
||||
},
|
||||
]);
|
||||
});
|
||||
});
|
||||
});
|
52
test/unit/karma-webpack/controller.test.js
Normal file
52
test/unit/karma-webpack/controller.test.js
Normal file
|
@ -0,0 +1,52 @@
|
|||
const KW_Controller = require('../../../lib/karma-webpack/controller');
|
||||
const DefaultWebpackOptionsFactory = require('../../../lib/webpack/defaults');
|
||||
|
||||
const defaultWebpackOptions = DefaultWebpackOptionsFactory.create();
|
||||
|
||||
describe('KW_Controller', () => {
|
||||
const EXPECTED_DEFAULT_PATH_PREFIX = '/tmp/_karma_webpack_';
|
||||
|
||||
let controller;
|
||||
|
||||
beforeEach(() => (controller = new KW_Controller()));
|
||||
|
||||
it('initializes with a webpackOptions object', () => {
|
||||
expect(controller.webpackOptions).toBeDefined();
|
||||
expect(controller.webpackOptions).toEqual(jasmine.any(Object));
|
||||
});
|
||||
|
||||
it('correctly sets the default output path prefix', () => {
|
||||
expect(
|
||||
controller.webpackOptions.output.path.startsWith(
|
||||
EXPECTED_DEFAULT_PATH_PREFIX
|
||||
)
|
||||
).toBeTruthy();
|
||||
});
|
||||
|
||||
it('correctly postfixes a random number to the end of the webpack options output path for parallel runs', () => {
|
||||
const postfix = controller.webpackOptions.output.path.split(
|
||||
EXPECTED_DEFAULT_PATH_PREFIX
|
||||
)[1];
|
||||
expect(isNaN(postfix)).toBe(false);
|
||||
});
|
||||
|
||||
it('should otherwise be equal to a newly instantiated default webpack options object', () => {
|
||||
controller.webpackOptions.output.path = EXPECTED_DEFAULT_PATH_PREFIX;
|
||||
defaultWebpackOptions.output.path = EXPECTED_DEFAULT_PATH_PREFIX;
|
||||
expect(controller.webpackOptions).toEqual(defaultWebpackOptions);
|
||||
});
|
||||
|
||||
it('can provide custom nested webpackOptions', () => {
|
||||
controller.updateWebpackOptions({
|
||||
output: {
|
||||
path: 'foo',
|
||||
publicPath: 'bar',
|
||||
},
|
||||
});
|
||||
expect(controller.webpackOptions.output.path).toBe('foo');
|
||||
expect(controller.webpackOptions.output.publicPath).toBe('bar');
|
||||
expect(controller.webpackOptions.output.filename).toBe(
|
||||
defaultWebpackOptions.output.filename
|
||||
);
|
||||
});
|
||||
});
|
36
test/unit/karma-webpack/framework.test.js
Normal file
36
test/unit/karma-webpack/framework.test.js
Normal file
|
@ -0,0 +1,36 @@
|
|||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const KW_Framework = require('../../../lib/karma-webpack/framework');
|
||||
|
||||
jest.mock('fs');
|
||||
|
||||
describe('KW_Framework', () => {
|
||||
test('Defaults', () => {
|
||||
const controller = { outputPath: 'foo/' };
|
||||
const config = { files: [], __karmaWebpackController: controller };
|
||||
fs.closeSync = jest.fn();
|
||||
fs.openSync = jest.fn();
|
||||
|
||||
KW_Framework(config);
|
||||
|
||||
expect(fs.openSync).toBeCalledWith(path.join('foo', 'commons.js'), 'w');
|
||||
expect(fs.openSync).toBeCalledWith(path.join('foo', 'runtime.js'), 'w');
|
||||
|
||||
expect(config.files.length).toBe(2);
|
||||
expect(config.files).toEqual([
|
||||
{
|
||||
pattern: path.join('foo', 'runtime.js'),
|
||||
included: true,
|
||||
served: true,
|
||||
watched: false,
|
||||
},
|
||||
{
|
||||
pattern: path.join('foo', 'commons.js'),
|
||||
included: true,
|
||||
served: true,
|
||||
watched: false,
|
||||
},
|
||||
]);
|
||||
});
|
||||
});
|
8
test/unit/karma-webpack/preprocessor.test.js
Normal file
8
test/unit/karma-webpack/preprocessor.test.js
Normal file
|
@ -0,0 +1,8 @@
|
|||
const KW_Preprocessor = require('../../../lib/karma-webpack/preprocessor');
|
||||
|
||||
describe('KW_Preprocessor', () => {
|
||||
it('should be defined', () => {
|
||||
expect(KW_Preprocessor).toBeDefined();
|
||||
});
|
||||
// todo(mikol): write a KW_Preprocessor test suite before v5 official release.
|
||||
});
|
8
test/unit/karma/plugin.test.js
Normal file
8
test/unit/karma/plugin.test.js
Normal file
|
@ -0,0 +1,8 @@
|
|||
const KW_KarmaPlugin = require('../../../lib/karma/plugin');
|
||||
|
||||
describe('KW_KarmaPlugin', () => {
|
||||
it('should be defined', () => {
|
||||
expect(KW_KarmaPlugin).toBeDefined();
|
||||
});
|
||||
// todo(mikol): write test suite for KarmaWebpackPlugin prior to v5 official release.
|
||||
});
|
|
@ -1,6 +1,4 @@
|
|||
const {
|
||||
ensureWebpackFrameworkSet,
|
||||
} = require('../../../lib/karma/karmaConfigValidator');
|
||||
const { ensureWebpackFrameworkSet } = require('../../../lib/karma/validation');
|
||||
|
||||
describe('karmaConfigValidation', () => {
|
||||
describe('ensureWebpackFrameworkExists', () => {
|
8
test/unit/webpack/defaults.test.js
Normal file
8
test/unit/webpack/defaults.test.js
Normal file
|
@ -0,0 +1,8 @@
|
|||
const DefaultWebpackOptionsFactory = require('../../../lib/webpack/defaults');
|
||||
|
||||
describe('DefaultWebpackOptionsFactory', () => {
|
||||
it('should be defined', () => {
|
||||
expect(DefaultWebpackOptionsFactory).toBeDefined();
|
||||
// todo(mikol): write a DefaultWebpackOptionsFactory test suite before v5 official release.
|
||||
});
|
||||
});
|
8
test/unit/webpack/plugin.test.js
Normal file
8
test/unit/webpack/plugin.test.js
Normal file
|
@ -0,0 +1,8 @@
|
|||
const KW_WebpackPlugin = require('../../../lib/webpack/plugin');
|
||||
|
||||
describe('KW_WebpackPlugin', () => {
|
||||
it('should be defined', () => {
|
||||
expect(KW_WebpackPlugin).toBeDefined();
|
||||
// todo(mikol): write a KW_WebpackPlugin test suite before v5 official release.
|
||||
});
|
||||
});
|
Loading…
Reference in a new issue