Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

use import by default for support code #2337

Merged
merged 4 commits into from
Oct 9, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ Please see [CONTRIBUTING.md](./CONTRIBUTING.md) on how to contribute to Cucumber
### Changed
- BREAKING CHANGE: Use appropriate module loading mechanism for configuration files ([#2334](https://github.com/cucumber/cucumber-js/pull/2334))
- BREAKING CHANGE: Use `await import()` to load all custom formatters and snippet syntaxes ([#2334](https://github.com/cucumber/cucumber-js/pull/2334))
- BREAKING CHANGE: Use `await import()` for default support code loading ([#2337](https://github.com/cucumber/cucumber-js/pull/2337))

### Removed
- BREAKING CHANGE: Drop support for Node.js 14, 16 and 19 ([#2331](https://github.com/cucumber/cucumber-js/pull/2331))
Expand Down
66 changes: 33 additions & 33 deletions docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,40 +8,20 @@ _**You are reading the documentation in the `main` development branch, which mig

You can keep your configuration in a file. Cucumber will look for one of these files in the root of your project, and use the first one it finds:

- `cucumber.js`
- `cucumber.cjs`
- `cucumber.mjs`
- `cucumber.json`
- `cucumber.yaml`
- `cucumber.yml`
- `cucumber.js`
- `cucumber.cjs`
- `cucumber.mjs`

You can also put your file somewhere else and tell Cucumber via the `--config` CLI option:

```shell
$ cucumber-js --config config/cucumber.js
```

Here's a concise example of a configuration file in CommonJS format:

```js
module.exports = {
default: {
parallel: 2,
format: ['html:cucumber-report.html']
}
}
```

And the same in ESM format:

```js
export default {
parallel: 2,
format: ['html:cucumber-report.html']
}
$ cucumber-js --config config/cucumber.json
```

And the same in JSON format:
Here's a concise example of a configuration file in JSON format:

```json
{
Expand All @@ -61,6 +41,26 @@ default:
- "html:cucumber-report.html"
```

And the same in JavaScript (ESM) format:

```js
export default {
parallel: 2,
format: ['html:cucumber-report.html']
}
```

And the same in JavaScript (CommonJS) format:

```js
module.exports = {
default: {
parallel: 2,
format: ['html:cucumber-report.html']
}
}
```

Cucumber also supports the configuration being a string of options in the style of the CLI, though this isn't recommended:

```js
Expand All @@ -87,7 +87,7 @@ These options can be used in a configuration file (see [above](#files)) or on th
| `failFast` | `boolean` | No | `--fail-fast` | Stop running tests when a test fails - see [Fail Fast](./fail_fast.md) | false |
| `format` | `string[]` | Yes | `--format`, `-f` | Name/path and (optionally) output file path of each formatter to use - see [Formatters](./formatters.md) | [] |
| `formatOptions` | `object` | Yes | `--format-options` | Options to be provided to formatters - see [Formatters](./formatters.md) | {} |
| `import` | `string[]` | Yes | `--import`, `-i` | Paths to where your support code is, for ESM - see [ESM](./esm.md) | [] |
| `import` | `string[]` | Yes | `--import`, `-i` | Paths to where your support code is | [] |
| `language` | `string` | No | `--language` | Default language for your feature files | en |
| `name` | `string` | No | `--name` | Regular expressions of which scenario names should match one of to be run - see [Filtering](./filtering.md#names) | [] |
| `order` | `string` | No | `--order` | Run in the order defined, or in a random order | defined |
Expand Down Expand Up @@ -123,15 +123,15 @@ For more granular options to control _which scenarios_ from your features should
By default, Cucumber finds support code files with this logic:

* If the features live in a `features` directory (at any level)
* `features/**/*.(js)`
* `features/**/*.@(js|cjs|mjs)`
* Otherwise
* `<DIR>/**/*.(js)` for each directory containing the selected features
* `<DIR>/**/*.@(js|cjs|mjs)` for each directory containing the selected features

If your files are somewhere else, you can override this by proving your own [glob](https://github.com/isaacs/node-glob), directory or file path to the `require` configuration option:
If your files are somewhere else, you can override this by proving your own [glob](https://github.com/isaacs/node-glob), directory or file path to the `import` configuration option:

- In a configuration file `{ require: ['somewhere-else/support/*.js'] }`
- On the CLI `$ cucumber-js --require somewhere-else/support/*.js`
- In a configuration file `{ import: ['somewhere-else/support/*.js'] }`
- On the CLI `$ cucumber-js --import somewhere-else/support/*.js`

Once you specify any `require` options, the defaults described above are no longer applied. The option is repeatable, so you can provide several values and they'll be combined, meaning you can load files from multiple locations.
Once you specify any `import` options, the defaults described above are no longer applied. The option is repeatable, so you can provide several values and they'll be combined, meaning you can load files from multiple locations.

The default behaviour and the `require` option both use the [legacy CommonJS modules API](https://nodejs.org/api/modules.html) to load your files. If your files are native ES modules, you'll need to use the `import` option instead in the same way, and they'll be loaded with the [new ES modules API](https://nodejs.org/api/esm.html). See [ES Modules](./esm.md) for more on using Cucumber in an ESM project.
The default behaviour and the `import` option both use the [new ES modules API](https://nodejs.org/api/esm.html) to load your files. This should work fine for the majority of cases, but sometimes (e.g. when transpiling with the `require-module` option), you'll need to use the `require` option instead in the same way, and they'll be loaded with the [legacy CommonJS modules API](https://nodejs.org/api/modules.html).
5 changes: 2 additions & 3 deletions src/api/paths.ts
Original file line number Diff line number Diff line change
Expand Up @@ -147,9 +147,8 @@ async function deriveSupportPaths(
unexpandedImportPaths.length === 0
) {
const defaultPaths = getFeatureDirectoryPaths(cwd, featurePaths)
const requirePaths = await expandPaths(cwd, defaultPaths, '.js')
const importPaths = await expandPaths(cwd, defaultPaths, '.mjs')
return { requirePaths, importPaths }
const importPaths = await expandPaths(cwd, defaultPaths, '.@(js|cjs|mjs)')
return { requirePaths: [], importPaths }
}
const requirePaths =
unexpandedRequirePaths.length > 0
Expand Down
26 changes: 13 additions & 13 deletions src/api/paths_spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,8 @@ describe('resolvePaths', () => {
// Assert
expect(featurePaths).to.eql([featurePath])
expect(unexpandedFeaturePaths).to.eql([relativeFeaturePath])
expect(requirePaths).to.eql([jsSupportCodePath])
expect(importPaths).to.eql([esmSupportCodePath])
expect(requirePaths).to.eql([])
expect(importPaths).to.eql([jsSupportCodePath, esmSupportCodePath])
})

it('deduplicates features based on overlapping expressions', async function () {
Expand Down Expand Up @@ -113,7 +113,7 @@ describe('resolvePaths', () => {
await fsExtra.outputFile(supportCodePath, '')

// Act
const { featurePaths, unexpandedFeaturePaths, requirePaths } =
const { featurePaths, unexpandedFeaturePaths, importPaths } =
await resolvePaths(
new FakeLogger(),
cwd,
Expand All @@ -130,7 +130,7 @@ describe('resolvePaths', () => {
// Assert
expect(featurePaths).to.eql([featurePath])
expect(unexpandedFeaturePaths).to.eql([relativeFeaturePath])
expect(requirePaths).to.eql([supportCodePath])
expect(importPaths).to.eql([supportCodePath])
})
})

Expand All @@ -145,7 +145,7 @@ describe('resolvePaths', () => {
await fsExtra.outputFile(supportCodePath, '')

// Act
const { featurePaths, unexpandedFeaturePaths, requirePaths } =
const { featurePaths, unexpandedFeaturePaths, importPaths } =
await resolvePaths(
new FakeLogger(),
cwd,
Expand All @@ -162,7 +162,7 @@ describe('resolvePaths', () => {
// Assert
expect(featurePaths).to.eql([featurePath])
expect(unexpandedFeaturePaths).to.eql([relativeFeaturePath])
expect(requirePaths).to.eql([supportCodePath])
expect(importPaths).to.eql([supportCodePath])
})

it('returns the appropriate .md and support code paths', async function () {
Expand All @@ -179,7 +179,7 @@ describe('resolvePaths', () => {
await fsExtra.outputFile(supportCodePath, '')

// Act
const { featurePaths, unexpandedFeaturePaths, requirePaths } =
const { featurePaths, unexpandedFeaturePaths, importPaths } =
await resolvePaths(
new FakeLogger(),
cwd,
Expand All @@ -196,7 +196,7 @@ describe('resolvePaths', () => {
// Assert
expect(featurePaths).to.eql([featurePath])
expect(unexpandedFeaturePaths).to.eql([relativeFeaturePath])
expect(requirePaths).to.eql([supportCodePath])
expect(importPaths).to.eql([supportCodePath])
})
})

Expand Down Expand Up @@ -297,8 +297,8 @@ describe('resolvePaths', () => {
const relativeFeaturePath = path.join('features', 'a.feature')
const featurePath = path.join(cwd, relativeFeaturePath)
await fsExtra.outputFile(featurePath, '')
const jsSupportCodePath = path.join(cwd, 'features', 'a.js')
await fsExtra.outputFile(jsSupportCodePath, '')
const cjsSupportCodePath = path.join(cwd, 'features', 'a.cjs')
await fsExtra.outputFile(cjsSupportCodePath, '')
const esmSupportCodePath = path.join(cwd, 'features', 'a.mjs')
await fsExtra.outputFile(esmSupportCodePath, '')

Expand All @@ -311,8 +311,8 @@ describe('resolvePaths', () => {
},
{
requireModules: [],
requirePaths: [],
importPaths: [],
requirePaths: [cjsSupportCodePath],
importPaths: [esmSupportCodePath],
}
)

Expand All @@ -323,7 +323,7 @@ describe('resolvePaths', () => {
)
expect(logger.debug).to.have.been.calledWith(
'Found support files to load via `require` based on configuration:',
[jsSupportCodePath]
[cjsSupportCodePath]
)
expect(logger.debug).to.have.been.calledWith(
'Found support files to load via `import` based on configuration:',
Expand Down
5 changes: 5 additions & 0 deletions src/configuration/validate_configuration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,11 @@ export function validateConfiguration(
'`publishQuiet` option is no longer needed, you can remove it from your configuration; see https://github.com/cucumber/cucumber-js/blob/main/docs/deprecations.md'
)
}
if (configuration.requireModule.length && !configuration.require.length) {
logger.warn(
'Use of `require-module` option normally means you should specify your support code paths with `require`; see https://github.com/cucumber/cucumber-js/blob/main/docs/configuration.md#finding-your-code'
)
}
if (configuration.retryTagFilter && !configuration.retry) {
throw new Error(
'a positive `retry` count must be specified when setting `retryTagFilter`'
Expand Down