From c17dd8c88bdf41c6f61ec28b04015f99a00a5022 Mon Sep 17 00:00:00 2001 From: Juerg B <44573692+juergba@users.noreply.github.com> Date: Fri, 18 Jun 2021 08:08:56 +0200 Subject: [PATCH] Browser: add separate build in ES2018 (#4657) --- .eslintignore | 2 + .eslintrc.yml | 1 + .gitignore | 1 + karma_no-ie11.conf.js | 323 +++++++++++++++++++++++++++++++++++++++ package-scripts.js | 7 +- package.json | 1 + rollup_no-ie11.config.js | 46 ++++++ 7 files changed, 378 insertions(+), 3 deletions(-) create mode 100644 karma_no-ie11.conf.js create mode 100644 rollup_no-ie11.config.js diff --git a/.eslintignore b/.eslintignore index bf70c4ebbe..bbdf158ecf 100644 --- a/.eslintignore +++ b/.eslintignore @@ -1,5 +1,7 @@ coverage/ mocha.js +mocha.js.map +mocha-es2018.js *.fixture.js docs/_site docs/api diff --git a/.eslintrc.yml b/.eslintrc.yml index 760545e0cd..3795fb90af 100644 --- a/.eslintrc.yml +++ b/.eslintrc.yml @@ -37,6 +37,7 @@ overrides: - files: - esm-utils.js - rollup.config.js + - rollup_no-ie11.config.js - scripts/pick-from-package-json.js parserOptions: ecmaVersion: 2018 diff --git a/.gitignore b/.gitignore index a575d78bef..ff084ef74e 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,7 @@ docs/images/supporters docs/api mocha.js mocha.js.map +mocha-es2018.js .karma/ !lib/mocha.js diff --git a/karma_no-ie11.conf.js b/karma_no-ie11.conf.js new file mode 100644 index 0000000000..8dacc48313 --- /dev/null +++ b/karma_no-ie11.conf.js @@ -0,0 +1,323 @@ +/** + * Mocha's Karma config for modern browsers. + * + * Copy of "./karma.config.js" + * + * Changelog: + * - remove IE11 out of SAUCE_BROWSER_PLATFORM_MAP + * - no sourcemap + * - configFile: 'rollup_no-ie11.config.js' + */ + +'use strict'; +const fs = require('fs'); +const path = require('path'); +const os = require('os'); +const rollupPlugin = require('./scripts/karma-rollup-plugin'); +const BASE_BUNDLE_DIR_PATH = path.join(__dirname, '.karma'); +const env = process.env; +const hostname = os.hostname(); + +const SAUCE_BROWSER_PLATFORM_MAP = { + 'chrome@latest': 'Windows 10', + 'MicrosoftEdge@latest': 'Windows 10', + 'firefox@latest': 'Windows 10', + 'safari@latest': 'macOS 10.13' +}; + +const baseConfig = { + frameworks: ['rollup', 'mocha'], + files: [ + // we use the BDD interface for all of the tests that + // aren't interface-specific. + 'test/unit/*.spec.js' + ], + plugins: [ + 'karma-mocha', + 'karma-mocha-reporter', + 'karma-sauce-launcher', + 'karma-chrome-launcher', + rollupPlugin + ], + rollup: { + configFile: 'rollup_no-ie11.config.js', + include: ['test/**'] + }, + reporters: ['mocha'], + colors: true, + browsers: ['ChromeHeadless'], + client: { + mocha: { + // this helps debug + reporter: 'html' + } + }, + mochaReporter: { + showDiff: true + }, + customLaunchers: { + ChromeDebug: { + base: 'Chrome', + flags: ['--remote-debugging-port=9333'] + } + } +}; + +module.exports = config => { + let bundleDirPath = path.join(BASE_BUNDLE_DIR_PATH, hostname); + let cfg = {...baseConfig}; + + // TO RUN AGAINST SAUCELABS LOCALLY, execute: + // `CI=1 SAUCE_USERNAME= SAUCE_ACCESS_KEY= npm start test.browser` + let sauceConfig; + + // configuration for CI mode + if (env.CI) { + console.error('CI mode enabled'); + if (env.GITHUB_RUN_ID) { + console.error('Github Actions detected'); + const buildId = `github-${env.GITHUB_RUN_ID}_${env.GITHUB_RUN_NUMBER}`; + bundleDirPath = path.join(BASE_BUNDLE_DIR_PATH, buildId); + sauceConfig = { + build: buildId + }; + } else { + console.error(`Local environment (${hostname}) detected`); + // don't need to run sauce from Windows CI b/c travis does it. + if (env.SAUCE_USERNAME || env.SAUCE_ACCESS_KEY) { + const id = `${hostname} (${Date.now()})`; + sauceConfig = { + build: id, + tunnelIdentifier: id + }; + console.error('Configured SauceLabs'); + } else { + console.error( + 'No SauceLabs credentials present; set SAUCE_USERNAME and SAUCE_ACCESS_KEY env vars' + ); + } + } + } + + cfg = createBundleDir(cfg, bundleDirPath); + cfg = addSauceTests(cfg, sauceConfig); + cfg = chooseTestSuite(cfg, env.MOCHA_TEST); + + config.set(cfg); +}; + +/** + * Creates dir `bundleDirPath` if it does not exist; returns new Karma config + * containing `bundleDirPath` for rollup plugin. + * + * If this fails, the rollup plugin will use a temp dir. + * @param {object} cfg - Karma config. + * @param {string} [bundleDirPath] - Path where the output bundle should live + * @returns {object} - New Karma config + */ +const createBundleDir = (cfg, bundleDirPath) => { + if (bundleDirPath) { + try { + fs.mkdirSync(bundleDirPath, {recursive: true}); + cfg = { + ...cfg, + rollup: { + ...cfg.rollup, + bundleDirPath + } + }; + } catch (ignored) { + console.error( + `Failed to create ${bundleDirPath}; using temp directory instead` + ); + } + } + return {...cfg}; +}; + +/** + * Adds Saucelabs-specific config to a Karma config object. + * + * If `sauceLabs` parameter is falsy, just return a clone of the `cfg` parameter. + * + * @see https://github.com/karma-runner/karma-sauce-launcher + * @see https://github.com/bermi/sauce-connect-launcher#advanced-usage + * @param {object} cfg - Karma config + * @param {object} [sauceLabs] - SauceLabs config + * @returns {object} Karma config + */ +const addSauceTests = (cfg, sauceLabs) => { + if (sauceLabs) { + const sauceBrowsers = Object.keys(SAUCE_BROWSER_PLATFORM_MAP); + + // creates Karma `customLauncher` configs from `SAUCE_BROWSER_PLATFORM_MAP` + const customLaunchers = sauceBrowsers.reduce((acc, sauceBrowser) => { + const platformName = SAUCE_BROWSER_PLATFORM_MAP[sauceBrowser]; + const [browserName, browserVersion] = sauceBrowser.split('@'); + return { + ...acc, + [sauceBrowser]: { + base: 'SauceLabs', + browserName, + browserVersion, + platformName, + 'sauce:options': sauceLabs + } + }; + }, {}); + + return { + ...cfg, + reporters: [...cfg.reporters, 'saucelabs'], + browsers: [...cfg.browsers, ...sauceBrowsers], + customLaunchers: { + ...cfg.customLaunchers, + ...customLaunchers + }, + sauceLabs, + concurrency: Infinity, + retryLimit: 1, + captureTimeout: 120000, + browserNoActivityTimeout: 20000 + }; + } + return {...cfg}; +}; + +/** + * Returns a new Karma config containing standard dependencies for our tests. + * + * Most suites use this. + * @param {object} cfg - Karma config + * @returns {object} New Karma config + */ +const addStandardDependencies = cfg => ({ + ...cfg, + files: [ + require.resolve('sinon/pkg/sinon.js'), + require.resolve('unexpected/unexpected'), + { + pattern: require.resolve('unexpected/unexpected.js.map'), + included: false + }, + require.resolve('unexpected-sinon'), + require.resolve('unexpected-eventemitter/dist/unexpected-eventemitter.js'), + require.resolve('./test/browser-specific/setup'), + ...cfg.files + ], + rollup: { + ...cfg.rollup, + external: [ + 'sinon', + 'unexpected', + 'unexpected-eventemitter', + 'unexpected-sinon' + ], + globals: { + sinon: 'sinon', + unexpected: 'weknowhow.expect', + 'unexpected-sinon': 'weknowhow.unexpectedSinon', + 'unexpected-eventemitter': 'unexpectedEventEmitter' + } + } +}); + +/** + * Adds a name for the tests, reflected in SauceLabs' UI. Returns new Karma + * config. + * + * Does not add a test name if the `sauceLabs` prop of `cfg` is falsy (which + * would imply that we're not running tests on SauceLabs). + * + * @param {string} testName - SauceLabs test name + * @param {object} cfg - Karma config. + * @returns {object} New Karma config + */ +const addSauceLabsTestName = (testName, cfg) => + cfg.sauceLabs + ? { + ...cfg, + sauceLabs: { + ...cfg.sauceLabs, + testName + } + } + : {...cfg}; + +/** + * Returns a new Karma config to run with specific configuration (which cannot + * be run with other configurations) as specified by `value`. Known values: + * + * - `bdd` - `bdd`-specific tests + * - `tdd` - `tdd`-specific tests + * - `qunit` - `qunit`-specific tests + * - `esm` - ESM-specific tests + * - `requirejs` - RequireJS-specific tests + * + * Since we can't change Mocha's interface on-the-fly, tests for specific interfaces + * must be run in isolation. + * @param {object} cfg - Karma config + * @param {string} [value] - Configuration identifier, if any + * @returns {object} New Karma config + */ +const chooseTestSuite = (cfg, value) => { + switch (value) { + case 'bdd': + case 'tdd': + case 'qunit': + return addStandardDependencies({ + ...cfg, + ...addSauceLabsTestName(`Interface "${value}" Integration Tests`, cfg), + files: [`test/interfaces/${value}.spec.js`], + client: { + ...cfg.client, + mocha: { + ...cfg.client.mocha, + ui: value + } + } + }); + case 'esm': + return addStandardDependencies({ + ...addSauceLabsTestName('ESM Integration Tests', cfg), + // just run against ChromeHeadless, since other browsers may not + // support ESM. + // XXX: remove following line when dropping IE11 + browsers: ['ChromeHeadless'], + files: [ + { + pattern: 'test/browser-specific/fixtures/esm.fixture.mjs', + type: 'module' + }, + { + pattern: 'test/browser-specific/esm.spec.mjs', + type: 'module' + } + ] + }); + case 'requirejs': + // no standard deps because I'm too lazy to figure out how to make + // them work with RequireJS. not important anyway + return { + ...addSauceLabsTestName('RequireJS Tests', cfg), + plugins: [...cfg.plugins, 'karma-requirejs'], + frameworks: ['requirejs', ...cfg.frameworks], + files: [ + { + pattern: 'test/browser-specific/fixtures/requirejs/*.fixture.js', + included: false + }, + 'test/browser-specific/requirejs-setup.js' + ], + // this skips bundling the above tests & fixtures + rollup: { + ...cfg.rollup, + include: [] + } + }; + default: + return addStandardDependencies({ + ...addSauceLabsTestName('Unit Tests', cfg) + }); + } +}; diff --git a/package-scripts.js b/package-scripts.js index 213be777c0..425e95df3e 100644 --- a/package-scripts.js +++ b/package-scripts.js @@ -37,7 +37,7 @@ function test(testName, mochaParams) { module.exports = { scripts: { build: { - script: `rollup -c`, + script: `rollup -c ./rollup.config.js && rollup -c ./rollup_no-ie11.config.js`, description: 'Build browser bundle' }, lint: { @@ -73,7 +73,7 @@ module.exports = { } }, clean: { - script: 'rimraf mocha.js', + script: 'rimraf mocha.js mocha.js.map mocha-es2018.js', description: 'Clean browser bundle' }, test: { @@ -210,7 +210,8 @@ module.exports = { description: 'Run browser tests' }, unit: { - script: 'cross-env NODE_PATH=. karma start --single-run --colors', + script: + 'cross-env NODE_PATH=. karma start karma.conf.js --single-run --colors && cross-env NODE_PATH=. karma start karma_no-ie11.conf.js --single-run --colors', description: 'Run browser unit tests' }, bdd: { diff --git a/package.json b/package.json index 86d052f777..73ad70f8a4 100644 --- a/package.json +++ b/package.json @@ -173,6 +173,7 @@ "mocha.css", "mocha.js", "mocha.js.map", + "mocha-es2018.js", "browser-entry.js" ], "browser": { diff --git a/rollup_no-ie11.config.js b/rollup_no-ie11.config.js new file mode 100644 index 0000000000..117c506b69 --- /dev/null +++ b/rollup_no-ie11.config.js @@ -0,0 +1,46 @@ +import commonjs from '@rollup/plugin-commonjs'; +import nodeResolve from '@rollup/plugin-node-resolve'; +import json from '@rollup/plugin-json'; +import nodePolyfills from 'rollup-plugin-node-polyfills'; +import globals from 'rollup-plugin-node-globals'; + +// Debugging tools +import visualizer from 'rollup-plugin-visualizer'; + +import pickFromPackageJson from './scripts/pick-from-package-json'; +import {version} from './package.json'; + +const config = { + input: './browser-entry.js', + output: { + file: './mocha-es2018.js', + format: 'umd', + sourcemap: false, + name: 'mocha', + banner: `// mocha@${version} in javascript ES2018` + }, + plugins: [ + json(), + pickFromPackageJson({ + keys: ['name', 'version', 'homepage', 'notifyLogo'] + }), + commonjs(), + globals(), + nodePolyfills(), + nodeResolve({ + browser: true + }) + ], + onwarn: (warning, warn) => { + if (warning.code === 'CIRCULAR_DEPENDENCY') return; + + // Use default for everything else + warn(warning); + } +}; + +if (!process.env.CI) { + config.plugins.push(visualizer()); +} + +export default config;