diff --git a/.eslintrc.js b/.eslintrc.js index 929a75c..43592c8 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -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", diff --git a/lib/index.js b/lib/index.js index 9eb1075..f761834 100644 --- a/lib/index.js +++ b/lib/index.js @@ -1 +1 @@ -module.exports = require('./karma-webpack'); +module.exports = require('./karma/plugin'); diff --git a/lib/KarmaWebpackController.js b/lib/karma-webpack/controller.js similarity index 54% rename from lib/KarmaWebpackController.js rename to lib/karma-webpack/controller.js index 7ff68c9..6fe27e5 100644 --- a/lib/KarmaWebpackController.js +++ b/lib/karma-webpack/controller.js @@ -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'); - 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(); - }); +class KW_Controller { + constructor() { + this.isActive = false; + this.bundlesContent = {}; + this.hasBeenBuiltAtLeastOnce = false; + this.webpackOptions = DefaultWebpackOptionsFactory.create(); } -} -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; diff --git a/lib/karma-webpack/framework.js b/lib/karma-webpack/framework.js new file mode 100644 index 0000000..b44e979 --- /dev/null +++ b/lib/karma-webpack/framework.js @@ -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; diff --git a/lib/karma-webpack.js b/lib/karma-webpack/preprocessor.js similarity index 60% rename from lib/karma-webpack.js rename to lib/karma-webpack/preprocessor.js index c1d7e55..ba037f9 100644 --- a/lib/karma-webpack.js +++ b/lib/karma-webpack/preprocessor.js @@ -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; diff --git a/lib/karma/plugin.js b/lib/karma/plugin.js new file mode 100644 index 0000000..1f990c4 --- /dev/null +++ b/lib/karma/plugin.js @@ -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; diff --git a/lib/karma/karmaConfigValidator.js b/lib/karma/validation.js similarity index 100% rename from lib/karma/karmaConfigValidator.js rename to lib/karma/validation.js diff --git a/lib/webpack/defaults.js b/lib/webpack/defaults.js new file mode 100644 index 0000000..b2de656 --- /dev/null +++ b/lib/webpack/defaults.js @@ -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 }; diff --git a/lib/webpack/plugin.js b/lib/webpack/plugin.js new file mode 100644 index 0000000..47b993c --- /dev/null +++ b/lib/webpack/plugin.js @@ -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; diff --git a/test/integration/scenarios/basic-setup/basic-setup.test.js b/test/integration/scenarios/basic-setup/basic-setup.test.js index 92b15c2..6699621 100644 --- a/test/integration/scenarios/basic-setup/basic-setup.test.js +++ b/test/integration/scenarios/basic-setup/basic-setup.test.js @@ -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; }) diff --git a/test/integration/utils/ScenarioUtils.js b/test/integration/utils/scenario.js similarity index 91% rename from test/integration/utils/ScenarioUtils.js rename to test/integration/utils/scenario.js index 930c1e5..dc0e5b3 100644 --- a/test/integration/utils/ScenarioUtils.js +++ b/test/integration/utils/scenario.js @@ -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; diff --git a/test/unit/KarmaWebpackController.test.js b/test/unit/KarmaWebpackController.test.js deleted file mode 100644 index 70e6d11..0000000 --- a/test/unit/KarmaWebpackController.test.js +++ /dev/null @@ -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 - ); - }); -}); diff --git a/test/unit/karma-webpack.test.js b/test/unit/karma-webpack.test.js deleted file mode 100644 index 24b37b9..0000000 --- a/test/unit/karma-webpack.test.js +++ /dev/null @@ -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, - }, - ]); - }); - }); -}); diff --git a/test/unit/karma-webpack/controller.test.js b/test/unit/karma-webpack/controller.test.js new file mode 100644 index 0000000..4462313 --- /dev/null +++ b/test/unit/karma-webpack/controller.test.js @@ -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 + ); + }); +}); diff --git a/test/unit/karma-webpack/framework.test.js b/test/unit/karma-webpack/framework.test.js new file mode 100644 index 0000000..4b8674d --- /dev/null +++ b/test/unit/karma-webpack/framework.test.js @@ -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, + }, + ]); + }); +}); diff --git a/test/unit/karma-webpack/preprocessor.test.js b/test/unit/karma-webpack/preprocessor.test.js new file mode 100644 index 0000000..4da3a93 --- /dev/null +++ b/test/unit/karma-webpack/preprocessor.test.js @@ -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. +}); diff --git a/test/unit/karma/plugin.test.js b/test/unit/karma/plugin.test.js new file mode 100644 index 0000000..60f8ec0 --- /dev/null +++ b/test/unit/karma/plugin.test.js @@ -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. +}); diff --git a/test/unit/util/karmaConfigValidation.test.js b/test/unit/karma/validation.test.js similarity index 91% rename from test/unit/util/karmaConfigValidation.test.js rename to test/unit/karma/validation.test.js index ca6954f..0a59df3 100644 --- a/test/unit/util/karmaConfigValidation.test.js +++ b/test/unit/karma/validation.test.js @@ -1,6 +1,4 @@ -const { - ensureWebpackFrameworkSet, -} = require('../../../lib/karma/karmaConfigValidator'); +const { ensureWebpackFrameworkSet } = require('../../../lib/karma/validation'); describe('karmaConfigValidation', () => { describe('ensureWebpackFrameworkExists', () => { diff --git a/test/unit/webpack/defaults.test.js b/test/unit/webpack/defaults.test.js new file mode 100644 index 0000000..5b493df --- /dev/null +++ b/test/unit/webpack/defaults.test.js @@ -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. + }); +}); diff --git a/test/unit/webpack/plugin.test.js b/test/unit/webpack/plugin.test.js new file mode 100644 index 0000000..f9ab454 --- /dev/null +++ b/test/unit/webpack/plugin.test.js @@ -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. + }); +});