Skip to content

Commit d7c2b90

Browse files
committed
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.
1 parent 59b0351 commit d7c2b90

File tree

14 files changed

+200
-369
lines changed

14 files changed

+200
-369
lines changed

docs/01-writing-tests.md

Lines changed: 0 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -319,21 +319,4 @@ test('providedTitle', macro, '3 * 3', 9);
319319

320320
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.
321321

322-
You can also pass arrays of macro functions:
323-
324-
```js
325-
const safeEval = require('safe-eval');
326-
327-
function evalMacro(t, input, expected) {
328-
t.is(eval(input), expected);
329-
}
330-
331-
function safeEvalMacro(t, input, expected) {
332-
t.is(safeEval(input), expected);
333-
}
334-
335-
test([evalMacro, safeEvalMacro], '2 + 2', 4);
336-
test([evalMacro, safeEvalMacro], '2 * 3', 6);
337-
```
338-
339322
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.

docs/03-assertions.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -330,7 +330,7 @@ AVA 3 supports an `options` object that lets you select a specific snapshot, fo
330330

331331
In AVA 3, you cannot update snapshots while using `t.snapshot.skip()`.
332332

333-
### `.try(title?, implementation | macro | macro[], ...args?)`
333+
### `.try(title?, implementation | macro, ...args?)`
334334

335335
`.try()` allows you to *try* assertions without causing the test to fail.
336336

index.d.ts

Lines changed: 26 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -409,24 +409,11 @@ export interface TryFn<Context = unknown> {
409409
*/
410410
<Args extends any[]>(title: string, fn: EitherMacro<Args, Context>, ...args: Args): Promise<TryResult>;
411411

412-
/**
413-
* Attempt to run some assertions. The result must be explicitly committed or discarded or else
414-
* the test will fail. A macro may be provided. The title may help distinguish attempts from
415-
* one another.
416-
*/
417-
<Args extends any[]>(title: string, fn: [EitherMacro<Args, Context>, ...Array<EitherMacro<Args, Context>>], ...args: Args): Promise<TryResult[]>;
418-
419412
/**
420413
* Attempt to run some assertions. The result must be explicitly committed or discarded or else
421414
* the test will fail. A macro may be provided.
422415
*/
423416
<Args extends any[]>(fn: EitherMacro<Args, Context>, ...args: Args): Promise<TryResult>;
424-
425-
/**
426-
* Attempt to run some assertions. The result must be explicitly committed or discarded or else
427-
* the test will fail. A macro may be provided.
428-
*/
429-
<Args extends any[]>(fn: [EitherMacro<Args, Context>, ...Array<EitherMacro<Args, Context>>], ...args: Args): Promise<TryResult[]>;
430417
}
431418

432419
export interface AssertionError extends Error {}
@@ -483,18 +470,15 @@ export type Macro<Args extends any[], Context = unknown> = UntitledMacro<Args, C
483470

484471
export type EitherMacro<Args extends any[], Context> = Macro<Args, Context> | UntitledMacro<Args, Context>;
485472

