summaryrefslogtreecommitdiff
path: root/preact/karma.conf.js
diff options
context:
space:
mode:
Diffstat (limited to 'preact/karma.conf.js')
-rw-r--r--preact/karma.conf.js338
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()]
+ }
+ });
+};