diff --git a/ava.config.js b/ava.config.js index 402ed8c71..969924dd0 100644 --- a/ava.config.js +++ b/ava.config.js @@ -1,6 +1,7 @@ const skipTests = []; if (process.versions.node < '12.17.0') { skipTests.push( + '!test/config/next-gen.js', '!test/configurable-module-format/module.js', '!test/shared-workers/!(requires-newish-node)/**' ); diff --git a/docs/06-configuration.md b/docs/06-configuration.md index 75d9f1c08..05474a454 100644 --- a/docs/06-configuration.md +++ b/docs/06-configuration.md @@ -73,11 +73,13 @@ To use these files: 2. Your `package.json` must not contain an `ava` property (or, if it does, it must be an empty object) 3. You must not both have an `ava.config.js` *and* an `ava.config.cjs` file -AVA recognizes `ava.config.mjs` files but refuses to load them. +AVA 3 recognizes `ava.config.mjs` files but refuses to load them. This is changing in AVA 4, [see below](#next-generation-configuration). ### `ava.config.js` -For `ava.config.js` files you must use `export default`. You cannot use ["module scope"](https://nodejs.org/docs/latest-v12.x/api/modules.html#modules_the_module_scope). You cannot import dependencies. +In AVA 3, for `ava.config.js` files you must use `export default`. You cannot use ["module scope"](https://nodejs.org/docs/latest-v12.x/api/modules.html#modules_the_module_scope). You cannot import dependencies. + +This is changing in AVA 4, [see below](#next-generation-configuration). The default export can either be a plain object or a factory function which returns a plain object: @@ -111,7 +113,7 @@ export default ({projectDir}) => { }; ``` -Note that the final configuration must not be a promise. +Note that the final configuration must not be a promise. This is changing in AVA 4, [see below](#next-generation-configuration). ### `ava.config.cjs` @@ -149,12 +151,14 @@ module.exports = ({projectDir}) => { }; ``` -Note that the final configuration must not be a promise. +Note that the final configuration must not be a promise. This is changing in AVA 4, [see below](#next-generation-configuration). ## Alternative configuration files The [CLI] lets you specify a specific configuration file, using the `--config` flag. This file must have either a `.js` or `.cjs` extension and is processed like an `ava.config.js` or `ava.config.cjs` file would be. +AVA 4 also supports `.mjs` extensions, [see below](#next-generation-configuration). + When the `--config` flag is set, the provided file will override all configuration from the `package.json` and `ava.config.js` or `ava.config.cjs` files. The configuration is not merged. The configuration file *must* be in the same directory as the `package.json` file. @@ -182,6 +186,25 @@ module.exports = { You can now run your unit tests through `npx ava` and the integration tests through `npx ava --config integration-tests.config.cjs`. +## Next generation configuration + +AVA 4 will add full support for ESM configuration files as well as allowing you to have asynchronous factory functions. If you're using Node.js 12 or later you can opt-in to these features in AVA 3 by enabling the `nextGenConfig` experiment. Say in an `ava.config.mjs` file: + +```js +export default { + nonSemVerExperiments: { + nextGenConfig: true + }, + files: ['unit-tests/**/*] +}; +``` + +This also allows you to pass an `.mjs` file using the `--config` argument. + +With this experiment enabled, AVA will no longer have special treatment for `ava.config.js` files. Instead AVA follows Node.js' behavior, so if you've set [`"type": "module"`](https://nodejs.org/docs/latest/api/packages.html#packages_type) you must use ESM, and otherwise you must use CommonJS. + +You mustn't have an `ava.config.mjs` file next to an `ava.config.js` or `ava.config.cjs` file. + ## Object printing depth By default, AVA prints nested objects to a depth of `3`. However, when debugging tests with deeply nested objects, it can be useful to print with more detail. This can be done by setting [`util.inspect.defaultOptions.depth`](https://nodejs.org/api/util.html#util_util_inspect_defaultoptions) to the desired depth, before the test is executed: diff --git a/eslint-plugin-helper.js b/eslint-plugin-helper.js index b327e25b0..b8e4a71ec 100644 --- a/eslint-plugin-helper.js +++ b/eslint-plugin-helper.js @@ -1,26 +1,25 @@ 'use strict'; -const normalizeExtensions = require('./lib/extensions'); +let isMainThread = true; +let supportsWorkers = false; +try { + ({isMainThread} = require('worker_threads')); + supportsWorkers = true; +} catch {} + const {classify, hasExtension, isHelperish, matches, normalizeFileForMatching, normalizeGlobs, normalizePatterns} = require('./lib/globs'); -const loadConfig = require('./lib/load-config'); -const providerManager = require('./lib/provider-manager'); -const configCache = new Map(); -const helperCache = new Map(); +let resolveGlobs; +let resolveGlobsSync; -function load(projectDir, overrides) { - const cacheKey = `${JSON.stringify(overrides)}\n${projectDir}`; - if (helperCache.has(cacheKey)) { - return helperCache.get(cacheKey); - } +if (!supportsWorkers || !isMainThread) { + const normalizeExtensions = require('./lib/extensions'); + const {loadConfig, loadConfigSync} = require('./lib/load-config'); + const providerManager = require('./lib/provider-manager'); - let conf; - let providers; - if (configCache.has(projectDir)) { - ({conf, providers} = configCache.get(projectDir)); - } else { - conf = loadConfig({resolveFrom: projectDir}); + const configCache = new Map(); - providers = []; + const collectProviders = ({conf, projectDir}) => { + const providers = []; if (Reflect.has(conf, 'babel')) { const {level, main} = providerManager.babel(projectDir); providers.push({ @@ -39,12 +38,125 @@ function load(projectDir, overrides) { }); } - configCache.set(projectDir, {conf, providers}); + return providers; + }; + + const buildGlobs = ({conf, providers, projectDir, overrideExtensions, overrideFiles}) => { + const extensions = overrideExtensions ? + normalizeExtensions(overrideExtensions) : + normalizeExtensions(conf.extensions, providers); + + return { + cwd: projectDir, + ...normalizeGlobs({ + extensions, + files: overrideFiles ? overrideFiles : conf.files, + providers + }) + }; + }; + + resolveGlobsSync = (projectDir, overrideExtensions, overrideFiles) => { + if (!configCache.has(projectDir)) { + const conf = loadConfigSync({resolveFrom: projectDir}); + const providers = collectProviders({conf, projectDir}); + configCache.set(projectDir, {conf, providers}); + } + + const {conf, providers} = configCache.get(projectDir); + return buildGlobs({conf, providers, projectDir, overrideExtensions, overrideFiles}); + }; + + resolveGlobs = async (projectDir, overrideExtensions, overrideFiles) => { + if (!configCache.has(projectDir)) { + configCache.set(projectDir, loadConfig({resolveFrom: projectDir}).then(conf => { // eslint-disable-line promise/prefer-await-to-then + const providers = collectProviders({conf, projectDir}); + return {conf, providers}; + })); + } + + const {conf, providers} = await configCache.get(projectDir); + return buildGlobs({conf, providers, projectDir, overrideExtensions, overrideFiles}); + }; +} + +if (supportsWorkers) { + const v8 = require('v8'); + + const MAX_DATA_LENGTH_EXCLUSIVE = 100 * 1024; // Allocate 100 KiB to exchange globs. + + if (isMainThread) { + const {Worker} = require('worker_threads'); + let data; + let sync; + let worker; + + resolveGlobsSync = (projectDir, overrideExtensions, overrideFiles) => { + if (worker === undefined) { + const dataBuffer = new SharedArrayBuffer(MAX_DATA_LENGTH_EXCLUSIVE); + data = new Uint8Array(dataBuffer); + + const syncBuffer = new SharedArrayBuffer(4); + sync = new Int32Array(syncBuffer); + + worker = new Worker(__filename, { + workerData: { + dataBuffer, + syncBuffer, + firstMessage: {projectDir, overrideExtensions, overrideFiles} + } + }); + worker.unref(); + } else { + worker.postMessage({projectDir, overrideExtensions, overrideFiles}); + } + + Atomics.wait(sync, 0, 0); + + const byteLength = Atomics.exchange(sync, 0, 0); + if (byteLength === MAX_DATA_LENGTH_EXCLUSIVE) { + throw new Error('Globs are over 100 KiB and cannot be resolved'); + } + + const globsOrError = v8.deserialize(data.slice(0, byteLength)); + if (globsOrError instanceof Error) { + throw globsOrError; + } + + return globsOrError; + }; + } else { + const {parentPort, workerData} = require('worker_threads'); + const data = new Uint8Array(workerData.dataBuffer); + const sync = new Int32Array(workerData.syncBuffer); + + const handleMessage = async ({projectDir, overrideExtensions, overrideFiles}) => { + let encoded; + try { + const globs = await resolveGlobs(projectDir, overrideExtensions, overrideFiles); + encoded = v8.serialize(globs); + } catch (error) { + encoded = v8.serialize(error); + } + + const byteLength = encoded.length < MAX_DATA_LENGTH_EXCLUSIVE ? encoded.copy(data) : MAX_DATA_LENGTH_EXCLUSIVE; + Atomics.store(sync, 0, byteLength); + Atomics.notify(sync, 0); + }; + + parentPort.on('message', handleMessage); + handleMessage(workerData.firstMessage); + delete workerData.firstMessage; } +} + +const helperCache = new Map(); - const extensions = overrides && overrides.extensions ? - normalizeExtensions(overrides.extensions) : - normalizeExtensions(conf.extensions, providers); +function load(projectDir, overrides) { + const cacheKey = `${JSON.stringify(overrides)}\n${projectDir}`; + if (helperCache.has(cacheKey)) { + return helperCache.get(cacheKey); + } let helperPatterns = []; if (overrides && overrides.helpers !== undefined) { @@ -55,14 +167,7 @@ function load(projectDir, overrides) { helperPatterns = normalizePatterns(overrides.helpers); } - const globs = { - cwd: projectDir, - ...normalizeGlobs({ - extensions, - files: overrides && overrides.files ? overrides.files : conf.files, - providers - }) - }; + const globs = resolveGlobsSync(projectDir, overrides && overrides.extensions, overrides && overrides.files); const classifyForESLint = file => { const {isTest} = classify(file, globs); diff --git a/lib/cli.js b/lib/cli.js index f98c713bb..e591a6643 100644 --- a/lib/cli.js +++ b/lib/cli.js @@ -7,7 +7,7 @@ const arrify = require('arrify'); const yargs = require('yargs'); const readPkg = require('read-pkg'); const isCi = require('./is-ci'); -const loadConfig = require('./load-config'); +const {loadConfig} = require('./load-config'); function exit(message) { console.error(`\n ${require('./chalk').get().red(figures.cross)} ${message}`); @@ -83,7 +83,7 @@ exports.run = async () => { // eslint-disable-line complexity let confError = null; try { const {argv: {config: configFile}} = yargs.help(false); - conf = loadConfig({configFile}); + conf = await loadConfig({configFile}); } catch (error) { confError = error; } diff --git a/lib/load-config.js b/lib/load-config.js index d8076d4d2..198a74207 100644 --- a/lib/load-config.js +++ b/lib/load-config.js @@ -1,6 +1,7 @@ 'use strict'; const fs = require('fs'); const path = require('path'); +const url = require('url'); const vm = require('vm'); const {isPlainObject} = require('is-plain-object'); const pkgConf = require('pkg-conf'); @@ -11,23 +12,37 @@ const EXPERIMENTS = new Set([ 'configurableModuleFormat', 'disableNullExpectations', 'disableSnapshotsInHooks', + 'nextGenConfig', 'reverseTeardowns', 'sharedWorkers' ]); // *Very* rudimentary support for loading ava.config.js files containing an `export default` statement. -const evaluateJsConfig = configFile => { - const contents = fs.readFileSync(configFile, 'utf8'); - const script = new vm.Script(`'use strict';(()=>{let __export__;\n${contents.replace(/export default/g, '__export__ =')};return __export__;})()`, { +const evaluateJsConfig = (contents, configFile) => { + const script = new vm.Script(`'use strict';(()=>{let __export__;\n${contents.toString('utf8').replace(/export default/g, '__export__ =')};return __export__;})()`, { filename: configFile, lineOffset: -1 }); - return { - default: script.runInThisContext() - }; + return script.runInThisContext(); }; -const loadJsConfig = ({projectDir, configFile = path.join(projectDir, 'ava.config.js')}) => { +const importConfig = async ({configFile, fileForErrorMessage}) => { + let module; + try { + module = await import(url.pathToFileURL(configFile)); // eslint-disable-line node/no-unsupported-features/es-syntax + } catch (error) { + throw Object.assign(new Error(`Error loading ${fileForErrorMessage}: ${error.message}`), {parent: error}); + } + + const {default: config = MISSING_DEFAULT_EXPORT} = module; + if (config === MISSING_DEFAULT_EXPORT) { + throw new Error(`${fileForErrorMessage} must have a default export`); + } + + return config; +}; + +const loadJsConfig = ({projectDir, configFile = path.join(projectDir, 'ava.config.js')}, useImport = false) => { if (!configFile.endsWith('.js')) { return null; } @@ -36,7 +51,10 @@ const loadJsConfig = ({projectDir, configFile = path.join(projectDir, 'ava.confi let config; try { - ({default: config = MISSING_DEFAULT_EXPORT} = evaluateJsConfig(configFile)); + const contents = fs.readFileSync(configFile); + config = useImport && contents.includes('nonSemVerExperiments') && contents.includes('nextGenConfig') ? + importConfig({configFile, fileForErrorMessage}) : + evaluateJsConfig(contents, configFile) || MISSING_DEFAULT_EXPORT; } catch (error) { if (error.code === 'ENOENT') { return null; @@ -69,14 +87,17 @@ const loadCjsConfig = ({projectDir, configFile = path.join(projectDir, 'ava.conf } }; -const loadMjsConfig = ({projectDir, configFile = path.join(projectDir, 'ava.config.mjs')}) => { +const loadMjsConfig = ({projectDir, configFile = path.join(projectDir, 'ava.config.mjs')}, experimentally = false) => { if (!configFile.endsWith('.mjs')) { return null; } const fileForErrorMessage = path.relative(projectDir, configFile); try { - fs.readFileSync(configFile); + const contents = fs.readFileSync(configFile); + if (experimentally && contents.includes('nonSemVerExperiments') && contents.includes('nextGenConfig')) { + return {config: importConfig({configFile, fileForErrorMessage}), fileForErrorMessage}; + } } catch (error) { if (error.code === 'ENOENT') { return null; @@ -88,11 +109,7 @@ const loadMjsConfig = ({projectDir, configFile = path.join(projectDir, 'ava.conf throw new Error(`AVA cannot yet load ${fileForErrorMessage} files`); }; -function loadConfig({configFile, resolveFrom = process.cwd(), defaults = {}} = {}) { // eslint-disable-line complexity - let packageConf = pkgConf.sync('ava', {cwd: resolveFrom}); - const filepath = pkgConf.filepath(packageConf); - const projectDir = filepath === null ? resolveFrom : path.dirname(filepath); - +function resolveConfigFile(projectDir, configFile) { if (configFile) { configFile = path.resolve(configFile); // Relative to CWD if (path.basename(configFile) !== path.relative(projectDir, configFile)) { @@ -104,6 +121,15 @@ function loadConfig({configFile, resolveFrom = process.cwd(), defaults = {}} = { } } + return configFile; +} + +function loadConfigSync({configFile, resolveFrom = process.cwd(), defaults = {}} = {}) { + let packageConf = pkgConf.sync('ava', {cwd: resolveFrom}); + const filepath = pkgConf.filepath(packageConf); + const projectDir = filepath === null ? resolveFrom : path.dirname(filepath); + + configFile = resolveConfigFile(projectDir, configFile); const allowConflictWithPackageJson = Boolean(configFile); let [{config: fileConf, fileForErrorMessage} = {config: NO_SUCH_FILE, fileForErrorMessage: undefined}, ...conflicting] = [ @@ -163,4 +189,79 @@ function loadConfig({configFile, resolveFrom = process.cwd(), defaults = {}} = { return config; } -module.exports = loadConfig; +exports.loadConfigSync = loadConfigSync; + +async function loadConfig({configFile, resolveFrom = process.cwd(), defaults = {}} = {}) { + let packageConf = await pkgConf('ava', {cwd: resolveFrom}); + const filepath = pkgConf.filepath(packageConf); + const projectDir = filepath === null ? resolveFrom : path.dirname(filepath); + + configFile = resolveConfigFile(projectDir, configFile); + const allowConflictWithPackageJson = Boolean(configFile); + + // TODO: Refactor resolution logic to implement https://github.com/avajs/ava/issues/2285. + let [{config: fileConf, fileForErrorMessage} = {config: NO_SUCH_FILE, fileForErrorMessage: undefined}, ...conflicting] = [ + loadJsConfig({projectDir, configFile}, true), + loadCjsConfig({projectDir, configFile}), + loadMjsConfig({projectDir, configFile}, true) + ].filter(result => result !== null); + + if (conflicting.length > 0) { + throw new Error(`Conflicting configuration in ${fileForErrorMessage} and ${conflicting.map(({fileForErrorMessage}) => fileForErrorMessage).join(' & ')}`); + } + + let sawPromise = false; + if (fileConf !== NO_SUCH_FILE) { + if (allowConflictWithPackageJson) { + packageConf = {}; + } else if (Object.keys(packageConf).length > 0) { + throw new Error(`Conflicting configuration in ${fileForErrorMessage} and package.json`); + } + + if (fileConf && typeof fileConf.then === 'function') { // eslint-disable-line promise/prefer-await-to-then + sawPromise = true; + fileConf = await fileConf; + } + + if (!isPlainObject(fileConf) && typeof fileConf !== 'function') { + throw new TypeError(`${fileForErrorMessage} must export a plain object or factory function`); + } + + if (typeof fileConf === 'function') { + fileConf = fileConf({projectDir}); + if (fileConf && typeof fileConf.then === 'function') { // eslint-disable-line promise/prefer-await-to-then + sawPromise = true; + fileConf = await fileConf; + } + + if (!isPlainObject(fileConf)) { + throw new TypeError(`Factory method exported by ${fileForErrorMessage} must return a plain object`); + } + } + + if ('ava' in fileConf) { + throw new Error(`Encountered ’ava’ property in ${fileForErrorMessage}; avoid wrapping the configuration`); + } + } + + const config = {...defaults, nonSemVerExperiments: {}, ...fileConf, ...packageConf, projectDir}; + + const {nonSemVerExperiments: experiments} = config; + if (!isPlainObject(experiments)) { + throw new Error(`nonSemVerExperiments from ${fileForErrorMessage} must be an object`); + } + + for (const key of Object.keys(experiments)) { + if (!EXPERIMENTS.has(key)) { + throw new Error(`nonSemVerExperiments.${key} from ${fileForErrorMessage} is not a supported experiment`); + } + } + + if (sawPromise && experiments.nextGenConfig !== true) { + throw new Error(`${fileForErrorMessage} exported a promise or an asynchronous factory function. You must enable the ’asyncConfigurationLoading’ experiment for this to work.`); + } + + return config; +} + +exports.loadConfig = loadConfig; diff --git a/test-tap/load-config.js b/test-tap/load-config.js index bfa48d064..0d1f21873 100644 --- a/test-tap/load-config.js +++ b/test-tap/load-config.js @@ -1,7 +1,7 @@ 'use strict'; const path = require('path'); const tap = require('tap'); -const loadConfig = require('../lib/load-config'); +const {loadConfigSync} = require('../lib/load-config'); const {test} = tap; @@ -16,40 +16,40 @@ const changeDir = fixtureDir => { test('finds config in package.json', t => { changeDir('package-only'); - const conf = loadConfig(); + const conf = loadConfigSync(); t.is(conf.failFast, true); t.end(); }); test('loads config from a particular directory', t => { changeDir('throws'); - const conf = loadConfig({resolveFrom: path.resolve(__dirname, 'fixture', 'load-config', 'package-only')}); + const conf = loadConfigSync({resolveFrom: path.resolve(__dirname, 'fixture', 'load-config', 'package-only')}); t.is(conf.failFast, true); t.end(); }); test('throws an error if both configs are present', t => { changeDir('package-yes-file-yes'); - t.throws(loadConfig, /Conflicting configuration in ava.config.js and package.json/); + t.throws(loadConfigSync, /Conflicting configuration in ava.config.js and package.json/); t.end(); }); test('explicit configFile option overrides package.json config', t => { changeDir('package-yes-explicit-yes'); - const {files} = loadConfig({configFile: 'explicit.js'}); + const {files} = loadConfigSync({configFile: 'explicit.js'}); t.is(files, 'package-yes-explicit-yes-test-value'); t.end(); }); test('throws if configFile option is not in the same directory as the package.json file', t => { changeDir('package-yes-explicit-yes'); - t.throws(() => loadConfig({configFile: 'nested/explicit.js'}), /Config files must be located next to the package.json file/); + t.throws(() => loadConfigSync({configFile: 'nested/explicit.js'}), /Config files must be located next to the package.json file/); t.end(); }); test('throws if configFile option has an unsupported extension', t => { changeDir('explicit-bad-extension'); - t.throws(() => loadConfig({configFile: 'explicit.txt'}), /Config files must have .js, .cjs or .mjs extensions/); + t.throws(() => loadConfigSync({configFile: 'explicit.txt'}), /Config files must have .js, .cjs or .mjs extensions/); t.end(); }); @@ -58,7 +58,7 @@ test('merges in defaults passed with initial call', t => { const defaults = { files: ['123', '!456'] }; - const {files, failFast} = loadConfig({defaults}); + const {files, failFast} = loadConfigSync({defaults}); t.is(failFast, true, 'preserves original props'); t.is(files, defaults.files, 'merges in extra props'); t.end(); @@ -66,51 +66,51 @@ test('merges in defaults passed with initial call', t => { test('loads config from file with `export default` syntax', t => { changeDir('package-no-file-yes'); - const conf = loadConfig(); + const conf = loadConfigSync(); t.is(conf.files, 'config-file-esm-test-value'); t.end(); }); test('loads config from factory function', t => { changeDir('package-no-file-yes-factory'); - const conf = loadConfig(); + const conf = loadConfigSync(); t.ok(conf.files.startsWith(__dirname)); t.end(); }); test('does not support require() inside config.js files', t => { changeDir('require'); - t.throws(loadConfig, /Error loading ava\.config\.js: require is not defined/); + t.throws(loadConfigSync, /Error loading ava\.config\.js: require is not defined/); t.end(); }); test('throws an error if a config factory returns a promise', t => { changeDir('factory-no-promise-return'); - t.throws(loadConfig, /Factory method exported by ava.config.js must not return a promise/); + t.throws(loadConfigSync, /Factory method exported by ava.config.js must not return a promise/); t.end(); }); test('throws an error if a config exports a promise', t => { changeDir('no-promise-config'); - t.throws(loadConfig, /ava.config.js must not export a promise/); + t.throws(loadConfigSync, /ava.config.js must not export a promise/); t.end(); }); test('throws an error if a config factory does not return a plain object', t => { changeDir('factory-no-plain-return'); - t.throws(loadConfig, /Factory method exported by ava.config.js must return a plain object/); + t.throws(loadConfigSync, /Factory method exported by ava.config.js must return a plain object/); t.end(); }); test('throws an error if a config does not export a plain object', t => { changeDir('no-plain-config'); - t.throws(loadConfig, /ava.config.js must export a plain object or factory function/); + t.throws(loadConfigSync, /ava.config.js must export a plain object or factory function/); t.end(); }); test('receives a `projectDir` property', t => { changeDir('package-only'); - const conf = loadConfig(); + const conf = loadConfigSync(); t.ok(conf.projectDir.startsWith(__dirname)); t.end(); }); @@ -119,7 +119,7 @@ test('rethrows wrapped module errors', t => { t.plan(1); changeDir('throws'); try { - loadConfig(); + loadConfigSync(); } catch (error) { t.is(error.parent.message, 'foo'); } @@ -127,49 +127,49 @@ test('rethrows wrapped module errors', t => { test('throws an error if a .js config file has no default export', t => { changeDir('no-default-export'); - t.throws(loadConfig, /ava.config.js must have a default export, using ES module syntax/); + t.throws(loadConfigSync, /ava.config.js must have a default export, using ES module syntax/); t.end(); }); test('throws an error if a config file contains `ava` property', t => { changeDir('contains-ava-property'); - t.throws(loadConfig, /Encountered ’ava’ property in ava.config.js; avoid wrapping the configuration/); + t.throws(loadConfigSync, /Encountered ’ava’ property in ava.config.js; avoid wrapping the configuration/); t.end(); }); test('throws an error if a config file contains a non-object `nonSemVerExperiments` property', t => { changeDir('non-object-experiments'); - t.throws(loadConfig, /nonSemVerExperiments from ava.config.js must be an object/); + t.throws(loadConfigSync, /nonSemVerExperiments from ava.config.js must be an object/); t.end(); }); test('throws an error if a config file enables an unsupported experiment', t => { changeDir('unsupported-experiments'); - t.throws(loadConfig, /nonSemVerExperiments.unsupported from ava.config.js is not a supported experiment/); + t.throws(loadConfigSync, /nonSemVerExperiments.unsupported from ava.config.js is not a supported experiment/); t.end(); }); test('loads .cjs config', t => { changeDir('cjs'); - const conf = loadConfig(); + const conf = loadConfigSync(); t.ok(conf.files.startsWith(__dirname)); t.end(); }); test('throws an error if both .js and .cjs configs are present', t => { changeDir('file-yes-cjs-yes'); - t.throws(loadConfig, /Conflicting configuration in ava.config.js and ava.config.cjs/); + t.throws(loadConfigSync, /Conflicting configuration in ava.config.js and ava.config.cjs/); t.end(); }); test('refuses to load .mjs config', t => { changeDir('mjs'); - t.throws(loadConfig, /AVA cannot yet load ava.config.mjs files/); + t.throws(loadConfigSync, /AVA cannot yet load ava.config.mjs files/); t.end(); }); test('throws an error if .js, .cjs and .mjs configs are present', t => { changeDir('file-yes-cjs-yes'); - t.throws(loadConfig, /Conflicting configuration in ava.config.js and ava.config.cjs & ava.config.mjs/); + t.throws(loadConfigSync, /Conflicting configuration in ava.config.js and ava.config.cjs & ava.config.mjs/); t.end(); }, {todo: true}); diff --git a/test/config/fixtures/factory-promise-return/ava.config.js b/test/config/fixtures/factory-promise-return/ava.config.js new file mode 100644 index 000000000..25171edc2 --- /dev/null +++ b/test/config/fixtures/factory-promise-return/ava.config.js @@ -0,0 +1,8 @@ +module.exports = async () => { + return { + nonSemVerExperiments: { + nextGenConfig: true + }, + failFast: true + }; +}; diff --git a/test/config/fixtures/factory-promise-return/package.json b/test/config/fixtures/factory-promise-return/package.json new file mode 100644 index 000000000..0967ef424 --- /dev/null +++ b/test/config/fixtures/factory-promise-return/package.json @@ -0,0 +1 @@ +{} diff --git a/test/config/fixtures/file-yes-cjs-yes-mjs-yes/ava.config.cjs b/test/config/fixtures/file-yes-cjs-yes-mjs-yes/ava.config.cjs new file mode 100644 index 000000000..358af79e4 --- /dev/null +++ b/test/config/fixtures/file-yes-cjs-yes-mjs-yes/ava.config.cjs @@ -0,0 +1,3 @@ +module.exports = { + files: 'package-yes-files-yes-test-value' +}; diff --git a/test/config/fixtures/file-yes-cjs-yes-mjs-yes/ava.config.js b/test/config/fixtures/file-yes-cjs-yes-mjs-yes/ava.config.js new file mode 100644 index 000000000..c6ddbbed4 --- /dev/null +++ b/test/config/fixtures/file-yes-cjs-yes-mjs-yes/ava.config.js @@ -0,0 +1,3 @@ +export default { + files: 'package-yes-files-yes-test-value' +}; diff --git a/test/config/fixtures/file-yes-cjs-yes-mjs-yes/ava.config.mjs b/test/config/fixtures/file-yes-cjs-yes-mjs-yes/ava.config.mjs new file mode 100644 index 000000000..951c53310 --- /dev/null +++ b/test/config/fixtures/file-yes-cjs-yes-mjs-yes/ava.config.mjs @@ -0,0 +1,6 @@ +export default { + nonSemVerExperiments: { + nextGenConfig: true + }, + files: 'package-yes-files-yes-test-value' +}; diff --git a/test/config/fixtures/file-yes-cjs-yes-mjs-yes/package.json b/test/config/fixtures/file-yes-cjs-yes-mjs-yes/package.json new file mode 100644 index 000000000..0967ef424 --- /dev/null +++ b/test/config/fixtures/file-yes-cjs-yes-mjs-yes/package.json @@ -0,0 +1 @@ +{} diff --git a/test/config/fixtures/js-as-cjs/ava.config.js b/test/config/fixtures/js-as-cjs/ava.config.js new file mode 100644 index 000000000..c0964d0da --- /dev/null +++ b/test/config/fixtures/js-as-cjs/ava.config.js @@ -0,0 +1,6 @@ +module.exports = { + nonSemVerExperiments: { + nextGenConfig: true + }, + failFast: true +}; diff --git a/test/config/fixtures/js-as-cjs/package.json b/test/config/fixtures/js-as-cjs/package.json new file mode 100644 index 000000000..0967ef424 --- /dev/null +++ b/test/config/fixtures/js-as-cjs/package.json @@ -0,0 +1 @@ +{} diff --git a/test/config/fixtures/js-as-esm/ava.config.js b/test/config/fixtures/js-as-esm/ava.config.js new file mode 100644 index 000000000..414e3e612 --- /dev/null +++ b/test/config/fixtures/js-as-esm/ava.config.js @@ -0,0 +1,6 @@ +export default { + nonSemVerExperiments: { + nextGenConfig: true + }, + failFast: true +}; diff --git a/test/config/fixtures/js-as-esm/error.js b/test/config/fixtures/js-as-esm/error.js new file mode 100644 index 000000000..a92906214 --- /dev/null +++ b/test/config/fixtures/js-as-esm/error.js @@ -0,0 +1,8 @@ +export default { + nonSemVerExperiments: { + nextGenConfig: true + }, + failFast: true +}; + +throw new Error('🙈'); diff --git a/test/config/fixtures/js-as-esm/no-default-export.js b/test/config/fixtures/js-as-esm/no-default-export.js new file mode 100644 index 000000000..d5c0b831b --- /dev/null +++ b/test/config/fixtures/js-as-esm/no-default-export.js @@ -0,0 +1,6 @@ +export const config = { + nonSemVerExperiments: { + nextGenConfig: true + }, + failFast: true +}; diff --git a/test/config/fixtures/js-as-esm/package.json b/test/config/fixtures/js-as-esm/package.json new file mode 100644 index 000000000..bedb411a9 --- /dev/null +++ b/test/config/fixtures/js-as-esm/package.json @@ -0,0 +1,3 @@ +{ + "type": "module" +} diff --git a/test/config/fixtures/mjs-with-tests/ava.config.mjs b/test/config/fixtures/mjs-with-tests/ava.config.mjs new file mode 100644 index 000000000..1dcd338ba --- /dev/null +++ b/test/config/fixtures/mjs-with-tests/ava.config.mjs @@ -0,0 +1,6 @@ +export default { + nonSemVerExperiments: { + nextGenConfig: true + }, + files: ['dir-a/*.js'] +}; diff --git a/test/config/fixtures/resolve-pkg-dir/dir-a-wrapper/dir-a/dir-a-wrapper-3.js b/test/config/fixtures/mjs-with-tests/dir-a-wrapper/dir-a/dir-a-wrapper-3.js similarity index 100% rename from test/config/fixtures/resolve-pkg-dir/dir-a-wrapper/dir-a/dir-a-wrapper-3.js rename to test/config/fixtures/mjs-with-tests/dir-a-wrapper/dir-a/dir-a-wrapper-3.js diff --git a/test/config/fixtures/resolve-pkg-dir/dir-a-wrapper/dir-a/dir-a-wrapper-4.js b/test/config/fixtures/mjs-with-tests/dir-a-wrapper/dir-a/dir-a-wrapper-4.js similarity index 100% rename from test/config/fixtures/resolve-pkg-dir/dir-a-wrapper/dir-a/dir-a-wrapper-4.js rename to test/config/fixtures/mjs-with-tests/dir-a-wrapper/dir-a/dir-a-wrapper-4.js diff --git a/test/config/fixtures/resolve-pkg-dir/dir-a/dir-a-base-1.js b/test/config/fixtures/mjs-with-tests/dir-a/dir-a-base-1.js similarity index 100% rename from test/config/fixtures/resolve-pkg-dir/dir-a/dir-a-base-1.js rename to test/config/fixtures/mjs-with-tests/dir-a/dir-a-base-1.js diff --git a/test/config/fixtures/resolve-pkg-dir/dir-a/dir-a-base-2.js b/test/config/fixtures/mjs-with-tests/dir-a/dir-a-base-2.js similarity index 100% rename from test/config/fixtures/resolve-pkg-dir/dir-a/dir-a-base-2.js rename to test/config/fixtures/mjs-with-tests/dir-a/dir-a-base-2.js diff --git a/test/config/fixtures/mjs-with-tests/package.json b/test/config/fixtures/mjs-with-tests/package.json new file mode 100644 index 000000000..9992cb44e --- /dev/null +++ b/test/config/fixtures/mjs-with-tests/package.json @@ -0,0 +1,4 @@ +{ + "name": "application-name", + "version": "0.0.1" +} diff --git a/test/config/fixtures/mjs/ava.config.mjs b/test/config/fixtures/mjs/ava.config.mjs new file mode 100644 index 000000000..414e3e612 --- /dev/null +++ b/test/config/fixtures/mjs/ava.config.mjs @@ -0,0 +1,6 @@ +export default { + nonSemVerExperiments: { + nextGenConfig: true + }, + failFast: true +}; diff --git a/test/config/fixtures/mjs/error.mjs b/test/config/fixtures/mjs/error.mjs new file mode 100644 index 000000000..a92906214 --- /dev/null +++ b/test/config/fixtures/mjs/error.mjs @@ -0,0 +1,8 @@ +export default { + nonSemVerExperiments: { + nextGenConfig: true + }, + failFast: true +}; + +throw new Error('🙈'); diff --git a/test/config/fixtures/mjs/no-default-export.mjs b/test/config/fixtures/mjs/no-default-export.mjs new file mode 100644 index 000000000..d5c0b831b --- /dev/null +++ b/test/config/fixtures/mjs/no-default-export.mjs @@ -0,0 +1,6 @@ +export const config = { + nonSemVerExperiments: { + nextGenConfig: true + }, + failFast: true +}; diff --git a/test/config/fixtures/mjs/package.json b/test/config/fixtures/mjs/package.json new file mode 100644 index 000000000..bedb411a9 --- /dev/null +++ b/test/config/fixtures/mjs/package.json @@ -0,0 +1,3 @@ +{ + "type": "module" +} diff --git a/test/config/fixtures/pkg-with-tests/dir-a-wrapper/dir-a/dir-a-wrapper-3.js b/test/config/fixtures/pkg-with-tests/dir-a-wrapper/dir-a/dir-a-wrapper-3.js new file mode 100644 index 000000000..159662d3d --- /dev/null +++ b/test/config/fixtures/pkg-with-tests/dir-a-wrapper/dir-a/dir-a-wrapper-3.js @@ -0,0 +1,6 @@ +// eslint-disable-next-line ava/no-ignored-test-files +const test = require('ava'); + +test('test', t => { + t.pass(); +}); diff --git a/test/config/fixtures/pkg-with-tests/dir-a-wrapper/dir-a/dir-a-wrapper-4.js b/test/config/fixtures/pkg-with-tests/dir-a-wrapper/dir-a/dir-a-wrapper-4.js new file mode 100644 index 000000000..159662d3d --- /dev/null +++ b/test/config/fixtures/pkg-with-tests/dir-a-wrapper/dir-a/dir-a-wrapper-4.js @@ -0,0 +1,6 @@ +// eslint-disable-next-line ava/no-ignored-test-files +const test = require('ava'); + +test('test', t => { + t.pass(); +}); diff --git a/test/config/fixtures/pkg-with-tests/dir-a/dir-a-base-1.js b/test/config/fixtures/pkg-with-tests/dir-a/dir-a-base-1.js new file mode 100644 index 000000000..5ca41efa1 --- /dev/null +++ b/test/config/fixtures/pkg-with-tests/dir-a/dir-a-base-1.js @@ -0,0 +1,5 @@ +const test = require('ava'); + +test('test', t => { + t.pass(); +}); diff --git a/test/config/fixtures/pkg-with-tests/dir-a/dir-a-base-2.js b/test/config/fixtures/pkg-with-tests/dir-a/dir-a-base-2.js new file mode 100644 index 000000000..5ca41efa1 --- /dev/null +++ b/test/config/fixtures/pkg-with-tests/dir-a/dir-a-base-2.js @@ -0,0 +1,5 @@ +const test = require('ava'); + +test('test', t => { + t.pass(); +}); diff --git a/test/config/fixtures/resolve-pkg-dir/package.json b/test/config/fixtures/pkg-with-tests/package.json similarity index 67% rename from test/config/fixtures/resolve-pkg-dir/package.json rename to test/config/fixtures/pkg-with-tests/package.json index 4cc2f3b9c..a1c131239 100644 --- a/test/config/fixtures/resolve-pkg-dir/package.json +++ b/test/config/fixtures/pkg-with-tests/package.json @@ -2,6 +2,6 @@ "name": "application-name", "version": "0.0.1", "ava": { - "files": ["dir-a/*.js"] + "files": ["dir-a/*.js"] } - } +} diff --git a/test/config/fixtures/promise-config/ava.config.js b/test/config/fixtures/promise-config/ava.config.js new file mode 100644 index 000000000..39bb4191e --- /dev/null +++ b/test/config/fixtures/promise-config/ava.config.js @@ -0,0 +1,6 @@ +module.exports = Promise.resolve({ + nonSemVerExperiments: { + nextGenConfig: true + }, + failFast: true +}); diff --git a/test/config/fixtures/promise-config/package.json b/test/config/fixtures/promise-config/package.json new file mode 100644 index 000000000..0967ef424 --- /dev/null +++ b/test/config/fixtures/promise-config/package.json @@ -0,0 +1 @@ +{} diff --git a/test/config/test.js b/test/config/integration.js similarity index 70% rename from test/config/test.js rename to test/config/integration.js index 19528e003..4190f0195 100644 --- a/test/config/test.js +++ b/test/config/integration.js @@ -20,9 +20,9 @@ test('formats errors from ava.config.js', async t => { t.regex(lines[4], /foo/); }); -test('pkg-conf(resolve-dir): works as expected when run from the package.json directory', async t => { +test('works as expected when run from the package.json directory', async t => { const options = { - cwd: exec.cwd('resolve-pkg-dir') + cwd: exec.cwd('pkg-with-tests') }; const result = await exec.fixture([], options); @@ -30,9 +30,9 @@ test('pkg-conf(resolve-dir): works as expected when run from the package.json di t.snapshot(result.stats.passed, 'resolves test files from configuration'); }); -test('pkg-conf(resolve-dir): resolves tests from the package.json dir if none are specified on cli', async t => { +test('resolves tests from the package.json dir if none are specified on cli', async t => { const options = { - cwd: exec.cwd('resolve-pkg-dir/dir-a-wrapper') + cwd: exec.cwd('pkg-with-tests/dir-a-wrapper') }; const result = await exec.fixture(['--verbose'], options); @@ -40,6 +40,18 @@ test('pkg-conf(resolve-dir): resolves tests from the package.json dir if none ar t.snapshot(result.stats.passed, 'resolves test files from configuration'); }); +if (process.versions.node >= '12.17.0') { + test('resolves tests from an .mjs config file', async t => { + const options = { + cwd: exec.cwd('mjs-with-tests/dir-a-wrapper') + }; + + const result = await exec.fixture(['--verbose'], options); + + t.snapshot(result.stats.passed, 'resolves test files from configuration'); + }); +} + test('use current working directory if `package.json` is not found', async t => { const cwd = tempy.directory(); const testFilePath = path.join(cwd, 'test.js'); diff --git a/test/config/loader.js b/test/config/loader.js new file mode 100644 index 000000000..ce4164a90 --- /dev/null +++ b/test/config/loader.js @@ -0,0 +1,123 @@ +const path = require('path'); +const test = require('@ava/test'); +const {loadConfig} = require('../../lib/load-config'); + +const CWD = process.cwd(); +const FIXTURE_ROOT = path.resolve(__dirname, '../../test-tap/fixture/load-config'); + +const resolve = relpath => path.resolve(FIXTURE_ROOT, relpath); + +const loadFromSetup = setup => { + if (typeof setup === 'string') { + return loadConfig(); + } + + const {configFile, defaults, resolveFrom} = setup; + return loadConfig({configFile, defaults, resolveFrom}); +}; + +const ok = setup => async (t, assert = tt => tt.pass()) => { + const fixture = typeof setup === 'string' ? setup : setup.fixture; + + t.teardown(() => process.chdir(CWD)); + process.chdir(resolve(fixture)); + + const conf = loadFromSetup(setup); + await t.notThrowsAsync(conf); + const result = await t.try(assert, await conf, setup); + result.commit(); +}; + +const notOk = setup => async (t, assert = (tt, error) => tt.snapshot(error.message, 'error message')) => { + const fixture = typeof setup === 'string' ? setup : setup.fixture; + + t.teardown(() => process.chdir(CWD)); + process.chdir(resolve(fixture)); + + const conf = loadFromSetup(setup); + const error = await t.throwsAsync(conf); + const result = await t.try(assert, error, setup); + result.commit(); +}; + +test.serial('finds config in package.json', ok('package-only'), (t, conf) => { + t.true(conf.failFast); +}); + +test.serial('loads config from a particular directory', ok({ + fixture: 'throws', + resolveFrom: resolve('package-only') +}), (t, conf) => { + t.true(conf.failFast); +}); + +test.serial('throws an error if both configs are present', notOk('package-yes-file-yes')); + +test.serial('explicit configFile option overrides package.json config', ok({ + fixture: 'package-yes-explicit-yes', + configFile: 'explicit.js' +}), (t, conf) => { + t.is(conf.files, 'package-yes-explicit-yes-test-value'); +}); + +test.serial('throws if configFile option is not in the same directory as the package.json file', notOk({ + fixture: 'package-yes-explicit-yes', + configFile: 'nested/explicit.js' +})); + +test.serial('throws if configFile option has an unsupported extension', notOk({ + fixture: 'explicit-bad-extension', + configFile: 'explicit.txt' +})); + +test.serial('merges in defaults passed with initial call', ok({ + fixture: 'package-only', + defaults: { + files: ['123', '!456'] + } +}), (t, conf, {defaults}) => { + t.true(conf.failFast, 'preserves original props'); + t.is(conf.files, defaults.files, 'merges in extra props'); +}); + +test.serial('loads config from file with `export default` syntax', ok('package-no-file-yes'), (t, conf) => { + t.is(conf.files, 'config-file-esm-test-value'); +}); + +test.serial('loads config from factory function', ok('package-no-file-yes-factory'), (t, conf) => { + t.assert(conf.files.startsWith(FIXTURE_ROOT)); +}); + +test.serial('does not support require() inside config.js files', notOk('require')); + +test.serial('throws an error if a config factory returns a promise', notOk('factory-no-promise-return')); + +test.serial('throws an error if a config exports a promise', notOk('no-promise-config')); + +test.serial('throws an error if a config factory does not return a plain object', notOk('factory-no-plain-return')); + +test.serial('throws an error if a config does not export a plain object', notOk('no-plain-config')); + +test.serial('receives a `projectDir` property', ok('package-only'), (t, conf) => { + t.assert(conf.projectDir.startsWith(FIXTURE_ROOT)); +}); + +test.serial('rethrows wrapped module errors', notOk('throws'), (t, error) => { + t.is(error.parent.message, 'foo'); +}); + +test.serial('throws an error if a .js config file has no default export', notOk('no-default-export')); + +test.serial('throws an error if a config file contains `ava` property', notOk('contains-ava-property')); + +test.serial('throws an error if a config file contains a non-object `nonSemVerExperiments` property', notOk('non-object-experiments')); + +test.serial('throws an error if a config file enables an unsupported experiment', notOk('unsupported-experiments')); + +test.serial('loads .cjs config', ok('cjs'), (t, conf) => { + t.assert(conf.files.startsWith(FIXTURE_ROOT)); +}); + +test.serial('throws an error if both .js and .cjs configs are present', notOk('file-yes-cjs-yes')); + +test.serial('refuses to load .mjs config', notOk('mjs')); diff --git a/test/config/next-gen.js b/test/config/next-gen.js new file mode 100644 index 000000000..b1d49987c --- /dev/null +++ b/test/config/next-gen.js @@ -0,0 +1,84 @@ +const path = require('path'); +const test = require('@ava/test'); +const {loadConfig} = require('../../lib/load-config'); + +const CWD = process.cwd(); +const FIXTURE_ROOT = path.resolve(__dirname, 'fixtures'); + +const resolve = relpath => path.resolve(FIXTURE_ROOT, relpath); + +const loadFromSetup = setup => { + if (typeof setup === 'string') { + return loadConfig(); + } + + const {configFile, defaults, resolveFrom} = setup; + return loadConfig({configFile, defaults, resolveFrom}); +}; + +const ok = setup => async (t, assert = tt => tt.pass()) => { + const fixture = typeof setup === 'string' ? setup : setup.fixture; + + t.teardown(() => process.chdir(CWD)); + process.chdir(resolve(fixture)); + + const conf = loadFromSetup(setup); + await t.notThrowsAsync(conf); + const result = await t.try(assert, await conf, setup); + result.commit(); +}; + +const notOk = setup => async (t, assert = (tt, error) => tt.snapshot(error.message, 'error message')) => { + const fixture = typeof setup === 'string' ? setup : setup.fixture; + + t.teardown(() => process.chdir(CWD)); + process.chdir(resolve(fixture)); + + const conf = loadFromSetup(setup); + const error = await t.throwsAsync(conf); + const result = await t.try(assert, error, setup); + result.commit(); +}; + +test.serial('loads .mjs config', ok('mjs'), (t, conf) => { + t.true(conf.failFast); +}); + +test.serial('handles errors when loading .mjs config', notOk({ + fixture: 'mjs', + configFile: 'error.mjs' +})); + +test.serial('fails when .mjs config does not have a default export', notOk({ + fixture: 'mjs', + configFile: 'no-default-export.mjs' +})); + +test.serial('loads .js config as CommonJS', ok('js-as-cjs'), (t, conf) => { + t.true(conf.failFast); +}); + +test.serial('loads .js config as ESM', ok('js-as-esm'), (t, conf) => { + t.true(conf.failFast); +}); + +test.serial('handles errors when loading .js config as ESM', notOk({ + fixture: 'js-as-esm', + configFile: 'error.js' +})); + +test.serial('fails when .js config does not have a default export', notOk({ + fixture: 'js-as-esm', + configFile: 'no-default-export.js' +})); + +test.serial('throws an error if .js, .cjs and .mjs configs are present', notOk('file-yes-cjs-yes-mjs-yes')); + +test.serial('config factory returns a promise', ok('factory-promise-return'), (t, conf) => { + t.true(conf.failFast); +}); + +test.serial('config exports a promise', ok('promise-config'), (t, conf) => { + t.true(conf.failFast); +}); + diff --git a/test/config/snapshots/test.js.md b/test/config/snapshots/integration.js.md similarity index 56% rename from test/config/snapshots/test.js.md rename to test/config/snapshots/integration.js.md index 4115fcb77..f49dea19a 100644 --- a/test/config/snapshots/test.js.md +++ b/test/config/snapshots/integration.js.md @@ -1,10 +1,10 @@ -# Snapshot report for `test/config/test.js` +# Snapshot report for `test/config/integration.js` -The actual snapshot is saved in `test.js.snap`. +The actual snapshot is saved in `integration.js.snap`. Generated by [AVA](https://avajs.dev). -## pkg-conf(resolve-dir): resolves tests from the package.json dir if none are specified on cli +## resolves tests from an .mjs config file > resolves test files from configuration @@ -19,17 +19,17 @@ Generated by [AVA](https://avajs.dev). }, ] -## pkg-conf(resolve-dir): works as expected when run from the package.json directory +## resolves tests from the package.json dir if none are specified on cli > resolves test files from configuration [ { - file: 'dir-a/dir-a-base-1.js', + file: '../dir-a/dir-a-base-1.js', title: 'test', }, { - file: 'dir-a/dir-a-base-2.js', + file: '../dir-a/dir-a-base-2.js', title: 'test', }, ] @@ -44,3 +44,18 @@ Generated by [AVA](https://avajs.dev). title: 'test name', }, ] + +## works as expected when run from the package.json directory + +> resolves test files from configuration + + [ + { + file: 'dir-a/dir-a-base-1.js', + title: 'test', + }, + { + file: 'dir-a/dir-a-base-2.js', + title: 'test', + }, + ] diff --git a/test/config/snapshots/integration.js.snap b/test/config/snapshots/integration.js.snap new file mode 100644 index 000000000..2e074123c Binary files /dev/null and b/test/config/snapshots/integration.js.snap differ diff --git a/test/config/snapshots/loader.js.md b/test/config/snapshots/loader.js.md new file mode 100644 index 000000000..e26f8bba5 --- /dev/null +++ b/test/config/snapshots/loader.js.md @@ -0,0 +1,89 @@ +# Snapshot report for `test/config/loader.js` + +The actual snapshot is saved in `loader.js.snap`. + +Generated by [AVA](https://avajs.dev). + +## does not support require() inside config.js files + +> error message + + 'Error loading ava.config.js: require is not defined' + +## refuses to load .mjs config + +> error message + + 'AVA cannot yet load ava.config.mjs files' + +## throws an error if a .js config file has no default export + +> error message + + 'ava.config.js must have a default export, using ES module syntax' + +## throws an error if a config does not export a plain object + +> error message + + 'ava.config.js must export a plain object or factory function' + +## throws an error if a config exports a promise + +> error message + + 'ava.config.js must export a plain object or factory function' + +## throws an error if a config factory does not return a plain object + +> error message + + 'Factory method exported by ava.config.js must return a plain object' + +## throws an error if a config factory returns a promise + +> error message + + 'ava.config.js exported a promise or an asynchronous factory function. You must enable the ’asyncConfigurationLoading’ experiment for this to work.' + +## throws an error if a config file contains `ava` property + +> error message + + 'Encountered ’ava’ property in ava.config.js; avoid wrapping the configuration' + +## throws an error if a config file contains a non-object `nonSemVerExperiments` property + +> error message + + 'nonSemVerExperiments from ava.config.js must be an object' + +## throws an error if a config file enables an unsupported experiment + +> error message + + 'nonSemVerExperiments.unsupported from ava.config.js is not a supported experiment' + +## throws an error if both .js and .cjs configs are present + +> error message + + 'Conflicting configuration in ava.config.js and ava.config.cjs' + +## throws an error if both configs are present + +> error message + + 'Conflicting configuration in ava.config.js and package.json' + +## throws if configFile option has an unsupported extension + +> error message + + 'Config files must have .js, .cjs or .mjs extensions' + +## throws if configFile option is not in the same directory as the package.json file + +> error message + + 'Config files must be located next to the package.json file' diff --git a/test/config/snapshots/loader.js.snap b/test/config/snapshots/loader.js.snap new file mode 100644 index 000000000..46bea3fef Binary files /dev/null and b/test/config/snapshots/loader.js.snap differ diff --git a/test/config/snapshots/next-gen.js.md b/test/config/snapshots/next-gen.js.md new file mode 100644 index 000000000..81006c2e7 --- /dev/null +++ b/test/config/snapshots/next-gen.js.md @@ -0,0 +1,35 @@ +# Snapshot report for `test/config/next-gen.js` + +The actual snapshot is saved in `next-gen.js.snap`. + +Generated by [AVA](https://avajs.dev). + +## fails when .js config does not have a default export + +> error message + + 'no-default-export.js must have a default export' + +## fails when .mjs config does not have a default export + +> error message + + 'no-default-export.mjs must have a default export' + +## handles errors when loading .js config as ESM + +> error message + + 'Error loading error.js: 🙈' + +## handles errors when loading .mjs config + +> error message + + 'Error loading error.mjs: 🙈' + +## throws an error if .js, .cjs and .mjs configs are present + +> error message + + 'Conflicting configuration in ava.config.js and ava.config.cjs & ava.config.mjs' diff --git a/test/config/snapshots/next-gen.js.snap b/test/config/snapshots/next-gen.js.snap new file mode 100644 index 000000000..f897341ee Binary files /dev/null and b/test/config/snapshots/next-gen.js.snap differ diff --git a/test/config/snapshots/test.js.snap b/test/config/snapshots/test.js.snap deleted file mode 100644 index 4c31532fe..000000000 Binary files a/test/config/snapshots/test.js.snap and /dev/null differ diff --git a/xo.config.js b/xo.config.js index 49c546469..5bd98ac41 100644 --- a/xo.config.js +++ b/xo.config.js @@ -2,6 +2,7 @@ module.exports = { ignores: [ 'media/**', 'test/config/fixtures/config-errors/test.js', + 'test/config/fixtures/mjs-with-tests/**', 'test-tap/fixture/ava-paths/target/test.js', 'test-tap/fixture/{source-map-initial,syntax-error}.js', 'test-tap/fixture/snapshots/test-sourcemaps/build/**', @@ -26,7 +27,11 @@ module.exports = { } }, { - files: ['lib/plugin-support/shared-worker-loader.js', 'lib/plugin-support/shared-workers.js'], + files: [ + 'eslint-plugin-helper.js', + 'lib/plugin-support/shared-worker-loader.js', + 'lib/plugin-support/shared-workers.js' + ], // TODO [engine:node@>=12]: Enable when targeting Node.js 12. rules: { 'import/no-unresolved': 'off',