Skip to content

Commit

Permalink
Pass an undefined title argument to macro-title functions
Browse files Browse the repository at this point in the history
Previously, AVA would pass the empty string. By passing an undefined value, the title function can use argument defaults which is more natural.

This is a breaking change for users who were concatenating `title`, under the assumption it was an empty string.
  • Loading branch information
novemberborn authored Oct 28, 2018
1 parent 37390e6 commit aa35f15
Show file tree
Hide file tree
Showing 6 changed files with 13 additions and 13 deletions.
2 changes: 1 addition & 1 deletion docs/recipes/typescript.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ import test, {Macro} from 'ava';
const macro: Macro = (t, input: string, expected: number) => {
t.is(eval(input), expected);
};
macro.title = (providedTitle: string, input: string, expected: number) => `${providedTitle} ${input} = ${expected}`.trim();
macro.title = (providedTitle = '', input: string, expected: number) => `${providedTitle} ${input} = ${expected}`.trim();

test(macro, '2 + 2', 4);
test(macro, '2 * 3', 6);
Expand Down
4 changes: 2 additions & 2 deletions index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -390,13 +390,13 @@ export interface Macro<Context = {}> {
* Implement this function to generate a test (or hook) title whenever this macro is used. `providedTitle` contains
* the title provided when the test or hook was declared. Also receives the remaining test arguments.
*/
title?: (providedTitle: string, ...args: Array<any>) => string;
title?: (providedTitle: string | undefined, ...args: Array<any>) => string;
}

/** A reusable test or hook implementation, for tests & hooks declared with the `.cb` modifier. */
export interface CbMacro<Context = {}> {
(t: CbExecutionContext<Context>, ...args: Array<any>): ImplementationResult;
title?: (providedTitle: string, ...args: Array<any>) => string;
title?: (providedTitle: string | undefined, ...args: Array<any>) => string;
}

export interface TestInterface<Context = {}> {
Expand Down
4 changes: 2 additions & 2 deletions index.js.flow
Original file line number Diff line number Diff line change
Expand Up @@ -403,13 +403,13 @@ export interface Macro<Context = {}> {
* Implement this function to generate a test (or hook) title whenever this macro is used. `providedTitle` contains
* the title provided when the test or hook was declared. Also receives the remaining test arguments.
*/
title?: (providedTitle: string, ...args: Array<any>) => string;
title?: (providedTitle: string | void, ...args: Array<any>) => string;
}

/** A reusable test or hook implementation, for tests & hooks declared with the `.cb` modifier. */
export interface CbMacro<Context = {}> {
(t: CbExecutionContext<Context>, ...args: Array<any>): ImplementationResult;
title?: (providedTitle: string, ...args: Array<any>) => string;
title?: (providedTitle: string | void, ...args: Array<any>) => string;
}

export interface TestInterface<Context = {}> {
Expand Down
10 changes: 5 additions & 5 deletions lib/runner.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ class Runner extends Emittery {

const specifiedTitle = typeof args[0] === 'string' ?
args.shift() :
'';
undefined;
const implementations = Array.isArray(args[0]) ?
args.shift() :
args.splice(0, 1);
Expand All @@ -65,7 +65,7 @@ class Runner extends Emittery {
throw new TypeError('`todo` tests are not allowed to have an implementation. Use `test.skip()` for tests with an implementation.');
}

if (specifiedTitle === '') {
if (specifiedTitle === undefined || specifiedTitle === '') {
throw new TypeError('`todo` tests require a title');
}

Expand Down Expand Up @@ -97,14 +97,14 @@ class Runner extends Emittery {

for (const implementation of implementations) {
let title = implementation.title ?
implementation.title.apply(implementation, [specifiedTitle].concat(args)) :
implementation.title(specifiedTitle, ...args) :
specifiedTitle;

if (typeof title !== 'string') {
if (title !== undefined && typeof title !== 'string') {
throw new TypeError('Test & hook titles must be strings');
}

if (title === '') {
if (title === undefined || title === '') {
if (metadata.type === 'test') {
throw new TypeError('Tests must have a title');
} else if (metadata.always) {
Expand Down
4 changes: 2 additions & 2 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -691,14 +691,14 @@ function macro(t, input, expected) {
t.is(eval(input), expected);
}

macro.title = (providedTitle, input, expected) => `${providedTitle} ${input} = ${expected}`.trim();
macro.title = (providedTitle = '', input, expected) => `${providedTitle} ${input} = ${expected}`.trim();

test(macro, '2 + 2', 4);
test(macro, '2 * 3', 6);
test('providedTitle', macro, '3 * 3', 9);
```

The `providedTitle` argument defaults to an empty string if the user does not supply a string title. This allows for easy concatenation without having to worry about `null` / `undefined`. It is worth remembering that the empty string is considered a falsy value, so you can still use `if(providedTitle) {...}`.
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:

Expand Down
2 changes: 1 addition & 1 deletion test/runner.js
Original file line number Diff line number Diff line change
Expand Up @@ -611,7 +611,7 @@ test('macros: Customize test names attaching a `title` function', t => {
avaT.pass();
}

macroFn.title = (title, firstArg) => (title || 'default') + firstArg;
macroFn.title = (title = 'default', firstArg) => title + firstArg;

return promiseEnd(new Runner(), runner => {
runner.on('stateChange', evt => {
Expand Down

0 comments on commit aa35f15

Please sign in to comment.