Skip to content

Commit 5300200

Browse files
authoredFeb 1, 2021
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
1 parent 8ad09d1 commit 5300200

20 files changed

+264
-200
lines changed
 

‎.eslintrc.js

+4
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
11
module.exports = {
22
root: true,
3+
globals: {
4+
"jasmine": true,
5+
},
36
plugins: ['prettier'],
47
extends: ['@webpack-contrib/eslint-config-webpack'],
58
rules: {
69
"consistent-return": "off",
10+
"camelcase": "off",
711
"no-console": "off",
812
"no-param-reassign": "off",
913
"no-underscore-dangle": "off",

‎lib/index.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
module.exports = require('./karma-webpack');
1+
module.exports = require('./karma/plugin');
Original file line numberDiff line numberDiff line change
@@ -1,75 +1,17 @@
1-
const path = require('path');
2-
const fs = require('fs');
3-
const os = require('os');
4-
51
const webpack = require('webpack');
62
const merge = require('webpack-merge');
73

8-
class KarmaSyncPlugin {
9-
constructor(options) {
10-
this.karmaEmitter = options.karmaEmitter;
11-
this.controller = options.controller;
12-
}
4+
const KW_WebpackPlugin = require('../webpack/plugin');
5+
const DefaultWebpackOptionsFactory = require('../webpack/defaults');
136

14-
apply(compiler) {
15-
this.compiler = compiler;
16-
17-
// webpack bundles are finished
18-
compiler.hooks.done.tap('KarmaSyncPlugin', async (stats) => {
19-
// read generated file content and store for karma preprocessor
20-
this.controller.bundlesContent = {};
21-
stats.toJson().assets.forEach((webpackFileObj) => {
22-
const filePath = `${compiler.options.output.path}/${webpackFileObj.name}`;
23-
this.controller.bundlesContent[webpackFileObj.name] = fs.readFileSync(
24-
filePath,
25-
'utf-8'
26-
);
27-
});
28-
29-
// karma refresh
30-
this.karmaEmitter.refreshFiles();
31-
});
7+
class KW_Controller {
8+
constructor() {
9+
this.isActive = false;
10+
this.bundlesContent = {};
11+
this.hasBeenBuiltAtLeastOnce = false;
12+
this.webpackOptions = DefaultWebpackOptionsFactory.create();
3213
}
33-
}
3414

35-
const defaultWebpackOptions = {
36-
mode: 'development',
37-
output: {
38-
filename: '[name].js',
39-
// eslint-disable-next-line prettier/prettier
40-
path: path.join(os.tmpdir(), '_karma_webpack_') + Math.floor(Math.random() * 1000000),
41-
},
42-
stats: {
43-
modules: false,
44-
colors: true,
45-
},
46-
watch: false,
47-
optimization: {
48-
runtimeChunk: 'single',
49-
splitChunks: {
50-
chunks: 'all',
51-
minSize: 0,
52-
cacheGroups: {
53-
commons: {
54-
name: 'commons',
55-
chunks: 'all',
56-
minChunks: 1,
57-
},
58-
},
59-
},
60-
},
61-
plugins: [],
62-
// Something like this will be auto added by this.configure()
63-
// entry: {
64-
// 'foo-one.test.js': 'path/to/test/foo-one.test.js',
65-
// 'foo-two.test.js': 'path/to/test/foo-two.test.js',
66-
// },
67-
// plugins: [
68-
// new KarmaSyncPlugin()
69-
// ],
70-
};
71-
72-
class KarmaWebpackController {
7315
set webpackOptions(options) {
7416
this.__webpackOptions = options;
7517
}
@@ -78,11 +20,15 @@ class KarmaWebpackController {
7820
return this.__webpackOptions;
7921
}
8022

23+
updateWebpackOptions(newOptions) {
24+
this.webpackOptions = merge(this.webpackOptions, newOptions);
25+
}
26+
8127
set karmaEmitter(emitter) {
8228
this.__karmaEmitter = emitter;
8329

8430
this.__webpackOptions.plugins.push(
85-
new KarmaSyncPlugin({
31+
new KW_WebpackPlugin({
8632
karmaEmitter: emitter,
8733
controller: this,
8834
})
@@ -97,13 +43,6 @@ class KarmaWebpackController {
9743
return this.webpackOptions.output.path;
9844
}
9945

100-
constructor() {
101-
this.isActive = false;
102-
this.bundlesContent = {};
103-
this.hasBeenBuiltAtLeastOnce = false;
104-
this.webpackOptions = defaultWebpackOptions;
105-
}
106-
10746
setupExitHandler(compiler) {
10847
this.karmaEmitter.once('exit', (done) => {
10948
compiler.close(() => {
@@ -113,10 +52,6 @@ class KarmaWebpackController {
11352
});
11453
}
11554

116-
updateWebpackOptions(newOptions) {
117-
this.webpackOptions = merge(this.webpackOptions, newOptions);
118-
}
119-
12055
async bundle() {
12156
if (this.isActive === false && this.hasBeenBuiltAtLeastOnce === false) {
12257
console.log('Webpack bundling...');
@@ -169,8 +104,4 @@ class KarmaWebpackController {
169104
}
170105
}
171106

172-
module.exports = {
173-
KarmaSyncPlugin,
174-
KarmaWebpackController,
175-
defaultWebpackOptions,
176-
};
107+
module.exports = KW_Controller;

‎lib/karma-webpack/framework.js

+36
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
const fs = require('fs');
2+
const path = require('path');
3+
4+
function KW_Framework(config) {
5+
// This controller is instantiated and set during the preprocessor phase.
6+
const controller = config.__karmaWebpackController;
7+
const commonsPath = path.join(controller.outputPath, 'commons.js');
8+
const runtimePath = path.join(controller.outputPath, 'runtime.js');
9+
10+
// make sure tmp folder exists
11+
if (!fs.existsSync(controller.outputPath)) {
12+
fs.mkdirSync(controller.outputPath);
13+
}
14+
15+
// create dummy files for commons.js and runtime.js so they get included by karma
16+
fs.closeSync(fs.openSync(commonsPath, 'w'));
17+
fs.closeSync(fs.openSync(runtimePath, 'w'));
18+
19+
// register for karma
20+
config.files.unshift({
21+
pattern: commonsPath,
22+
included: true,
23+
served: true,
24+
watched: false,
25+
});
26+
config.files.unshift({
27+
pattern: runtimePath,
28+
included: true,
29+
served: true,
30+
watched: false,
31+
});
32+
}
33+
34+
KW_Framework.$inject = ['config'];
35+
36+
module.exports = KW_Framework;

‎lib/karma-webpack.js ‎lib/karma-webpack/preprocessor.js

+8-45
Original file line numberDiff line numberDiff line change
@@ -1,45 +1,12 @@
11
const path = require('path');
2-
const fs = require('fs');
32

43
const glob = require('glob');
54
const minimatch = require('minimatch');
65

7-
const { ensureWebpackFrameworkSet } = require('./karma/karmaConfigValidator');
6+
const { ensureWebpackFrameworkSet } = require('../karma/validation');
7+
const { hash } = require('../utils/hash');
88

9-
const { hash } = require('./utils/hash');
10-
11-
const { KarmaWebpackController } = require('./KarmaWebpackController');
12-
13-
const controller = new KarmaWebpackController();
14-
15-
function registerExtraWebpackFiles(config, _controller) {
16-
const localController = _controller || controller;
17-
const commonsPath = path.join(localController.outputPath, 'commons.js');
18-
const runtimePath = path.join(localController.outputPath, 'runtime.js');
19-
20-
// make sure tmp folder exists
21-
if (!fs.existsSync(localController.outputPath)) {
22-
fs.mkdirSync(localController.outputPath);
23-
}
24-
25-
// create dummy files for commons.js and runtime.js so they get included by karma
26-
fs.closeSync(fs.openSync(commonsPath, 'w'));
27-
fs.closeSync(fs.openSync(runtimePath, 'w'));
28-
29-
// register for karma
30-
config.files.unshift({
31-
pattern: commonsPath,
32-
included: true,
33-
served: true,
34-
watched: false,
35-
});
36-
config.files.unshift({
37-
pattern: runtimePath,
38-
included: true,
39-
served: true,
40-
watched: false,
41-
});
42-
}
9+
const KW_Controller = require('./controller');
4310

4411
function getPathKey(filePath, withExtension = false) {
4512
const pathParts = path.parse(filePath);
@@ -81,7 +48,9 @@ function configToWebpackEntries(config) {
8148
return webpackEntries;
8249
}
8350

84-
function preprocessorFactory(config, emitter) {
51+
function KW_Preprocessor(config, emitter) {
52+
const controller = new KW_Controller();
53+
config.__karmaWebpackController = controller;
8554
ensureWebpackFrameworkSet(config);
8655

8756
// one time setup
@@ -118,12 +87,6 @@ function preprocessorFactory(config, emitter) {
11887
};
11988
}
12089

121-
registerExtraWebpackFiles.$inject = ['config'];
122-
preprocessorFactory.$inject = ['config', 'emitter'];
90+
KW_Preprocessor.$inject = ['config', 'emitter'];
12391

124-
module.exports = {
125-
'preprocessor:webpack': ['factory', preprocessorFactory],
126-
'framework:webpack': ['factory', registerExtraWebpackFiles],
127-
registerExtraWebpackFiles,
128-
configToWebpackEntries,
129-
};
92+
module.exports = KW_Preprocessor;

‎lib/karma/plugin.js

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
const KW_Framework = require('../karma-webpack/framework');
2+
const KW_Preprocessor = require('../karma-webpack/preprocessor');
3+
4+
const KW_KarmaPlugin = {
5+
'preprocessor:webpack': ['factory', KW_Preprocessor],
6+
'framework:webpack': ['factory', KW_Framework],
7+
};
8+
9+
module.exports = KW_KarmaPlugin;
File renamed without changes.

‎lib/webpack/defaults.js

+35
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
const path = require('path');
2+
const os = require('os');
3+
4+
function create() {
5+
return {
6+
mode: 'development',
7+
output: {
8+
filename: '[name].js',
9+
// eslint-disable-next-line prettier/prettier
10+
path: path.join(os.tmpdir(), '_karma_webpack_') + Math.floor(Math.random() * 1000000),
11+
},
12+
stats: {
13+
modules: false,
14+
colors: true,
15+
},
16+
watch: false,
17+
optimization: {
18+
runtimeChunk: 'single',
19+
splitChunks: {
20+
chunks: 'all',
21+
minSize: 0,
22+
cacheGroups: {
23+
commons: {
24+
name: 'commons',
25+
chunks: 'all',
26+
minChunks: 1,
27+
},
28+
},
29+
},
30+
},
31+
plugins: [],
32+
};
33+
}
34+
35+
module.exports = { create };

‎lib/webpack/plugin.js

+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
const fs = require('fs');
2+
3+
class KW_WebpackPlugin {
4+
constructor(options) {
5+
this.karmaEmitter = options.karmaEmitter;
6+
this.controller = options.controller;
7+
}
8+
9+
apply(compiler) {
10+
this.compiler = compiler;
11+
12+
// webpack bundles are finished
13+
compiler.hooks.done.tap('KW_WebpackPlugin', async (stats) => {
14+
// read generated file content and store for karma preprocessor
15+
this.controller.bundlesContent = {};
16+
stats.toJson().assets.forEach((webpackFileObj) => {
17+
const filePath = `${compiler.options.output.path}/${
18+
webpackFileObj.name
19+
}`;
20+
this.controller.bundlesContent[webpackFileObj.name] = fs.readFileSync(
21+
filePath,
22+
'utf-8'
23+
);
24+
});
25+
26+
// karma refresh
27+
this.karmaEmitter.refreshFiles();
28+
});
29+
}
30+
}
31+
32+
module.exports = KW_WebpackPlugin;

‎test/integration/scenarios/basic-setup/basic-setup.test.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import karmaChromeLauncher from 'karma-chrome-launcher';
44
import karmaMocha from 'karma-mocha';
55
import karmaChai from 'karma-chai';
66

7-
import ScenarioUtils from '../../utils/ScenarioUtils';
7+
import Scenario from '../../utils/scenario';
88

99
process.env.CHROME_BIN = require('puppeteer').executablePath();
1010

@@ -36,7 +36,7 @@ describe('A basic karma-webpack setup', () => {
3636
};
3737

3838
beforeAll((done) => {
39-
ScenarioUtils.run(config)
39+
Scenario.run(config)
4040
.then((res) => {
4141
scenario = res;
4242
})

‎test/integration/utils/ScenarioUtils.js ‎test/integration/utils/scenario.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
const karma = require('karma');
22

3-
const ScenarioUtils = { run };
3+
const Scenario = { run };
44

55
/**
66
* This allows you to run karma with a given configuration and be returned.
@@ -20,4 +20,4 @@ function run(config) {
2020
});
2121
}
2222

23-
export default ScenarioUtils;
23+
export default Scenario;

‎test/unit/KarmaWebpackController.test.js

-26
This file was deleted.

‎test/unit/karma-webpack.test.js

-38
This file was deleted.
+52
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
const KW_Controller = require('../../../lib/karma-webpack/controller');
2+
const DefaultWebpackOptionsFactory = require('../../../lib/webpack/defaults');
3+
4+
const defaultWebpackOptions = DefaultWebpackOptionsFactory.create();
5+
6+
describe('KW_Controller', () => {
7+
const EXPECTED_DEFAULT_PATH_PREFIX = '/tmp/_karma_webpack_';
8+
9+
let controller;
10+
11+
beforeEach(() => (controller = new KW_Controller()));
12+
13+
it('initializes with a webpackOptions object', () => {
14+
expect(controller.webpackOptions).toBeDefined();
15+
expect(controller.webpackOptions).toEqual(jasmine.any(Object));
16+
});
17+
18+
it('correctly sets the default output path prefix', () => {
19+
expect(
20+
controller.webpackOptions.output.path.startsWith(
21+
EXPECTED_DEFAULT_PATH_PREFIX
22+
)
23+
).toBeTruthy();
24+
});
25+
26+
it('correctly postfixes a random number to the end of the webpack options output path for parallel runs', () => {
27+
const postfix = controller.webpackOptions.output.path.split(
28+
EXPECTED_DEFAULT_PATH_PREFIX
29+
)[1];
30+
expect(isNaN(postfix)).toBe(false);
31+
});
32+
33+
it('should otherwise be equal to a newly instantiated default webpack options object', () => {
34+
controller.webpackOptions.output.path = EXPECTED_DEFAULT_PATH_PREFIX;
35+
defaultWebpackOptions.output.path = EXPECTED_DEFAULT_PATH_PREFIX;
36+
expect(controller.webpackOptions).toEqual(defaultWebpackOptions);
37+
});
38+
39+
it('can provide custom nested webpackOptions', () => {
40+
controller.updateWebpackOptions({
41+
output: {
42+
path: 'foo',
43+
publicPath: 'bar',
44+
},
45+
});
46+
expect(controller.webpackOptions.output.path).toBe('foo');
47+
expect(controller.webpackOptions.output.publicPath).toBe('bar');
48+
expect(controller.webpackOptions.output.filename).toBe(
49+
defaultWebpackOptions.output.filename
50+
);
51+
});
52+
});
+36
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
const fs = require('fs');
2+
const path = require('path');
3+
4+
const KW_Framework = require('../../../lib/karma-webpack/framework');
5+
6+
jest.mock('fs');
7+
8+
describe('KW_Framework', () => {
9+
test('Defaults', () => {
10+
const controller = { outputPath: 'foo/' };
11+
const config = { files: [], __karmaWebpackController: controller };
12+
fs.closeSync = jest.fn();
13+
fs.openSync = jest.fn();
14+
15+
KW_Framework(config);
16+
17+
expect(fs.openSync).toBeCalledWith(path.join('foo', 'commons.js'), 'w');
18+
expect(fs.openSync).toBeCalledWith(path.join('foo', 'runtime.js'), 'w');
19+
20+
expect(config.files.length).toBe(2);
21+
expect(config.files).toEqual([
22+
{
23+
pattern: path.join('foo', 'runtime.js'),
24+
included: true,
25+
served: true,
26+
watched: false,
27+
},
28+
{
29+
pattern: path.join('foo', 'commons.js'),
30+
included: true,
31+
served: true,
32+
watched: false,
33+
},
34+
]);
35+
});
36+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
const KW_Preprocessor = require('../../../lib/karma-webpack/preprocessor');
2+
3+
describe('KW_Preprocessor', () => {
4+
it('should be defined', () => {
5+
expect(KW_Preprocessor).toBeDefined();
6+
});
7+
// todo(mikol): write a KW_Preprocessor test suite before v5 official release.
8+
});

‎test/unit/karma/plugin.test.js

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
const KW_KarmaPlugin = require('../../../lib/karma/plugin');
2+
3+
describe('KW_KarmaPlugin', () => {
4+
it('should be defined', () => {
5+
expect(KW_KarmaPlugin).toBeDefined();
6+
});
7+
// todo(mikol): write test suite for KarmaWebpackPlugin prior to v5 official release.
8+
});

‎test/unit/util/karmaConfigValidation.test.js ‎test/unit/karma/validation.test.js

+1-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,4 @@
1-
const {
2-
ensureWebpackFrameworkSet,
3-
} = require('../../../lib/karma/karmaConfigValidator');
1+
const { ensureWebpackFrameworkSet } = require('../../../lib/karma/validation');
42

53
describe('karmaConfigValidation', () => {
64
describe('ensureWebpackFrameworkExists', () => {

‎test/unit/webpack/defaults.test.js

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
const DefaultWebpackOptionsFactory = require('../../../lib/webpack/defaults');
2+
3+
describe('DefaultWebpackOptionsFactory', () => {
4+
it('should be defined', () => {
5+
expect(DefaultWebpackOptionsFactory).toBeDefined();
6+
// todo(mikol): write a DefaultWebpackOptionsFactory test suite before v5 official release.
7+
});
8+
});

‎test/unit/webpack/plugin.test.js

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
const KW_WebpackPlugin = require('../../../lib/webpack/plugin');
2+
3+
describe('KW_WebpackPlugin', () => {
4+
it('should be defined', () => {
5+
expect(KW_WebpackPlugin).toBeDefined();
6+
// todo(mikol): write a KW_WebpackPlugin test suite before v5 official release.
7+
});
8+
});

0 commit comments

Comments
 (0)
Please sign in to comment.