diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 92b3ef3..1fef16d 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -1,6 +1,12 @@ name: CI -on: [push, pull_request] +on: + pull_request: + branches: + - '**' + push: + branches: + - master jobs: build: diff --git a/e2e-test/webpack-custom/babel.config.js b/e2e-test/webpack-custom/babel.config.js new file mode 100644 index 0000000..0d4806e --- /dev/null +++ b/e2e-test/webpack-custom/babel.config.js @@ -0,0 +1,9 @@ +module.exports = (api) => { + api.cache(true); + + return { + plugins: [ + ['@babel/plugin-transform-react-jsx', { pragma: 'createElement' }], + ], + }; +}; diff --git a/e2e-test/webpack-custom/package.json b/e2e-test/webpack-custom/package.json index b181fa6..4116f8c 100644 --- a/e2e-test/webpack-custom/package.json +++ b/e2e-test/webpack-custom/package.json @@ -4,6 +4,7 @@ "private": true, "dependencies": { "@babel/core": "^7.10.3", + "@babel/plugin-transform-react-jsx": "^7.10.3", "babel-loader": "8.1.0", "babel-plugin-transform-rename-properties": "^0.1.0", "karmatic": "file:../..", diff --git a/e2e-test/webpack-custom/src/index.js b/e2e-test/webpack-custom/src/index.js index 0dc0ae1..2e191f2 100644 --- a/e2e-test/webpack-custom/src/index.js +++ b/e2e-test/webpack-custom/src/index.js @@ -1,3 +1,8 @@ -export function box(value) { - return { _value: value }; +// eslint-disable-next-line no-unused-vars +function createElement(tag, props) { + return { tag, props }; +} + +export function render(value1, value2) { + return
; } diff --git a/e2e-test/webpack-custom/test/index.test.js b/e2e-test/webpack-custom/test/index.test.js index e55386a..025e0b6 100644 --- a/e2e-test/webpack-custom/test/index.test.js +++ b/e2e-test/webpack-custom/test/index.test.js @@ -1,9 +1,19 @@ -import { box } from '../src/index'; +import { render } from '../src/index'; -describe('Box', () => { - it('should have a __v property', () => { - const boxed = box(1); - expect('_value' in boxed).toBe(false); - expect(boxed.__v).toBe(1); +describe('Render result', () => { + it('should have a _value1 property', () => { + const result = render(1, 2); + expect('_value1' in result.props).toBe(true); + expect(result.props._value1).toBe(1); + }); + it('should have a __v2 property', () => { + const result = render(1, 2); + expect('_value2' in result.props).toBe(false); + expect(result.props.__v2).toBe(2); + }); + it('should have a __t property', () => { + const result = render(1, 2); + expect('tag' in result).toBe(false); + expect(result.__t).toBe('div'); }); }); diff --git a/e2e-test/webpack-custom/webpack.config.js b/e2e-test/webpack-custom/webpack.config.js index af1fa3a..bfc93f2 100644 --- a/e2e-test/webpack-custom/webpack.config.js +++ b/e2e-test/webpack-custom/webpack.config.js @@ -9,7 +9,7 @@ module.exports = { plugins: [ [ 'babel-plugin-transform-rename-properties', - { rename: { _value: '__v' } }, + { rename: { _value2: '__v2', tag: '__t' } }, ], ], }, diff --git a/scripts/run-e2e-tests.mjs b/scripts/run-e2e-tests.mjs index 7bc6456..e35a960 100644 --- a/scripts/run-e2e-tests.mjs +++ b/scripts/run-e2e-tests.mjs @@ -181,17 +181,22 @@ async function npmInstall(cwd, prefix) { */ async function setupTests(projectPath, prefix) { const name = path.basename(projectPath); - const log = (...msgs) => console.log(`${info(prefix)}`, ...msgs); + const log = (...msgs) => console.log(info(prefix), ...msgs); + const throwError = (...msgs) => { + process.exitCode = 1; + console.error(error(prefix), ...msgs.map((msg) => error(msg))); + throw new Error(msgs[0]); + }; log(`Beginning E2E test at`, projectPath); const pkgJsonPath = path.join(projectPath, 'package.json'); if (!(await fileExists(pkgJsonPath))) { prefix = error(prefix); - console.error( - `${prefix} Could not locate package.json for "${name}". Ensure every e2e test has a package.json defined.` + throwError( + `Could not locate package.json for "${name}".`, + `Ensure every e2e test has a package.json.\n`, + `${prefix} Expected to find one at "${pkgJsonPath}".` ); - console.error(`${prefix} Expected to find one at "${pkgJsonPath}".`); - throw new Error(`Could not locate package.json for "${name}".`); } const pkg = JSON.parse(await fs.readFile(pkgJsonPath, 'utf8')); @@ -228,10 +233,22 @@ async function setupTests(projectPath, prefix) { await onExit(cp); } catch (e) { process.exitCode = 1; - console.error(error(prefix) + ` Test run failed: ${e.message}`); + throwError(`Test run failed: ${e.message}`); } - // TODO: validate coverage/lcov.info is not empty + const coveragePath = path.join(projectPath, 'coverage', 'lcov.info'); + if (!(await fileExists)) { + throwError( + `Code coverage failed to generate results: Results file does not exist at ${coveragePath}` + ); + } + + const coverageInfo = await fs.readFile(coveragePath, { encoding: 'utf8' }); + if (!coverageInfo) { + throwError( + `Code coverage failed to generate results: Results file is empty` + ); + } }; } diff --git a/src/lib/babel-loader.js b/src/lib/babel-loader.js index 028d696..e406413 100644 --- a/src/lib/babel-loader.js +++ b/src/lib/babel-loader.js @@ -1,4 +1,4 @@ -export default function babelLoader(options) { +export function getDefaultBabelLoader(options) { return { test: /\.jsx?$/, exclude: /node_modules/, diff --git a/src/webpack.js b/src/webpack.js index dc887fb..df9de68 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 { getDefaultBabelLoader } from './lib/babel-loader'; import cssLoader from './lib/css-loader'; /** @@ -104,6 +104,32 @@ export function addWebpackConfig(karmaConfig, pkg, options) { return Object.assign({}, configured || {}, value); } + let babelLoader = getLoader((rule) => + `${rule.use},${rule.loader}`.match(/\bbabel-loader\b/) + ); + if (babelLoader) { + if (options.coverage) { + if (babelLoader.loader.query) { + babelLoader.loader.query.plugins = [ + ...(babelLoader.loader.query?.plugins ?? []), + require.resolve('babel-plugin-istanbul'), + ]; + } else { + babelLoader.loader.options = babelLoader.loader.options || {}; + babelLoader.loader.options.plugins = [ + ...(babelLoader.loader.options?.plugins ?? []), + require.resolve('babel-plugin-istanbul'), + ]; + } + } + } else { + loaders.push(getDefaultBabelLoader(options)); + } + + if (!getLoader('foo.css')) { + loaders.push(cssLoader(options)); + } + for (let prop of Object.keys(karmaConfig.preprocessors)) { karmaConfig.preprocessors[prop].unshift('webpack'); } @@ -115,21 +141,7 @@ export function addWebpackConfig(karmaConfig, pkg, options) { // devtool: 'module-source-map', mode: webpackConfig.mode || 'development', module: { - // @TODO check webpack version and use loaders VS rules as the key here appropriately: - // - // TODO: Consider adding coverage as a separate babel-loader so that - // regardless if the user provides their own babel plugins, coverage still - // works - rules: loaders - .concat( - !getLoader((rule) => - `${rule.use},${rule.loader}`.match(/\bbabel-loader\b/) - ) - ? babelLoader(options) - : false, - !getLoader('foo.css') && cssLoader(options) - ) - .filter(Boolean), + rules: loaders, }, resolve: webpackProp('resolve', { modules: webpackProp('resolve.modules', [