diff --git a/README.md b/README.md index 36f528b..5bfb8be 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,12 @@ # Karmatic [![npm](https://img.shields.io/npm/v/karmatic.svg)](https://npm.im/karmatic) [![travis](https://travis-ci.org/developit/karmatic.svg?branch=master)](https://travis-ci.org/developit/karmatic) -A simplified zero-configuration wrapper around [Karma], [Webpack], [Jasmine] & [Puppeteer]. +Zero-config browser tests powered by [Karma] & [Puppeteer], with automatic [Rollup] & [Webpack] support. Think of it like **Jest for cross-browser testing** - it even uses the same [expect syntax](https://jestjs.io/docs/en/using-matchers). ## Why do I want this? -Karma, Webpack and Jasmine are all great. They're all also quite powerful and each highly configurable. When creating and maintaining small modules, duplication of these configurations and dependencies is cumbersome. +Karma, Rollup/Webpack and Jasmine are all great. They're all also quite powerful and each highly configurable. When creating and maintaining small modules, duplication of these configurations and dependencies is cumbersome. Karmatic is a zero-configuration wrapper around these tools with intelligent defaults, configuration auto-detection, and optimizations most configurations don't include. @@ -15,7 +15,7 @@ Most importantly, Karmatic provides a (headless) browser test harness in a singl ## Installation ```sh -npm i -D webpack karmatic +npm i -D karmatic ``` ... then add a `test` script to your `package.json`: @@ -30,6 +30,8 @@ npm i -D webpack karmatic ... now you can run your tests using `npm t`. Here's a [minimal example repo](https://gist.github.com/developit/acd8a075350eeb6574439e92888c50cf). +If you have webpack set up in your project, it will be detected and your `webpack.config.js` will be used. Otherwise, Rollup is used to bundle tests and any `rollup.config.js` will be used if present. + ### Test File Patterns By default, Karmatic will find tests in any files ending in `.test.js` or `_test.js`. @@ -95,6 +97,7 @@ Karmatic is pretty new! Here are some projects that have switched to it you may [MIT](https://oss.ninja/mit/developit) © [developit](https://github.com/developit) [karma]: https://karma-runner.github.io +[rollup]: https://rollupjs.org/ [webpack]: https://webpack.js.org [jasmine]: https://jasmine.github.io [puppeteer]: https://github.com/GoogleChrome/puppeteer diff --git a/e2e-test/default/index.js b/e2e-test/default/index.js new file mode 100644 index 0000000..2b3e669 --- /dev/null +++ b/e2e-test/default/index.js @@ -0,0 +1,7 @@ +export function render(container) { + let div = document.createElement('div'); + div.id = 'hello-world'; + div.textContent = 'Hello World!'; + + container.appendChild(div); +} diff --git a/e2e-test/default/index.test.js b/e2e-test/default/index.test.js new file mode 100644 index 0000000..d8c79ea --- /dev/null +++ b/e2e-test/default/index.test.js @@ -0,0 +1,16 @@ +import { render } from './index'; + +describe('Hello World', () => { + it('should not be bundled using webpack', () => { + // eslint-disable-next-line camelcase + expect(typeof __webpack_require__).toBe('undefined'); + }); + + it('should be rendered to container', () => { + render(document.body); + + let element = document.getElementById('hello-world'); + expect(element).toBeTruthy(); + expect(element.textContent).toBe('Hello World!'); + }); +}); diff --git a/e2e-test/default/package.json b/e2e-test/default/package.json new file mode 100644 index 0000000..af8d063 --- /dev/null +++ b/e2e-test/default/package.json @@ -0,0 +1,14 @@ +{ + "name": "karmatic-e2e-default", + "description": "Test default config in karmatic.", + "private": true, + "scripts": { + "test:debug": "cross-env NODE_PRESERVE_SYMLINKS_MAIN=1 NODE_PRESERVE_SYMLINKS=1 node ./node_modules/karmatic/dist/cli.js debug" + }, + "dependencies": { + "karmatic": "file:../.." + }, + "devDependencies": { + "cross-env": "^7.0.2" + } +} diff --git a/e2e-test/rollup-custom/index.js b/e2e-test/rollup-custom/index.js new file mode 100644 index 0000000..0dc0ae1 --- /dev/null +++ b/e2e-test/rollup-custom/index.js @@ -0,0 +1,3 @@ +export function box(value) { + return { _value: value }; +} diff --git a/e2e-test/rollup-custom/index.test.js b/e2e-test/rollup-custom/index.test.js new file mode 100644 index 0000000..11e7df8 --- /dev/null +++ b/e2e-test/rollup-custom/index.test.js @@ -0,0 +1,14 @@ +import { box } from './index'; + +describe('Box', () => { + it('should not be bundled using webpack', () => { + // eslint-disable-next-line camelcase + expect(typeof __webpack_require__).toBe('undefined'); + }); + + it('should have a __v property', () => { + const boxed = box(1); + expect('_value' in boxed).toBe(false); + expect(boxed.__v).toBe(1); + }); +}); diff --git a/e2e-test/rollup-custom/package.json b/e2e-test/rollup-custom/package.json new file mode 100644 index 0000000..f3f15cb --- /dev/null +++ b/e2e-test/rollup-custom/package.json @@ -0,0 +1,20 @@ +{ + "name": "karmatic-e2e-rollup-custom", + "description": "Test custom rollup config in karmatic", + "private": true, + "scripts": { + "test:debug": "cross-env NODE_PRESERVE_SYMLINKS_MAIN=1 NODE_PRESERVE_SYMLINKS=1 node ./node_modules/karmatic/dist/cli.js debug" + }, + "dependencies": { + "@babel/core": "^7.10.3", + "@rollup/plugin-babel": "5.2.0", + "@rollup/plugin-commonjs": "^14.0.0", + "@rollup/plugin-node-resolve": "^8.4.0", + "babel-plugin-transform-rename-properties": "^0.1.0", + "rollup": "^2.3.0", + "karmatic": "file:../../" + }, + "devDependencies": { + "cross-env": "^7.0.2" + } +} diff --git a/e2e-test/rollup-custom/rollup.config.js b/e2e-test/rollup-custom/rollup.config.js new file mode 100644 index 0000000..3cf1979 --- /dev/null +++ b/e2e-test/rollup-custom/rollup.config.js @@ -0,0 +1,24 @@ +import nodeResolve from '@rollup/plugin-node-resolve'; +import commonjs from '@rollup/plugin-commonjs'; +import babel from '@rollup/plugin-babel'; + +export default { + input: 'index.js', + output: { + dir: 'dist', + format: 'es', + }, + plugins: [ + babel({ + babelHelpers: 'bundled', + plugins: [ + [ + 'babel-plugin-transform-rename-properties', + { rename: { _value: '__v' } }, + ], + ], + }), + nodeResolve(), + commonjs(), + ], +}; diff --git a/e2e-test/rollup-default/package.json b/e2e-test/rollup-default/package.json new file mode 100644 index 0000000..57767e5 --- /dev/null +++ b/e2e-test/rollup-default/package.json @@ -0,0 +1,15 @@ +{ + "name": "karmatic-e2e-rollup-default", + "description": "Test default rollup config in karmatic. Mildly complex src implementation to verify coverage works", + "private": true, + "scripts": { + "test:debug": "cross-env NODE_PRESERVE_SYMLINKS_MAIN=1 NODE_PRESERVE_SYMLINKS=1 node ./node_modules/karmatic/dist/cli.js debug" + }, + "dependencies": { + "rollup": "^2.3.0", + "karmatic": "file:../.." + }, + "devDependencies": { + "cross-env": "^7.0.2" + } +} diff --git a/e2e-test/rollup-default/src/index.js b/e2e-test/rollup-default/src/index.js new file mode 100644 index 0000000..5b325bc --- /dev/null +++ b/e2e-test/rollup-default/src/index.js @@ -0,0 +1,29 @@ +export function combine(input1, input2) { + const isInput1Array = Array.isArray(input1); + const isInput2Array = Array.isArray(input2); + + if (typeof input1 !== typeof input2 || isInput1Array !== isInput2Array) { + const input1Type = isInput1Array ? 'array' : typeof input1; + const input2Type = isInput2Array ? 'array' : typeof input2; + throw new Error( + `Types of inputs are not the same: input1=${input1Type}, input2=${input2Type}` + ); + } + + if (typeof input1 == 'string' || typeof input1 == 'number') { + return input1 + input2; + } + + if (isInput1Array) { + return [...input1, ...input2]; + } + + if (typeof input1 == 'object') { + return { + ...input1, + ...input2, + }; + } + + throw new Error(`Unsupported type: ${typeof input1}`); +} diff --git a/e2e-test/rollup-default/test/combine.test.js b/e2e-test/rollup-default/test/combine.test.js new file mode 100644 index 0000000..ab57c4c --- /dev/null +++ b/e2e-test/rollup-default/test/combine.test.js @@ -0,0 +1,48 @@ +import { combine } from '../src/index'; + +describe('combine', () => { + it('should concatenate strings', () => { + expect(combine('a', 'b')).toBe('ab'); + }); + + it('should add numbers', () => { + expect(combine(1, 2)).toBe(3); + }); + + it('should concatenate arrays', () => { + expect(combine([1, 2], ['a', 'b'])).toEqual([1, 2, 'a', 'b']); + }); + + it('should merge objects', () => { + expect(combine({ a: 1, b: 2 }, { c: 'c', d: 'd', a: 'a' })).toEqual({ + a: 'a', + b: 2, + c: 'c', + d: 'd', + }); + }); + + it('throw an error if types do not match', () => { + expect(() => combine('a', 1)).toThrow(); + expect(() => combine(1, 'a')).toThrow(); + + expect(() => combine([1], 1)).toThrow(); + expect(() => combine(1, [1])).toThrow(); + + expect(() => combine({}, 2)).toThrow(); + expect(() => combine(1, {})).toThrow(); + }); + + it('throw an error if type is unsupported', () => { + expect(() => + combine( + () => {}, + () => {} + ) + ).toThrow(); + + expect(() => combine(true, false)).toThrow(); + + expect(() => combine(Symbol.for('a'), Symbol.for('b'))).toThrow(); + }); +}); diff --git a/e2e-test/rollup-default/test/custom-pragma.test.js b/e2e-test/rollup-default/test/custom-pragma.test.js new file mode 100644 index 0000000..bdd6396 --- /dev/null +++ b/e2e-test/rollup-default/test/custom-pragma.test.js @@ -0,0 +1,15 @@ +/** @jsx createElement */ + +describe('Custom JSX Pragma', () => { + it('should use custom babel config', () => { + let h = jasmine.createSpy('h'); + let createElement = jasmine.createSpy('createElement'); + let React = { createElement: jasmine.createSpy('React.createElement') }; + +
hello
; + + expect(h).not.toHaveBeenCalled(); + expect(React.createElement).not.toHaveBeenCalled(); + expect(createElement).toHaveBeenCalledWith('div', { id: 'foo' }, 'hello'); + }); +}); diff --git a/e2e-test/rollup-default/test/index.test.js b/e2e-test/rollup-default/test/index.test.js new file mode 100644 index 0000000..d05506b --- /dev/null +++ b/e2e-test/rollup-default/test/index.test.js @@ -0,0 +1,24 @@ +const sleep = (ms) => new Promise((r) => setTimeout(r, ms)); + +describe('Basic test functions', () => { + it('should be not bundled using webpack', () => { + // eslint-disable-next-line camelcase + expect(typeof __webpack_require__).toBe('undefined'); + }); + + it('should work', () => { + expect(1).toEqual(1); + }); + + it('should handle deep equality', () => { + expect({ foo: 1 }).toEqual({ foo: 1 }); + }); + + it('should handle async tests', async () => { + let start = Date.now(); + await sleep(100); + + let now = Date.now(); + expect(now - start).toBeGreaterThan(50); + }); +}); diff --git a/e2e-test/rollup-default/test/jest-style.test.js b/e2e-test/rollup-default/test/jest-style.test.js new file mode 100644 index 0000000..7292d79 --- /dev/null +++ b/e2e-test/rollup-default/test/jest-style.test.js @@ -0,0 +1,9 @@ +describe('jest-style', () => { + describe('not.stringContaining', () => { + const expected = 'Hello world!'; + + it('matches if the received value does not contain the expected substring', () => { + expect('How are you?').toEqual(expect.not.stringContaining(expected)); + }); + }); +}); diff --git a/e2e-test/webpack-custom/package.json b/e2e-test/webpack-custom/package.json index b181fa6..4d64446 100644 --- a/e2e-test/webpack-custom/package.json +++ b/e2e-test/webpack-custom/package.json @@ -2,11 +2,17 @@ "name": "karmatic-e2e-webpack-custom", "description": "Test custom webpack config in karmatic using a custom Babel config", "private": true, + "scripts": { + "test:debug": "cross-env NODE_PRESERVE_SYMLINKS_MAIN=1 NODE_PRESERVE_SYMLINKS=1 node ./node_modules/karmatic/dist/cli.js debug" + }, "dependencies": { "@babel/core": "^7.10.3", "babel-loader": "8.1.0", "babel-plugin-transform-rename-properties": "^0.1.0", "karmatic": "file:../..", "webpack": "^4.44.1" + }, + "devDependencies": { + "cross-env": "^7.0.2" } } diff --git a/e2e-test/webpack-custom/test/index.test.js b/e2e-test/webpack-custom/test/index.test.js index e55386a..77ff68c 100644 --- a/e2e-test/webpack-custom/test/index.test.js +++ b/e2e-test/webpack-custom/test/index.test.js @@ -1,6 +1,11 @@ import { box } from '../src/index'; describe('Box', () => { + it('should be bundled using webpack', () => { + // eslint-disable-next-line camelcase + expect(typeof __webpack_require__).toBe('function'); + }); + it('should have a __v property', () => { const boxed = box(1); expect('_value' in boxed).toBe(false); diff --git a/e2e-test/webpack-default/package.json b/e2e-test/webpack-default/package.json index beb5160..25d3a75 100644 --- a/e2e-test/webpack-default/package.json +++ b/e2e-test/webpack-default/package.json @@ -4,7 +4,8 @@ "private": true, "scripts": { "test": "cross-env NODE_PRESERVE_SYMLINKS_MAIN=1 NODE_PRESERVE_SYMLINKS=1 node ./node_modules/karmatic/dist/cli.js run", - "test:watch": "cross-env NODE_PRESERVE_SYMLINKS_MAIN=1 NODE_PRESERVE_SYMLINKS=1 node ./node_modules/karmatic/dist/cli.js --headless false" + "test:watch": "cross-env NODE_PRESERVE_SYMLINKS_MAIN=1 NODE_PRESERVE_SYMLINKS=1 node ./node_modules/karmatic/dist/cli.js --headless false", + "test:debug": "cross-env NODE_PRESERVE_SYMLINKS_MAIN=1 NODE_PRESERVE_SYMLINKS=1 node ./node_modules/karmatic/dist/cli.js debug" }, "dependencies": { "karmatic": "file:../..", diff --git a/e2e-test/webpack-default/test/index.test.js b/e2e-test/webpack-default/test/index.test.js index 59ddaa7..a6fc602 100644 --- a/e2e-test/webpack-default/test/index.test.js +++ b/e2e-test/webpack-default/test/index.test.js @@ -1,6 +1,11 @@ const sleep = (ms) => new Promise((r) => setTimeout(r, ms)); describe('Basic test functions', () => { + it('should be bundled using webpack', () => { + // eslint-disable-next-line camelcase + expect(typeof __webpack_require__).toBe('function'); + }); + it('should work', () => { expect(1).toEqual(1); }); diff --git a/e2e-test/webpack-loader/index.test.js b/e2e-test/webpack-loader/index.test.js index dd416ae..48ff0e7 100644 --- a/e2e-test/webpack-loader/index.test.js +++ b/e2e-test/webpack-loader/index.test.js @@ -1,6 +1,11 @@ import { getWorker } from './index'; describe('demo', () => { + it('should be bundled using webpack', () => { + // eslint-disable-next-line camelcase + expect(typeof __webpack_require__).toBe('function'); + }); + it('should do MAGIC', async () => { let worker = getWorker(); diff --git a/e2e-test/webpack-loader/package.json b/e2e-test/webpack-loader/package.json index 9e93bfa..843127c 100644 --- a/e2e-test/webpack-loader/package.json +++ b/e2e-test/webpack-loader/package.json @@ -2,9 +2,15 @@ "name": "karmatic-e2e-webpack-workerize-loader", "description": "Test customer webpack loader testing in karmatic", "private": true, + "scripts": { + "test:debug": "cross-env NODE_PRESERVE_SYMLINKS_MAIN=1 NODE_PRESERVE_SYMLINKS=1 node ./node_modules/karmatic/dist/cli.js debug" + }, "dependencies": { "webpack": "^4.44.1", "workerize-loader": "^1.3.0", "karmatic": "file:../.." + }, + "devDependencies": { + "cross-env": "^7.0.2" } } diff --git a/package.json b/package.json index f1aafeb..1c7ee3c 100644 --- a/package.json +++ b/package.json @@ -46,6 +46,9 @@ "@babel/core": "^7.11.0", "@babel/plugin-transform-react-jsx": "^7.10.3", "@babel/preset-env": "^7.11.0", + "@rollup/plugin-babel": "^5.1.0", + "@rollup/plugin-commonjs": "^14.0.0", + "@rollup/plugin-node-resolve": "^8.4.0", "babel-loader": "^8.1.0", "babel-plugin-istanbul": "^6.0.0", "chalk": "^2.3.0", @@ -59,11 +62,13 @@ "karma-firefox-launcher": "^1.3.0", "karma-jasmine": "^4.0.0", "karma-min-reporter": "^0.1.0", + "karma-rollup-preprocessor": "^7.0.5", "karma-sauce-launcher": "^4.1.5", "karma-sourcemap-loader": "^0.3.7", "karma-spec-reporter": "0.0.32", "karma-webpack": "^4.0.2", "minimatch": "^3.0.4", + "rollup": "^2.3.4", "sade": "^1.7.3", "simple-code-frame": "^1.0.0" }, diff --git a/src/configure.js b/src/configure.js index aa874ff..3f73c42 100644 --- a/src/configure.js +++ b/src/configure.js @@ -3,7 +3,7 @@ import puppeteer from 'puppeteer'; import chalk from 'chalk'; import { tryRequire, cleanStack, readFile, readDir } from './lib/util'; import { shouldUseWebpack, addWebpackConfig } from './webpack'; -// import minimatch from 'minimatch'; +import { addRollupConfig } from './rollup'; /** * @typedef Options @@ -13,12 +13,14 @@ import { shouldUseWebpack, addWebpackConfig } from './webpack'; * @property {Boolean} [watch=false] - Start a continuous test server and retest when files change * @property {Boolean} [coverage=false] - Instrument and collect code coverage statistics * @property {Object} [webpackConfig] - Custom webpack configuration + * @property {Object} [rollupConfig] - Custom rollup configuration + * @property {string} [pragma] - JSX pragma to compile JSX with * @property {Boolean} [downlevel=false] - Downlevel/transpile syntax to ES5 * @property {string} [chromeDataDir] - Use a custom Chrome profile directory * * @param {Options} options */ -export default function configure(options) { +export default async function configure(options) { let cwd = process.cwd(), res = (file) => path.resolve(cwd, file); @@ -218,6 +220,8 @@ export default function configure(options) { if (shouldUseWebpack(options)) { addWebpackConfig(generatedConfig, pkg, options); + } else { + await addRollupConfig(generatedConfig, pkg, options); } return generatedConfig; diff --git a/src/index.js b/src/index.js index 47f78f0..3e0ce56 100644 --- a/src/index.js +++ b/src/index.js @@ -2,7 +2,7 @@ import { Server } from 'karma'; import configure from './configure'; export default async function karmatic(options) { - let config = configure(options); + let config = await configure(options); if (!options.watch) config.singleRun = true; diff --git a/src/lib/babel-loader.js b/src/lib/babel-loader.js deleted file mode 100644 index 028d696..0000000 --- a/src/lib/babel-loader.js +++ /dev/null @@ -1,42 +0,0 @@ -export default function babelLoader(options) { - return { - test: /\.jsx?$/, - exclude: /node_modules/, - loader: require.resolve('babel-loader'), - query: { - presets: [ - [ - require.resolve('@babel/preset-env'), - { - targets: { - browsers: [ - 'last 2 Chrome versions', - 'last 2 Firefox versions', - (options.downlevel || - (options.browsers && - String(options.browsers).match( - /(\b|ms|microsoft)(ie|internet.explorer|edge)/gi - ))) && - 'ie>=9', - ].filter(Boolean), - }, - corejs: 3, - useBuiltIns: 'usage', - modules: false, - loose: true, - }, - ], - ], - plugins: [ - [ - require.resolve('@babel/plugin-transform-react-jsx'), - { - pragma: options.pragma || 'h', - }, - ], - ].concat( - options.coverage ? [require.resolve('babel-plugin-istanbul')] : [] - ), - }, - }; -} diff --git a/src/lib/babel.js b/src/lib/babel.js new file mode 100644 index 0000000..641d6bc --- /dev/null +++ b/src/lib/babel.js @@ -0,0 +1,52 @@ +/** + * @param {import('../configure').Options} options + */ +export function babelConfig(options) { + return { + presets: [ + [ + require.resolve('@babel/preset-env'), + { + targets: { + browsers: [ + 'last 2 Chrome versions', + 'last 2 Firefox versions', + (options.downlevel || + (options.browsers && + String(options.browsers).match( + /(\b|ms|microsoft)(ie|internet.explorer|edge)/gi + ))) && + 'ie>=9', + ].filter(Boolean), + }, + corejs: 3, + useBuiltIns: 'usage', + modules: false, + loose: true, + }, + ], + ], + plugins: [ + [ + require.resolve('@babel/plugin-transform-react-jsx'), + { + pragma: options.pragma || 'h', + }, + ], + ].concat( + options.coverage ? [require.resolve('babel-plugin-istanbul')] : [] + ), + }; +} + +/** + * @param {import('../configure').Options} options + */ +export function babelLoader(options) { + return { + test: /\.jsx?$/, + exclude: /node_modules/, + loader: require.resolve('babel-loader'), + query: babelConfig(options), + }; +} diff --git a/src/lib/util.js b/src/lib/util.js index 7b37fe2..0f86fb3 100644 --- a/src/lib/util.js +++ b/src/lib/util.js @@ -4,6 +4,9 @@ import chalk from 'chalk'; import { createCodeFrame } from 'simple-code-frame'; import { parseStackTrace } from 'errorstacks'; +const cwd = process.cwd(); +export const res = (file) => path.resolve(cwd, file); + export function moduleDir(name) { let file = require.resolve(name), find = `${path.sep}node_modules${path.sep}${name}`, diff --git a/src/rollup.js b/src/rollup.js new file mode 100644 index 0000000..8471d27 --- /dev/null +++ b/src/rollup.js @@ -0,0 +1,116 @@ +import { babelConfig } from './lib/babel'; +import { res, fileExists } from './lib/util'; + +/** + * @param {import('./configure').Options} options + */ +function getDefaultConfig(options) { + let babel = require('@rollup/plugin-babel').default; + let commonjs = require('@rollup/plugin-commonjs'); + let nodeResolve = require('@rollup/plugin-node-resolve').default; + + return { + output: { + format: 'iife', + name: `KarmaticTests`, + sourcemap: 'inline', + }, + plugins: [ + babel({ + babelHelpers: 'bundled', + ...babelConfig(options), + }), + nodeResolve(), + commonjs(), + ], + }; +} + +/** + * @param {Object} pkg + * @param {import('./configure').Options} options + */ +async function getRollupConfig(pkg, options) { + const ROLLUP_CONFIGS = [ + 'rollup.config.mjs', + 'rollup.config.cjs', + 'rollup.config.js', + ]; + + let rollupConfig = options.rollupConfig; + + if (pkg.scripts) { + for (let i in pkg.scripts) { + let script = pkg.scripts[i]; + if (/\brollup\b[^&|]*(-c|--config)\b/.test(script)) { + let matches = script.match( + /(?:-c|--config)\s+(?:([^\s])|(["'])(.*?)\2)/ + ); + let configFile = matches && (matches[1] || matches[2]); + if (configFile) ROLLUP_CONFIGS.push(configFile); + } + } + } + + if (!rollupConfig) { + for (let i = ROLLUP_CONFIGS.length; i--; ) { + let possiblePath = res(ROLLUP_CONFIGS[i]); + if (fileExists(possiblePath)) { + // Require Rollup 2.3.0 for this export: https://github.com/rollup/rollup/blob/master/CHANGELOG.md#230 + let loadConfigFile = require('rollup/dist/loadConfigFile'); + let rollupConfigResult = await loadConfigFile(possiblePath); + rollupConfigResult.warnings.flush(); + + if (rollupConfigResult.options.length > 1) { + console.error( + 'Rollup config returns an array configs. Using the first one for tests' + ); + } + + rollupConfig = rollupConfigResult.options[0]; + break; + } + } + } + + if (rollupConfig) { + let babel = require('@rollup/plugin-babel').default; + rollupConfig.plugins = (rollupConfig.plugins || []).concat([ + babel({ + babelHelpers: 'bundled', + plugins: [require.resolve('babel-plugin-istanbul')], + }), + ]); + + return rollupConfig; + } + + return getDefaultConfig(options); +} + +/** + * @param {Object} karmaConfig + * @param {Object} pkg + * @param {import('./configure').Options} options + */ +export async function addRollupConfig(karmaConfig, pkg, options) { + // From karma-rollup-preprocessor readme: + // Make sure to disable Karma’s file watcher + // because the preprocessor will use its own. + for (let i = 0; i < karmaConfig.files; i++) { + let entry = karmaConfig.files[i]; + if (typeof entry == 'string') { + karmaConfig.files[i] = { pattern: entry, watched: false }; + } else { + karmaConfig.files[i].watched = false; + } + } + + for (let prop of Object.keys(karmaConfig.preprocessors)) { + karmaConfig.preprocessors[prop].unshift('rollup'); + } + + karmaConfig.plugins.push(require.resolve('karma-rollup-preprocessor')); + + karmaConfig.rollupPreprocessor = await getRollupConfig(pkg, options); +} diff --git a/src/webpack.js b/src/webpack.js index dc887fb..d02f69d 100644 --- a/src/webpack.js +++ b/src/webpack.js @@ -1,7 +1,7 @@ import path from 'path'; import delve from 'dlv'; -import { tryRequire, dedupe } from './lib/util'; -import babelLoader from './lib/babel-loader'; +import { res, tryRequire, dedupe } from './lib/util'; +import { babelLoader } from './lib/babel'; import cssLoader from './lib/css-loader'; /** @@ -9,7 +9,14 @@ import cssLoader from './lib/css-loader'; * @returns {boolean} */ export function shouldUseWebpack(options) { - return true; + let shouldUse = true; + try { + require('webpack'); + } catch (error) { + shouldUse = false; + } + + return shouldUse; } /** @@ -23,9 +30,6 @@ export function addWebpackConfig(karmaConfig, pkg, options) { const WEBPACK_CONFIGS = ['webpack.config.babel.js', 'webpack.config.js']; - let cwd = process.cwd(), - res = (file) => path.resolve(cwd, file); - let webpackConfig = options.webpackConfig; if (pkg.scripts) {