Remove support for passing multiple test implementations

This affects test() and t.try().

Supporting multiple implementations made both the implementation and the
type definitions needlessly complex. Using for/of syntax it's easy
enough to declare tests (or t.try() attempts) with the same arguments.
This commit is contained in:
Mark Wubben 2021-07-04 16:24:12 +02:00
parent 59b035125a
commit d7c2b90ae4
14 changed files with 206 additions and 375 deletions

View file

@ -319,21 +319,4 @@ test('providedTitle', macro, '3 * 3', 9);
The `providedTitle` argument defaults to `undefined` if the user does not supply a string title. This means you can use a parameter assignment to set the default value. The example above uses the empty string as the default.
You can also pass arrays of macro functions:
```js
const safeEval = require('safe-eval');
function evalMacro(t, input, expected) {
t.is(eval(input), expected);
}
function safeEvalMacro(t, input, expected) {
t.is(safeEval(input), expected);
}
test([evalMacro, safeEvalMacro], '2 + 2', 4);
test([evalMacro, safeEvalMacro], '2 * 3', 6);
```
We encourage you to use macros instead of building your own test generators ([here is an example](https://github.com/avajs/ava-codemods/blob/47073b5b58aa6f3fb24f98757be5d3f56218d160/test/ok-to-truthy.js#L7-L9) of code that should be replaced with a macro). Macros are designed to perform static analysis of your code, which can lead to better performance, IDE integration, and linter rules.

View file

@ -330,7 +330,7 @@ AVA 3 supports an `options` object that lets you select a specific snapshot, fo
In AVA 3, you cannot update snapshots while using `t.snapshot.skip()`.
### `.try(title?, implementation | macro | macro[], ...args?)`
### `.try(title?, implementation | macro, ...args?)`
`.try()` allows you to *try* assertions without causing the test to fail.

68
index.d.ts vendored
View file

@ -409,24 +409,11 @@ export interface TryFn<Context = unknown> {
*/
<Args extends any[]>(title: string, fn: EitherMacro<Args, Context>, ...args: Args): Promise<TryResult>;
/**
* Attempt to run some assertions. The result must be explicitly committed or discarded or else
* the test will fail. A macro may be provided. The title may help distinguish attempts from
* one another.
*/
<Args extends any[]>(title: string, fn: [EitherMacro<Args, Context>, ...Array<EitherMacro<Args, Context>>], ...args: Args): Promise<TryResult[]>;
/**
* Attempt to run some assertions. The result must be explicitly committed or discarded or else
* the test will fail. A macro may be provided.
*/
<Args extends any[]>(fn: EitherMacro<Args, Context>, ...args: Args): Promise<TryResult>;
/**
* Attempt to run some assertions. The result must be explicitly committed or discarded or else
* the test will fail. A macro may be provided.
*/
<Args extends any[]>(fn: [EitherMacro<Args, Context>, ...Array<EitherMacro<Args, Context>>], ...args: Args): Promise<TryResult[]>;
}
export interface AssertionError extends Error {}
@ -483,18 +470,15 @@ export type Macro<Args extends any[], Context = unknown> = UntitledMacro<Args, C
export type EitherMacro<Args extends any[], Context> = Macro<Args, Context> | UntitledMacro<Args, Context>;
/** Alias for a single macro, or an array of macros. */
export type OneOrMoreMacros<Args extends any[], Context> = EitherMacro<Args, Context> | [EitherMacro<Args, Context>, ...Array<EitherMacro<Args, Context>>];
export interface TestInterface<Context = unknown> {
/** Declare a concurrent test. */
(title: string, implementation: Implementation<Context>): void;
/** Declare a concurrent test that uses one or more macros. Additional arguments are passed to the macro. */
<T extends any[]>(title: string, macros: OneOrMoreMacros<T, Context>, ...rest: T): void;
/** Declare a concurrent test that uses a macro. Additional arguments are passed to the macro. */
<T extends any[]>(title: string, macro: EitherMacro<T, Context>, ...rest: T): void;
/** Declare a concurrent test that uses one or more macros. The macro is responsible for generating a unique test title. */
<T extends any[]>(macros: OneOrMoreMacros<T, Context>, ...rest: T): void;
/** Declare a concurrent test that uses a macro. The macro is responsible for generating a unique test title. */
<T extends any[]>(macro: EitherMacro<T, Context>, ...rest: T): void;
/** Declare a hook that is run once, after all tests have passed. */
after: AfterInterface<Context>;
@ -528,10 +512,10 @@ export interface AfterInterface<Context = unknown> {
(title: string, implementation: Implementation<Context>): void;
/** Declare a hook that is run once, after all tests have passed. Additional arguments are passed to the macro. */
<T extends any[]>(title: string, macros: OneOrMoreMacros<T, Context>, ...rest: T): void;
<T extends any[]>(title: string, macro: EitherMacro<T, Context>, ...rest: T): void;
/** Declare a hook that is run once, after all tests have passed. */
<T extends any[]>(macros: OneOrMoreMacros<T, Context>, ...rest: T): void;
<T extends any[]>(macro: EitherMacro<T, Context>, ...rest: T): void;
/** Declare a hook that is run once, after all tests are done. */
always: AlwaysInterface<Context>;
@ -547,10 +531,10 @@ export interface AlwaysInterface<Context = unknown> {
(title: string, implementation: Implementation<Context>): void;
/** Declare a hook that is run once, after all tests are done. Additional arguments are passed to the macro. */
<T extends any[]>(title: string, macros: OneOrMoreMacros<T, Context>, ...rest: T): void;
<T extends any[]>(title: string, macro: EitherMacro<T, Context>, ...rest: T): void;
/** Declare a hook that is run once, after all tests are done. */
<T extends any[]>(macros: OneOrMoreMacros<T, Context>, ...rest: T): void;
<T extends any[]>(macro: EitherMacro<T, Context>, ...rest: T): void;
skip: HookSkipInterface<Context>;
}
@ -563,10 +547,10 @@ export interface BeforeInterface<Context = unknown> {
(title: string, implementation: Implementation<Context>): void;
/** Declare a hook that is run once, before all tests. Additional arguments are passed to the macro. */
<T extends any[]>(title: string, macros: OneOrMoreMacros<T, Context>, ...rest: T): void;
<T extends any[]>(title: string, macro: EitherMacro<T, Context>, ...rest: T): void;
/** Declare a hook that is run once, before all tests. */
<T extends any[]>(macros: OneOrMoreMacros<T, Context>, ...rest: T): void;
<T extends any[]>(macro: EitherMacro<T, Context>, ...rest: T): void;
skip: HookSkipInterface<Context>;
}
@ -576,16 +560,16 @@ export interface FailingInterface<Context = unknown> {
(title: string, implementation: Implementation<Context>): void;
/**
* Declare a concurrent test that uses one or more macros. Additional arguments are passed to the macro.
* Declare a concurrent test that uses a macro. Additional arguments are passed to the macro.
* The test is expected to fail.
*/
<T extends any[]>(title: string, macros: OneOrMoreMacros<T, Context>, ...rest: T): void;
<T extends any[]>(title: string, macro: EitherMacro<T, Context>, ...rest: T): void;
/**
* Declare a concurrent test that uses one or more macros. The macro is responsible for generating a unique test title.
* Declare a concurrent test that uses a macro. The macro is responsible for generating a unique test title.
* The test is expected to fail.
*/
<T extends any[]>(macros: OneOrMoreMacros<T, Context>, ...rest: T): void;
<T extends any[]>(macro: EitherMacro<T, Context>, ...rest: T): void;
only: OnlyInterface<Context>;
skip: SkipInterface<Context>;
@ -599,10 +583,10 @@ export interface HookSkipInterface<Context = unknown> {
(title: string, implementation: Implementation<Context>): void;
/** Skip this hook. */
<T extends any[]>(title: string, macros: OneOrMoreMacros<T, Context>, ...rest: T): void;
<T extends any[]>(title: string, macro: EitherMacro<T, Context>, ...rest: T): void;
/** Skip this hook. */
<T extends any[]>(macros: OneOrMoreMacros<T, Context>, ...rest: T): void;
<T extends any[]>(macro: EitherMacro<T, Context>, ...rest: T): void;
}
export interface OnlyInterface<Context = unknown> {
@ -610,29 +594,29 @@ export interface OnlyInterface<Context = unknown> {
(title: string, implementation: Implementation<Context>): void;
/**
* Declare a test that uses one or more macros. Additional arguments are passed to the macro.
* Declare a test that uses a macro. Additional arguments are passed to the macro.
* Only this test and others declared with `.only()` are run.
*/
<T extends any[]>(title: string, macros: OneOrMoreMacros<T, Context>, ...rest: T): void;
<T extends any[]>(title: string, macro: EitherMacro<T, Context>, ...rest: T): void;
/**
* Declare a test that uses one or more macros. The macro is responsible for generating a unique test title.
* Declare a test that uses a macro. The macro is responsible for generating a unique test title.
* Only this test and others declared with `.only()` are run.
*/
<T extends any[]>(macros: OneOrMoreMacros<T, Context>, ...rest: T): void;
<T extends any[]>(macro: EitherMacro<T, Context>, ...rest: T): void;
}
export interface SerialInterface<Context = unknown> {
/** Declare a serial test. */
(title: string, implementation: Implementation<Context>): void;
/** Declare a serial test that uses one or more macros. Additional arguments are passed to the macro. */
<T extends any[]>(title: string, macros: OneOrMoreMacros<T, Context>, ...rest: T): void;
/** Declare a serial test that uses a macro. Additional arguments are passed to the macro. */
<T extends any[]>(title: string, macro: EitherMacro<T, Context>, ...rest: T): void;
/**
* Declare a serial test that uses one or more macros. The macro is responsible for generating a unique test title.
* Declare a serial test that uses a macro. The macro is responsible for generating a unique test title.
*/
<T extends any[]>(macros: OneOrMoreMacros<T, Context>, ...rest: T): void;
<T extends any[]>(macro: EitherMacro<T, Context>, ...rest: T): void;
/** Declare a serial hook that is run once, after all tests have passed. */
after: AfterInterface<Context>;
@ -659,10 +643,10 @@ export interface SkipInterface<Context = unknown> {
(title: string, implementation: Implementation<Context>): void;
/** Skip this test. */
<T extends any[]>(title: string, macros: OneOrMoreMacros<T, Context>, ...rest: T): void;
<T extends any[]>(title: string, macro: EitherMacro<T, Context>, ...rest: T): void;
/** Skip this test. */
<T extends any[]>(macros: OneOrMoreMacros<T, Context>, ...rest: T): void;
<T extends any[]>(macro: EitherMacro<T, Context>, ...rest: T): void;
}
export interface TodoDeclaration {

View file

@ -1,14 +1,26 @@
const normalize = title => typeof title === 'string' ? title.trim().replace(/\s+/g, ' ') : title;
const buildTitle = (raw, implementation, args) => {
let value = implementation && implementation.title ? implementation.title(raw, ...args) : raw;
const isValid = typeof value === 'string';
if (isValid) {
value = value.trim().replace(/\s+/g, ' ');
}
return {
raw,
value,
isSet: value !== undefined,
isValid,
isEmpty: !isValid || value === ''
};
};
export default function parseTestArgs(args) {
const rawTitle = typeof args[0] === 'string' ? args.shift() : undefined;
const receivedImplementationArray = Array.isArray(args[0]);
const implementations = receivedImplementationArray ? args.shift() : args.splice(0, 1);
const implementation = args.shift();
const buildTitle = implementation => {
const title = normalize(implementation.title ? implementation.title(rawTitle, ...args) : rawTitle);
return {title, isSet: typeof title !== 'undefined', isValid: typeof title === 'string', isEmpty: !title};
return {
args,
implementation,
title: buildTitle(rawTitle, implementation, args)
};
return {args, buildTitle, implementations, rawTitle, receivedImplementationArray};
}

View file

@ -100,95 +100,96 @@ export default class Runner extends Emittery {
metadata.taskIndex = this.nextTaskIndex++;
const {args, buildTitle, implementations, rawTitle} = parseTestArgs(testArgs);
const {args, implementation, title} = parseTestArgs(testArgs);
if (this.checkSelectedByLineNumbers) {
metadata.selected = this.checkSelectedByLineNumbers();
}
if (metadata.todo) {
if (implementations.length > 0) {
if (implementation) {
throw new TypeError('`todo` tests are not allowed to have an implementation. Use `test.skip()` for tests with an implementation.');
}
if (!rawTitle) { // Either undefined or a string.
if (!title.raw) { // Either undefined or a string.
throw new TypeError('`todo` tests require a title');
}
if (!this.registerUniqueTitle(rawTitle)) {
throw new Error(`Duplicate test title: ${rawTitle}`);
if (!this.registerUniqueTitle(title.value)) {
throw new Error(`Duplicate test title: ${title.value}`);
}
// --match selects TODO tests.
if (this.match.length > 0 && matcher([rawTitle], this.match).length === 1) {
if (this.match.length > 0 && matcher([title.value], this.match).length === 1) {
metadata.exclusive = true;
this.runOnlyExclusive = true;
}
this.tasks.todo.push({title: rawTitle, metadata});
this.tasks.todo.push({title: title.value, metadata});
this.emit('stateChange', {
type: 'declared-test',
title: rawTitle,
title: title.value,
knownFailing: false,
todo: true
});
} else {
if (implementations.length === 0) {
if (!implementation) {
throw new TypeError('Expected an implementation. Use `test.todo()` for tests without an implementation.');
}
for (const implementation of implementations) {
let {title, isSet, isValid, isEmpty} = buildTitle(implementation);
if (Array.isArray(implementation)) {
throw new TypeError('AVA 4 no longer supports multiple implementations.');
}
if (isSet && !isValid) {
throw new TypeError('Test & hook titles must be strings');
}
if (isEmpty) {
if (metadata.type === 'test') {
throw new TypeError('Tests must have a title');
} else if (metadata.always) {
title = `${metadata.type}.always hook`;
} else {
title = `${metadata.type} hook`;
}
}
if (metadata.type === 'test' && !this.registerUniqueTitle(title)) {
throw new Error(`Duplicate test title: ${title}`);
}
const task = {
title,
implementation,
args,
metadata: {...metadata}
};
if (title.isSet && !title.isValid) {
throw new TypeError('Test & hook titles must be strings');
}
let fallbackTitle = title.value;
if (title.isEmpty) {
if (metadata.type === 'test') {
if (this.match.length > 0) {
// --match overrides .only()
task.metadata.exclusive = matcher([title], this.match).length === 1;
}
if (task.metadata.exclusive) {
this.runOnlyExclusive = true;
}
this.tasks[metadata.serial ? 'serial' : 'concurrent'].push(task);
this.snapshots.touch(title, metadata.taskIndex);
this.emit('stateChange', {
type: 'declared-test',
title,
knownFailing: metadata.failing,
todo: false
});
} else if (!metadata.skipped) {
this.tasks[metadata.type + (metadata.always ? 'Always' : '')].push(task);
throw new TypeError('Tests must have a title');
} else if (metadata.always) {
fallbackTitle = `${metadata.type}.always hook`;
} else {
fallbackTitle = `${metadata.type} hook`;
}
}
if (metadata.type === 'test' && !this.registerUniqueTitle(title.value)) {
throw new Error(`Duplicate test title: ${title.value}`);
}
const task = {
title: title.value || fallbackTitle,
implementation,
args,
metadata: {...metadata}
};
if (metadata.type === 'test') {
if (this.match.length > 0) {
// --match overrides .only()
task.metadata.exclusive = matcher([title.value], this.match).length === 1;
}
if (task.metadata.exclusive) {
this.runOnlyExclusive = true;
}
this.tasks[metadata.serial ? 'serial' : 'concurrent'].push(task);
this.snapshots.touch(title.value, metadata.taskIndex);
this.emit('stateChange', {
type: 'declared-test',
title: title.value,
knownFailing: metadata.failing,
todo: false
});
} else if (!metadata.skipped) {
this.tasks[metadata.type + (metadata.always ? 'Always' : '')].push(task);
}
}
}, {
serial: false,

View file

@ -81,90 +81,86 @@ class ExecutionContext extends Assertions {
throw error;
}
const {args, buildTitle, implementations, receivedImplementationArray} = parseTestArgs(attemptArgs);
const {args, implementation, title} = parseTestArgs(attemptArgs);
if (implementations.length === 0) {
if (!implementation) {
throw new TypeError('Expected an implementation.');
}
const attemptPromises = implementations.map((implementation, index) => {
let {title, isSet, isValid, isEmpty} = buildTitle(implementation);
if (Array.isArray(implementation)) {
throw new TypeError('AVA 4 no longer supports t.try() with multiple implementations.');
}
if (!isSet || isEmpty) {
title = `${test.title} ─ attempt ${test.attemptCount + 1 + index}`;
} else if (isValid) {
title = `${test.title} ─ ${title}`;
} else {
throw new TypeError('`t.try()` titles must be strings'); // Throw synchronously!
}
let attemptTitle;
if (!title.isSet || title.isEmpty) {
attemptTitle = `${test.title} ─ attempt ${test.attemptCount + 1}`;
} else if (title.isValid) {
attemptTitle = `${test.title} ─ ${title.value}`;
} else {
throw new TypeError('`t.try()` titles must be strings');
}
if (!test.registerUniqueTitle(title)) {
throw new Error(`Duplicate test title: ${title}`);
}
if (!test.registerUniqueTitle(attemptTitle)) {
throw new Error(`Duplicate test title: ${attemptTitle}`);
}
return {implementation, title};
}).map(async ({implementation, title}) => {
let committed = false;
let discarded = false;
let committed = false;
let discarded = false;
const {assertCount, deferredSnapshotRecordings, errors, logs, passed, snapshotCount, startingSnapshotCount} = await test.runAttempt(title, t => implementation(t, ...args));
const {assertCount, deferredSnapshotRecordings, errors, logs, passed, snapshotCount, startingSnapshotCount} = await test.runAttempt(attemptTitle, t => implementation(t, ...args));
return {
errors,
logs: [...logs], // Don't allow modification of logs.
passed,
title,
commit: ({retainLogs = true} = {}) => {
if (committed) {
return;
}
if (discarded) {
test.saveFirstError(new Error('Can’t commit a result that was previously discarded'));
return;
}
committed = true;
test.finishAttempt({
assertCount,
commit: true,
deferredSnapshotRecordings,
errors,
logs,
passed,
retainLogs,
snapshotCount,
startingSnapshotCount
});
},
discard: ({retainLogs = false} = {}) => {
if (committed) {
test.saveFirstError(new Error('Can’t discard a result that was previously committed'));
return;
}
if (discarded) {
return;
}
discarded = true;
test.finishAttempt({
assertCount: 0,
commit: false,
deferredSnapshotRecordings,
errors,
logs,
passed,
retainLogs,
snapshotCount,
startingSnapshotCount
});
return {
errors,
logs: [...logs], // Don't allow modification of logs.
passed,
title: attemptTitle,
commit: ({retainLogs = true} = {}) => {
if (committed) {
return;
}
};
});
const results = await Promise.all(attemptPromises);
return receivedImplementationArray ? results : results[0];
if (discarded) {
test.saveFirstError(new Error('Can’t commit a result that was previously discarded'));
return;
}
committed = true;
test.finishAttempt({
assertCount,
commit: true,
deferredSnapshotRecordings,
errors,
logs,
passed,
retainLogs,
snapshotCount,
startingSnapshotCount
});
},
discard: ({retainLogs = false} = {}) => {
if (committed) {
test.saveFirstError(new Error('Can’t discard a result that was previously committed'));
return;
}
if (discarded) {
return;
}
discarded = true;
test.finishAttempt({
assertCount: 0,
commit: false,
deferredSnapshotRecordings,
errors,
logs,
passed,
retainLogs,
snapshotCount,
startingSnapshotCount
});
}
};
};
}

View file

@ -10,7 +10,6 @@ import test, {ExecutionContext, Macro} from '..';
};
test('bar has length 3', hasLength, 'bar', 3);
test('bar has length 3', [hasLength], 'bar', 3);
}
// Infer macro
@ -18,24 +17,13 @@ import test, {ExecutionContext, Macro} from '..';
const hasLength = (t: ExecutionContext, input: string, expected: number) => {};
test('bar has length 3', hasLength, 'bar', 3);
test('bar has length 3', [hasLength], 'bar', 3);
}
// Multiple macros
{
const hasLength = (t: ExecutionContext, input: string, expected: number) => {};
const hasCodePoints = (t: ExecutionContext, input: string, expected: number) => {};
test('bar has length 3', [hasLength, hasCodePoints], 'bar', 3);
}
// No title
{
const hasLength: Macro<[string, number]> = (t, input, expected) => {};
const hasCodePoints: Macro<[string, number]> = (t, input, expected) => {};
test(hasLength, 'bar', 3);
test([hasLength, hasCodePoints], 'bar', 3);
}
// No arguments

View file

@ -29,16 +29,6 @@ test('attempt with title', async t => {
attempt.commit();
});
test('multiple attempts', async t => {
const attempts = [
...await t.try([tt => tt.pass(), tt => tt.pass()]),
...await t.try('title', [tt => tt.pass(), tt => tt.pass()])
];
for (const attempt of attempts) {
attempt.commit();
}
});
{
const lengthCheck = (t: ExecutionContext, a: string, b: number): void => {
t.is(a.length, b);
@ -58,31 +48,21 @@ test('multiple attempts', async t => {
test('all possible variants to pass to t.try', async t => {
// No params
t.try(tt => tt.pass());
/* Fails as expected */ // t.try([]);
t.try([tt => tt.pass()]);
t.try([tt => tt.pass(), tt => tt.fail()]);
t.try('test', tt => tt.pass());
/* Fails as expected */ // t.try('test', []);
t.try('test', [tt => tt.pass()]);
t.try('test', [tt => tt.pass(), tt => tt.fail()]);
// Some params
t.try((tt, a, b) => tt.is(a.length, b), 'hello', 5);
/* Fails as expected */ // t.try([], 'hello', 5);
t.try([(tt, a, b) => tt.is(a.length, b)], 'hello', 5);
t.try([(tt, a, b) => tt.is(a.length, b), (tt, a, b) => tt.is(a.slice(b), '')], 'hello', 5);
t.try('test', (tt, a, b) => tt.is(a.length, b), 'hello', 5);
/* Fails as expected */ // t.try('test', [], 'hello', 5);
t.try('test', [(tt, a, b) => tt.is(a.length, b)], 'hello', 5);
t.try('test', [(tt, a, b) => tt.is(a.length, b), (tt, a, b) => tt.is(a.slice(b), '')], 'hello', 5);
// Macro with title
const macro1: Macro<[string, number]> = (tt, a, b) => tt.is(a.length, b);
macro1.title = (title, a, b) => `${title ? `${String(title)} ` : ''}str: "${String(a)}" with len: "${String(b)}"`;
const macro2: Macro<[string, number]> = (tt, a, b) => tt.is(a.slice(b), '');
t.try([macro1, macro2], 'hello', 5);
t.try('title', [macro1, macro2], 'hello', 5);
t.try(macro1, 'hello', 5);
t.try(macro2, 'hello', 5);
t.try('title', macro1, 'hello', 5);
t.try('title', macro2, 'hello', 5);
});

View file

@ -684,86 +684,6 @@ test('match applies to macros', t => {
});
});
test('arrays of macros', t => {
const expectedArgsA = [
['A'],
['B'],
['C']
];
const expectedArgsB = [
['A'],
['B'],
['D']
];
function macroFnA(a, ...rest) {
t.same(rest, expectedArgsA.shift());
a.pass();
}
macroFnA.title = prefix => `${prefix}.A`;
function macroFnB(a, ...rest) {
t.same(rest, expectedArgsB.shift());
a.pass();
}
macroFnB.title = prefix => `${prefix}.B`;
return promiseEnd(new Runner({file: import.meta.url}), runner => {
runner.on('stateChange', evt => {
if (evt.type === 'test-passed') {
t.pass();
}
});
runner.chain('A', [macroFnA, macroFnB], 'A');
runner.chain('B', [macroFnA, macroFnB], 'B');
runner.chain('C', macroFnA, 'C');
runner.chain('D', macroFnB, 'D');
}).then(() => {
t.equal(expectedArgsA.length, 0);
t.equal(expectedArgsB.length, 0);
});
});
test('match applies to arrays of macros', t => {
t.plan(1);
// Foo
function fooMacro(a) {
t.fail();
a.pass();
}
fooMacro.title = (title, firstArg) => `${firstArg}foo`;
function barMacro(avaT) {
avaT.pass();
}
barMacro.title = (title, firstArg) => `${firstArg}bar`;
function bazMacro(a) {
t.fail();
a.pass();
}
bazMacro.title = (title, firstArg) => `${firstArg}baz`;
return promiseEnd(new Runner({file: import.meta.url, match: ['foobar']}), runner => {
runner.on('stateChange', evt => {
if (evt.type === 'test-passed') {
t.equal(evt.title, 'foobar');
}
});
runner.chain([fooMacro, barMacro, bazMacro], 'foo');
runner.chain([fooMacro, barMacro, bazMacro], 'bar');
});
});
test('silently skips other tests when .only is used', t => {
t.plan(1);
return promiseEnd(new Runner({file: import.meta.url}), runner => {

View file

@ -369,68 +369,6 @@ test('try-commit accepts macros', async t => {
t.ok(result.passed);
});
test('try-commit accepts multiple macros', async t => {
const ava = newAva();
const result = await ava(async a => {
const [result1, result2] = await a.try([
b => {
t.equal(b.title, 'test ─ attempt 1');
b.pass();
},
b => {
t.equal(b.title, 'test ─ attempt 2');
b.fail();
}
]);
t.ok(result1.passed);
result1.commit();
t.notOk(result2.passed);
result2.discard();
const [result3, result4] = await a.try([
b => {
t.equal(b.title, 'test ─ attempt 3');
b.pass();
},
b => {
t.equal(b.title, 'test ─ attempt 4');
b.fail();
}
]);
t.ok(result3.passed);
result3.commit();
t.notOk(result4.passed);
result4.discard();
}).run();
t.ok(result.passed);
});
test('try-commit returns results in the same shape as when implementations are passed', async t => {
const ava = newAva();
const result = await ava(async a => {
const [result1, result2, result3] = await Promise.all([
a.try(b => b.pass()),
a.try([b => b.pass()]),
a.try([b => b.pass(), b => b.fail()])
]);
t.match(result1, {passed: true});
result1.commit();
t.equal(result2.length, 1);
t.match(result2, [{passed: true}]);
result2[0].commit();
t.equal(result3.length, 2);
t.match(result3, [{passed: true}, {passed: false}]);
result3[0].commit();
result3[1].discard();
}).run();
t.ok(result.passed);
});
test('try-commit abides timeout', async t => {
const ava = newAva();
const result1 = await ava(async a => {

View file

@ -0,0 +1,8 @@
{
"type": "module",
"ava": {
"files": [
"*"
]
}
}

View file

@ -0,0 +1,3 @@
import test from 'ava';
test('title', []);

View file

@ -0,0 +1,5 @@
import test from 'ava';
test('try', async t => {
await t.try([]);
});

View file

@ -0,0 +1,13 @@
import test from '@ava/test';
import {fixture} from '../helpers/exec.js';
test('test()', async t => {
const result = await t.throwsAsync(fixture(['test.js']));
t.regex(result.stdout, /AVA 4 no longer supports multiple implementations/);
});
test('t.try()', async t => {
const result = await t.throwsAsync(fixture(['try.js']));
t.regex(result.stdout, /AVA 4 no longer supports t\.try\(\) with multiple implementations/);
});