Skip to content

Commit adbfcde

Browse files
authored
Experimentally expose internal events for custom reporters
Add a new `observeRunsFromConfig` experiment, which allows a test run to be observed by a function installed through an `ava.config.*` file. The function has access to AVA's internal events, which can then be used to report to a file. AVA's internal event structure is not currently covered by any SemVer guarantees, which is why this feature requires the experimental opt-in. Does not currently support watch mode. Only the first run is observed.
1 parent 6790d50 commit adbfcde

File tree

11 files changed

+229
-1
lines changed

11 files changed

+229
-1
lines changed

entrypoints/internal.d.mts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import type {StateChangeEvent} from '../types/state-change-events.d';
2+
3+
export type Event = StateChangeEvent;
4+
5+
export type ObservedRun = {
6+
events: AsyncIterableIterator<Event>;
7+
};

lib/api-event-iterator.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
export async function * asyncEventIteratorFromApi(api) {
2+
// TODO: support multiple runs (watch mode)
3+
const {value: plan} = await api.events('run').next();
4+
5+
for await (const stateChange of plan.status.events('stateChange')) {
6+
yield stateChange;
7+
8+
if (stateChange.type === 'end' || stateChange.type === 'interrupt') {
9+
break;
10+
}
11+
}
12+
}

lib/cli.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import figures from 'figures';
88
import yargs from 'yargs';
99
import {hideBin} from 'yargs/helpers'; // eslint-disable-line n/file-extension-in-import
1010

11+
import {asyncEventIteratorFromApi} from './api-event-iterator.js';
1112
import Api from './api.js';
1213
import {chalk} from './chalk.js';
1314
import validateEnvironmentVariables from './environment-variables.js';
@@ -470,6 +471,12 @@ export default async function loadCli() { // eslint-disable-line complexity
470471
});
471472
}
472473

474+
if (combined.observeRun && experiments.observeRunsFromConfig) {
475+
combined.observeRun({
476+
events: asyncEventIteratorFromApi(api),
477+
});
478+
}
479+
473480
api.on('run', plan => {
474481
reporter.startRun(plan);
475482

lib/load-config.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import {packageConfig, packageJsonPath} from 'package-config';
88

99
const NO_SUCH_FILE = Symbol('no ava.config.js file');
1010
const MISSING_DEFAULT_EXPORT = Symbol('missing default export');
11-
const EXPERIMENTS = new Set();
11+
const EXPERIMENTS = new Set(['observeRunsFromConfig']);
1212

1313
const importConfig = async ({configFile, fileForErrorMessage}) => {
1414
const {default: config = MISSING_DEFAULT_EXPORT} = await import(url.pathToFileURL(configFile));

package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,9 @@
2929
"types": "./entrypoints/plugin.d.cts",
3030
"default": "./entrypoints/plugin.cjs"
3131
}
32+
},
33+
"./internal": {
34+
"types": "./entrypoints/internal.d.mts"
3235
}
3336
},
3437
"type": "module",
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
internal-events.json
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import fs from 'node:fs/promises';
2+
3+
const internalEvents = [];
4+
5+
export default {
6+
files: [
7+
'test.js',
8+
],
9+
nonSemVerExperiments: {
10+
observeRunsFromConfig: true,
11+
},
12+
async observeRun(run) {
13+
for await (const event of run.events) {
14+
internalEvents.push(event);
15+
}
16+
17+
await fs.writeFile('internal-events.json', JSON.stringify(internalEvents));
18+
},
19+
};
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"type": "module"
3+
}

test/internal-events/fixtures/test.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import test from 'ava';
2+
3+
test('placeholder', t => {
4+
t.pass();
5+
});

test/internal-events/test.js

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import fs from 'node:fs/promises';
2+
import {fileURLToPath} from 'node:url';
3+
4+
import test from '@ava/test';
5+
6+
import {fixture} from '../helpers/exec.js';
7+
8+
test('internal events are emitted', async t => {
9+
await fixture();
10+
11+
const result = JSON.parse(await fs.readFile(fileURLToPath(new URL('fixtures/internal-events.json', import.meta.url))));
12+
13+
t.like(result[0], {
14+
type: 'starting',
15+
testFile: fileURLToPath(new URL('fixtures/test.js', import.meta.url)),
16+
});
17+
18+
const testPassedEvent = result.find(event => event.type === 'test-passed');
19+
t.like(testPassedEvent, {
20+
type: 'test-passed',
21+
title: 'placeholder',
22+
testFile: fileURLToPath(new URL('fixtures/test.js', import.meta.url)),
23+
});
24+
25+
t.like(result.at(-1), {
26+
type: 'end',
27+
});
28+
});

0 commit comments

Comments
 (0)