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:
parent
59b035125a
commit
d7c2b90ae4
14 changed files with 206 additions and 375 deletions
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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
68
index.d.ts
vendored
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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};
|
||||
}
|
||||
|
|
|
|||
113
lib/runner.js
113
lib/runner.js
|
|
@ -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,
|
||||
|
|
|
|||
142
lib/test.js
142
lib/test.js
|
|
@ -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
|
||||
});
|
||||
}
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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 => {
|
||||
|
|
|
|||
|
|
@ -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 => {
|
||||
|
|
|
|||
8
test/multiple-implementations/fixtures/package.json
Normal file
8
test/multiple-implementations/fixtures/package.json
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"type": "module",
|
||||
"ava": {
|
||||
"files": [
|
||||
"*"
|
||||
]
|
||||
}
|
||||
}
|
||||
3
test/multiple-implementations/fixtures/test.js
Normal file
3
test/multiple-implementations/fixtures/test.js
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
import test from 'ava';
|
||||
|
||||
test('title', []);
|
||||
5
test/multiple-implementations/fixtures/try.js
Normal file
5
test/multiple-implementations/fixtures/try.js
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
import test from 'ava';
|
||||
|
||||
test('try', async t => {
|
||||
await t.try([]);
|
||||
});
|
||||
13
test/multiple-implementations/test.js
Normal file
13
test/multiple-implementations/test.js
Normal 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/);
|
||||
});
|
||||
Loading…
Reference in a new issue