486-
/** Alias for a single macro, or an array of macros. */
487-
export type OneOrMoreMacros<Args extends any[], Context> = EitherMacro<Args, Context> | [EitherMacro<Args, Context>, ...Array<EitherMacro<Args, Context>>];
488-
489473
export interface TestInterface<Context = unknown> {
490474
/** Declare a concurrent test. */
491475
(title: string, implementation: Implementation<Context>): void;
492476

493-
/** Declare a concurrent test that uses one or more macros. Additional arguments are passed to the macro. */
494-
<T extends any[]>(title: string, macros: OneOrMoreMacros<T, Context>, ...rest: T): void;
477+
/** Declare a concurrent test that uses a macro. Additional arguments are passed to the macro. */
478+
<T extends any[]>(title: string, macro: EitherMacro<T, Context>, ...rest: T): void;
495479

496-
/** Declare a concurrent test that uses one or more macros. The macro is responsible for generating a unique test title. */
497-
<T extends any[]>(macros: OneOrMoreMacros<T, Context>, ...rest: T): void;
480+
/** Declare a concurrent test that uses a macro. The macro is responsible for generating a unique test title. */
481+
<T extends any[]>(macro: EitherMacro<T, Context>, ...rest: T): void;
498482

499483
/** Declare a hook that is run once, after all tests have passed. */
500484
after: AfterInterface<Context>;
@@ -528,10 +512,10 @@ export interface AfterInterface<Context = unknown> {
528512
(title: string, implementation: Implementation<Context>): void;
529513

530514
/** Declare a hook that is run once, after all tests have passed. Additional arguments are passed to the macro. */
531-
<T extends any[]>(title: string, macros: OneOrMoreMacros<T, Context>, ...rest: T): void;
515+
<T extends any[]>(title: string, macro: EitherMacro<T, Context>, ...rest: T): void;
532516

533517
/** Declare a hook that is run once, after all tests have passed. */
534-
<T extends any[]>(macros: OneOrMoreMacros<T, Context>, ...rest: T): void;
518+
<T extends any[]>(macro: EitherMacro<T, Context>, ...rest: T): void;
535519

536520
/** Declare a hook that is run once, after all tests are done. */
537521
always: AlwaysInterface<Context>;
@@ -547,10 +531,10 @@ export interface AlwaysInterface<Context = unknown> {
547531
(title: string, implementation: Implementation<Context>): void;
548532

549533
/** Declare a hook that is run once, after all tests are done. Additional arguments are passed to the macro. */
550-
<T extends any[]>(title: string, macros: OneOrMoreMacros<T, Context>, ...rest: T): void;
534+
<T extends any[]>(title: string, macro: EitherMacro<T, Context>, ...rest: T): void;
551535

552536
/** Declare a hook that is run once, after all tests are done. */
553-
<T extends any[]>(macros: OneOrMoreMacros<T, Context>, ...rest: T): void;
537+
<T extends any[]>(macro: EitherMacro<T, Context>, ...rest: T): void;
554538

555539
skip: HookSkipInterface<Context>;
556540
}
@@ -563,10 +547,10 @@ export interface BeforeInterface<Context = unknown> {
563547
(title: string, implementation: Implementation<Context>): void;
564548

565549
/** Declare a hook that is run once, before all tests. Additional arguments are passed to the macro. */
566-
<T extends any[]>(title: string, macros: OneOrMoreMacros<T, Context>, ...rest: T): void;
550+
<T extends any[]>(title: string, macro: EitherMacro<T, Context>, ...rest: T): void;
567551

568552
/** Declare a hook that is run once, before all tests. */
569-
<T extends any[]>(macros: OneOrMoreMacros<T, Context>, ...rest: T): void;
553+
<T extends any[]>(macro: EitherMacro<T, Context>, ...rest: T): void;
570554

571555
skip: HookSkipInterface<Context>;
572556
}
@@ -576,16 +560,16 @@ export interface FailingInterface<Context = unknown> {
576560
(title: string, implementation: Implementation<Context>): void;
577561

578562
/**
579-
* Declare a concurrent test that uses one or more macros. Additional arguments are passed to the macro.
563+
* Declare a concurrent test that uses a macro. Additional arguments are passed to the macro.
580564
* The test is expected to fail.
581565
*/
582-
<T extends any[]>(title: string, macros: OneOrMoreMacros<T, Context>, ...rest: T): void;
566+
<T extends any[]>(title: string, macro: EitherMacro<T, Context>, ...rest: T): void;
583567

584568
/**
585-
* Declare a concurrent test that uses one or more macros. The macro is responsible for generating a unique test title.
569+
* Declare a concurrent test that uses a macro. The macro is responsible for generating a unique test title.
586570
* The test is expected to fail.
587571
*/
588-
<T extends any[]>(macros: OneOrMoreMacros<T, Context>, ...rest: T): void;
572+
<T extends any[]>(macro: EitherMacro<T, Context>, ...rest: T): void;
589573

590574
only: OnlyInterface<Context>;
591575
skip: SkipInterface<Context>;
@@ -599,40 +583,40 @@ export interface HookSkipInterface<Context = unknown> {
599583
(title: string, implementation: Implementation<Context>): void;
600584

601585
/** Skip this hook. */
602-
<T extends any[]>(title: string, macros: OneOrMoreMacros<T, Context>, ...rest: T): void;
586+
<T extends any[]>(title: string, macro: EitherMacro<T, Context>, ...rest: T): void;
603587

604588
/** Skip this hook. */
605-
<T extends any[]>(macros: OneOrMoreMacros<T, Context>, ...rest: T): void;
589+
<T extends any[]>(macro: EitherMacro<T, Context>, ...rest: T): void;
606590
}
607591

608592
export interface OnlyInterface<Context = unknown> {
609593
/** Declare a test. Only this test and others declared with `.only()` are run. */
610594
(title: string, implementation: Implementation<Context>): void;
611595

612596
/**
613-
* Declare a test that uses one or more macros. Additional arguments are passed to the macro.
597+
* Declare a test that uses a macro. Additional arguments are passed to the macro.
614598
* Only this test and others declared with `.only()` are run.
615599
*/
616-
<T extends any[]>(title: string, macros: OneOrMoreMacros<T, Context>, ...rest: T): void;
600+
<T extends any[]>(title: string, macro: EitherMacro<T, Context>, ...rest: T): void;
617601

618602
/**
619-
* Declare a test that uses one or more macros. The macro is responsible for generating a unique test title.
603+
* Declare a test that uses a macro. The macro is responsible for generating a unique test title.
620604
* Only this test and others declared with `.only()` are run.
621605
*/
622-
<T extends any[]>(macros: OneOrMoreMacros<T, Context>, ...rest: T): void;
606+
<T extends any[]>(macro: EitherMacro<T, Context>, ...rest: T): void;
623607
}
624608

625609
export interface SerialInterface<Context = unknown> {
626610
/** Declare a serial test. */
627611
(title: string, implementation: Implementation<Context>): void;
628612

629-
/** Declare a serial test that uses one or more macros. Additional arguments are passed to the macro. */
630-
<T extends any[]>(title: string, macros: OneOrMoreMacros<T, Context>, ...rest: T): void;
613+
/** Declare a serial test that uses a macro. Additional arguments are passed to the macro. */
614+
<T extends any[]>(title: string, macro: EitherMacro<T, Context>, ...rest: T): void;
631615

632616
/**
633-
* Declare a serial test that uses one or more macros. The macro is responsible for generating a unique test title.
617+
* Declare a serial test that uses a macro. The macro is responsible for generating a unique test title.
634618
*/
635-
<T extends any[]>(macros: OneOrMoreMacros<T, Context>, ...rest: T): void;
619+
<T extends any[]>(macro: EitherMacro<T, Context>, ...rest: T): void;
636620

637621
/** Declare a serial hook that is run once, after all tests have passed. */
638622
after: AfterInterface<Context>;
@@ -659,10 +643,10 @@ export interface SkipInterface<Context = unknown> {
659643
(title: string, implementation: Implementation<Context>): void;
660644

661645
/** Skip this test. */
662-
<T extends any[]>(title: string, macros: OneOrMoreMacros<T, Context>, ...rest: T): void;
646+
<T extends any[]>(title: string, macro: EitherMacro<T, Context>, ...rest: T): void;
663647

664648
/** Skip this test. */
665-
<T extends any[]>(macros: OneOrMoreMacros<T, Context>, ...rest: T): void;
649+
<T extends any[]>(macro: EitherMacro<T, Context>, ...rest: T): void;
666650
}
667651

668652
export interface TodoDeclaration {

lib/parse-test-args.js

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,26 @@
1-
const normalize = title => typeof title === 'string' ? title.trim().replace(/\s+/g, ' ') : title;
1+
const buildTitle = (raw, implementation, args) => {
2+
let value = implementation && implementation.title ? implementation.title(raw, ...args) : raw;
3+
const isValid = typeof value === 'string';
4+
if (isValid) {
5+
value = value.trim().replace(/\s+/g, ' ');
6+
}
7+
8+
return {
9+
raw,
10+
value,
11+
isSet: value !== undefined,
12+
isValid,
13+
isEmpty: !isValid || value === ''
14+
};
15+
};
216

317
export default function parseTestArgs(args) {
418
const rawTitle = typeof args[0] === 'string' ? args.shift() : undefined;
5-
const receivedImplementationArray = Array.isArray(args[0]);
6-
const implementations = receivedImplementationArray ? args.shift() : args.splice(0, 1);
19+
const implementation = args.shift();
720

8-
const buildTitle = implementation => {
9-
const title = normalize(implementation.title ? implementation.title(rawTitle, ...args) : rawTitle);
10-
return {title, isSet: typeof title !== 'undefined', isValid: typeof title === 'string', isEmpty: !title};
21+
return {
22+
args,
23+
implementation,
24+
title: buildTitle(rawTitle, implementation, args)
1125
};
12-
13-
return {args, buildTitle, implementations, rawTitle, receivedImplementationArray};
1426
}

lib/runner.js

Lines changed: 53 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -100,94 +100,95 @@ export default class Runner extends Emittery {
100100

101101
metadata.taskIndex = this.nextTaskIndex++;
102102

103-
const {args, buildTitle, implementations, rawTitle} = parseTestArgs(testArgs);
103+
const {args, implementation, title} = parseTestArgs(testArgs);
104104

105105
if (this.checkSelectedByLineNumbers) {
106106
metadata.selected = this.checkSelectedByLineNumbers();
107107
}
108108

109109
if (metadata.todo) {
110-
if (implementations.length > 0) {
110+
if (implementation) {
111111
throw new TypeError('`todo` tests are not allowed to have an implementation. Use `test.skip()` for tests with an implementation.');
112112
}
113113

114-
if (!rawTitle) { // Either undefined or a string.
114+
if (!title.raw) { // Either undefined or a string.
115115
throw new TypeError('`todo` tests require a title');
116116
}
117117

118-
if (!this.registerUniqueTitle(rawTitle)) {
119-
throw new Error(`Duplicate test title: ${rawTitle}`);
118+
if (!this.registerUniqueTitle(title.value)) {
119+
throw new Error(`Duplicate test title: ${title.value}`);
120120
}
121121

122122
// --match selects TODO tests.
123-
if (this.match.length > 0 && matcher([rawTitle], this.match).length === 1) {
123+
if (this.match.length > 0 && matcher([title.value], this.match).length === 1) {
124124
metadata.exclusive = true;
125125
this.runOnlyExclusive = true;
126126
}
127127

128-
this.tasks.todo.push({title: rawTitle, metadata});
128+
this.tasks.todo.push({title: title.value, metadata});
129129
this.emit('stateChange', {
130130
type: 'declared-test',
131-
title: rawTitle,
131+
title: title.value,
132132
knownFailing: false,
133133
todo: true
134134
});
135135
} else {
136-
if (implementations.length === 0) {
136+
if (!implementation) {
137137
throw new TypeError('Expected an implementation. Use `test.todo()` for tests without an implementation.');
138138
}
139139

140-
for (const implementation of implementations) {
141-
let {title, isSet, isValid, isEmpty} = buildTitle(implementation);
140+
if (Array.isArray(implementation)) {
141+
throw new TypeError('AVA 4 no longer supports multiple implementations.');
142+
}
143+
144+
if (title.isSet && !title.isValid) {
145+
throw new TypeError('Test & hook titles must be strings');
146+
}
142147

143-
if (isSet && !isValid) {
144-
throw new TypeError('Test & hook titles must be strings');
148+
let fallbackTitle = title.value;
149+
if (title.isEmpty) {
150+
if (metadata.type === 'test') {
151+
throw new TypeError('Tests must have a title');
152+
} else if (metadata.always) {
153+
fallbackTitle = `${metadata.type}.always hook`;
154+
} else {
155+
fallbackTitle = `${metadata.type} hook`;
145156
}
157+
}
146158

147-
if (isEmpty) {
148-
if (metadata.type === 'test') {
149-
throw new TypeError('Tests must have a title');
150-
} else if (metadata.always) {
151-
title = `${metadata.type}.always hook`;
152-
} else {
153-
title = `${metadata.type} hook`;
154-
}
159+
if (metadata.type === 'test' && !this.registerUniqueTitle(title.value)) {
160+
throw new Error(`Duplicate test title: ${title.value}`);
161+
}
162+
163+
const task = {
164+
title: title.value || fallbackTitle,
165+
implementation,
166+
args,
167+
metadata: {...metadata}
168+
};
169+
170+
if (metadata.type === 'test') {
171+
if (this.match.length > 0) {
172+
// --match overrides .only()
173+
task.metadata.exclusive = matcher([title.value], this.match).length === 1;
155174
}
156175

157-
if (metadata.type === 'test' && !this.registerUniqueTitle(title)) {
158-
throw new Error(`Duplicate test title: ${title}`);
176+
if (task.metadata.exclusive) {
177+
this.runOnlyExclusive = true;
159178
}
160179

161-
const task = {
162-
title,
163-
implementation,
164-
args,
165-
metadata: {...metadata}
166-
};
180+
this.tasks[metadata.serial ? 'serial' : 'concurrent'].push(task);
167181

168-
if (metadata.type === 'test') {
169-
if (this.match.length > 0) {
170-
// --match overrides .only()
171-
task.metadata.exclusive = matcher([title], this.match).length === 1;
172-
}
173-
174-
if (task.metadata.exclusive) {
175-
this.runOnlyExclusive = true;
176-
}
177-
178-
this.tasks[metadata.serial ? 'serial' : 'concurrent'].push(task);
179-
180-
this.snapshots.touch(title, metadata.taskIndex);
181-
182-
this.emit('stateChange', {
183-
type: 'declared-test',
184-
title,
185-
knownFailing: metadata.failing,
186-
todo: false
187-
});
188-
} else if (!metadata.skipped) {
189-
this.tasks[metadata.type + (metadata.always ? 'Always' : '')].push(task);
190-
}
182+
this.snapshots.touch(title.value, metadata.taskIndex);
183+
184+
this.emit('stateChange', {
185+
type: 'declared-test',
186+
title: title.value,
187+
knownFailing: metadata.failing,
188+
todo: false
189+
});
190+
} else if (!metadata.skipped) {
191+
this.tasks[metadata.type + (metadata.always ? 'Always' : '')].push(task);
191192
}
192193
}
193194
}, {

0 commit comments

Comments
 (0)