diff options
Diffstat (limited to 'preact/karma.conf.js')
-rw-r--r-- | preact/karma.conf.js | 338 |
1 files changed, 338 insertions, 0 deletions
diff --git a/preact/karma.conf.js b/preact/karma.conf.js new file mode 100644 index 0000000..187192a --- /dev/null +++ b/preact/karma.conf.js @@ -0,0 +1,338 @@ +/*eslint no-var:0, object-shorthand:0 */ + +var coverage = String(process.env.COVERAGE) === 'true', + minify = String(process.env.MINIFY) === 'true', + ci = String(process.env.CI).match(/^(1|true)$/gi), + sauceLabs = ci && String(process.env.RUN_SAUCE_LABS) === 'true', + performance = !coverage && String(process.env.PERFORMANCE) !== 'false', + path = require('path'), + errorstacks = require('errorstacks'), + kl = require('kolorist'); + +const babel = require('@babel/core'); +const fs = require('fs').promises; + +// This strips Karma's annoying `LOG: '...'` string from logs +const orgStdoutWrite = process.stdout.write; +process.stdout.write = msg => { + let out = ''; + const match = msg.match( + /(^|.*\s)(LOG|WARN|ERROR):\s'__LOG_CUSTOM:([\S\s]*)'/ + ); + if (match && match.length >= 4) { + // Sometimes the UA of the browser will be included in the message + if (match[1].length) { + out += kl.yellow(kl.italic(match[1])); + out += match[3] + .split('\n') + .map(line => ' ' + line) + .join('\n'); + } else { + out += match[3]; + } + out += '\n'; + } else if (/(^|.*\s)(LOG|WARN|ERROR):\s([\S\s]*)/.test(msg)) { + // Nothing + } else { + out = msg; + } + + return orgStdoutWrite.call(process.stdout, out); +}; + +var sauceLabsLaunchers = { + sl_chrome: { + base: 'SauceLabs', + browserName: 'chrome', + platform: 'Windows 10' + }, + sl_firefox: { + base: 'SauceLabs', + browserName: 'firefox', + platform: 'Windows 10' + }, + // TODO: Safari always fails and disconnects before any tests are executed. + // This seems to be an issue with Saucelabs and they're actively investigating + // that (see: https://mobile.twitter.com/bromann/status/1136323458328084482). + // We'll disable Safari for now until that's resolved. + // sl_safari: { + // base: 'SauceLabs', + // browserName: 'Safari', + // version: '11', + // platform: 'OS X 10.13' + // }, + sl_edge: { + base: 'SauceLabs', + browserName: 'MicrosoftEdge', + platform: 'Windows 10' + }, + sl_ie_11: { + base: 'SauceLabs', + browserName: 'internet explorer', + version: '11.0', + platform: 'Windows 7' + } +}; + +var localLaunchers = { + ChromeNoSandboxHeadless: { + base: 'Chrome', + flags: [ + '--no-sandbox', + '--disable-setuid-sandbox', + // See https://chromium.googlesource.com/chromium/src/+/lkgr/headless/README.md + '--headless', + '--disable-gpu', + '--no-gpu', + // Without a remote debugging port, Google Chrome exits immediately. + '--remote-debugging-port=9333' + ] + } +}; + +const subPkgPath = pkgName => { + if (!minify) { + return path.join(__dirname, pkgName, 'src', 'index.js'); + } + + // Resolve from package.exports field + const stripped = pkgName.replace(/[/\\./]/g, ''); + const pkgJson = path.join(__dirname, 'package.json'); + const pkgExports = require(pkgJson).exports; + const file = pkgExports[stripped ? `./${stripped}` : '.'].browser; + return path.join(__dirname, file); +}; + +// Esbuild plugin for aliasing + babel pass +function createEsbuildPlugin() { + const pending = new Map(); + const cache = new Map(); + + const rename = {}; + const mangle = require('./mangle.json'); + for (let prop in mangle.props.props) { + let name = prop; + if (name[0] === '$') { + name = name.slice(1); + } + + rename[name] = mangle.props.props[prop]; + } + + const alias = { + 'preact/debug': subPkgPath('./debug/'), + 'preact/devtools': subPkgPath('./devtools/'), + 'preact/compat': subPkgPath('./compat/'), + 'preact/hooks': subPkgPath('./hooks/'), + 'preact/test-utils': subPkgPath('./test-utils/'), + 'preact/jsx-runtime': subPkgPath('./jsx-runtime/'), + 'preact/jsx-dev-runtime': subPkgPath('./jsx-runtime/'), + preact: subPkgPath('') + }; + return { + name: 'custom', + setup(build) { + // Aliasing: If "MINIFY" is set to "true" we use the dist/ + // files instead of those from src/ + build.onResolve({ filter: /^preact.*/ }, args => { + const pkg = alias[args.path]; + return { + path: pkg, + namespace: 'preact' + }; + }); + + build.onResolve({ filter: /^(react|react-dom)$/ }, args => { + const pkg = alias['preact/compat']; + return { + path: pkg, + namespace: 'preact' + }; + }); + + // Transpile node_modules that are es2015+ to es5 for IE11 + build.onLoad({ filter: /kolorist/ }, async args => { + const contents = await fs.readFile(args.path, 'utf-8'); + + const tmp = await babel.transformAsync(contents, { + filename: args.path, + presets: [ + [ + '@babel/preset-env', + { + loose: true, + modules: false, + targets: { + browsers: ['last 2 versions', 'IE >= 9'] + } + } + ] + ] + }); + + return { + contents: tmp.code, + resolveDir: path.dirname(args.path), + loader: 'js' + }; + }); + + // Apply babel pass whenever we load a .js file + build.onLoad({ filter: /\.js$/ }, async args => { + const contents = await fs.readFile(args.path, 'utf-8'); + + // Using a cache is crucial as babel is 30x slower than esbuild + const cached = cache.get(args.path); + if (cached && cached.input === contents) { + return { + contents: cached.result, + resolveDir: path.dirname(args.path), + loader: 'js' + }; + } + + let result = contents; + + // Check if somebody already requested the current file. If they + // did than we push a listener instead of doing a duplicate + // transform of the same file. This is crucial for build perf. + if (!pending.has(args.path)) { + pending.set(args.path, []); + + const tmp = await babel.transformAsync(result, { + filename: args.path, + sourceMaps: 'inline', + plugins: [ + coverage && [ + 'istanbul', + { + include: minify ? '**/dist/**/*.js' : '**/src/**/*.js' + } + ] + ].filter(Boolean) + }); + result = tmp.code || result; + cache.set(args.path, { input: contents, result }); + + // Fire all pending listeners that are waiting on the same + // file transformation + const waited = pending.get(args.path); + pending.delete(args.path); + waited.forEach(fn => fn()); + } else { + // Subscribe to the existing transformation completion call + await new Promise(r => { + pending.get(args.path).push(r); + }); + result = cache.get(args.path).result; + } + + return { + contents: result, + resolveDir: path.dirname(args.path), + loader: 'js' + }; + }); + } + }; +} + +module.exports = function(config) { + config.set({ + browsers: sauceLabs + ? Object.keys(sauceLabsLaunchers) + : Object.keys(localLaunchers), + + frameworks: ['mocha', 'chai-sinon'], + + reporters: ['mocha'].concat( + coverage ? 'coverage' : [], + sauceLabs ? 'saucelabs' : [] + ), + + formatError(msg) { + const frames = errorstacks.parseStackTrace(msg); + if (!frames.length || frames[0].column === -1) return '\n' + msg + '\n'; + + const frame = frames[0]; + const filePath = kl.lightCyan( + frame.fileName.replace(__dirname + '/', '') + ); + + const indentMatch = msg.match(/^(\s*)/); + const indent = indentMatch ? indentMatch[1] : ' '; + const location = kl.yellow(`:${frame.line}:${frame.column}`); + return `${indent}at ${frame.name} (${filePath}${location})\n`; + }, + + coverageReporter: { + dir: path.join(__dirname, 'coverage'), + reporters: [ + { type: 'text-summary' }, + { type: 'html' }, + { type: 'lcovonly', subdir: '.', file: 'lcov.info' } + ] + }, + + mochaReporter: { + showDiff: true + }, + + browserLogOptions: { terminal: true }, + browserConsoleLogOptions: { terminal: true }, + + browserNoActivityTimeout: 5 * 60 * 1000, + + // Use only two browsers concurrently, works better with open source Sauce Labs remote testing + concurrency: 2, + + captureTimeout: 0, + + sauceLabs: { + build: `CI #${process.env.GITHUB_RUN_NUMBER} (${process.env.GITHUB_RUN_ID})`, + tunnelIdentifier: + process.env.GITHUB_RUN_NUMBER || + `local${require('./package.json').version}`, + connectLocationForSERelay: 'localhost', + connectPortForSERelay: 4445, + startConnect: !!sauceLabs + }, + + customLaunchers: sauceLabs ? sauceLabsLaunchers : localLaunchers, + + files: [ + { pattern: 'test/polyfills.js', watched: false }, + { + pattern: + config.grep || + '{debug,devtools,hooks,compat,test-utils,jsx-runtime,}/test/{browser,shared}/**/*.test.js', + watched: false, + type: 'js' + } + ], + + mime: { + 'text/javascript': ['js', 'jsx'] + }, + + preprocessors: { + '{debug,devtools,hooks,compat,test-utils,jsx-runtime,}/test/**/*': [ + 'esbuild' + ] + }, + + esbuild: { + // karma-esbuild options + singleBundle: false, + + // esbuild options + target: 'es5', + define: { + COVERAGE: coverage, + 'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV || ''), + ENABLE_PERFORMANCE: performance + }, + plugins: [createEsbuildPlugin()] + } + }); +}; |