Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Core: Add test.each. #1569

Merged
merged 23 commits into from
May 16, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
3333757
Core: Add test.each.
ventuno Mar 19, 2021
68ff879
Merge branch 'main' into ftr-1568
ventuno Apr 3, 2021
0360909
Core: update test.each API.
ventuno Apr 3, 2021
f3aecd8
Merge branch 'main' into ftr-1568
ventuno Apr 6, 2021
153786a
Core: add support for 1D arrays in test.each.
ventuno Apr 6, 2021
5d725ab
Core: add support for each.todo and additional assertions.
ventuno Apr 6, 2021
45c7175
Test: update page title and docs.
ventuno Apr 6, 2021
603c88b
Test: add test with then-able result.
ventuno Apr 7, 2021
03aa8d1
Docs: update docs with then-able example.
ventuno Apr 7, 2021
998c349
Docs: add sample implementation.
ventuno Apr 7, 2021
9d3721b
Core: correct error message.
ventuno Apr 7, 2021
7460dd3
Core: simplify skip.
ventuno Apr 7, 2021
a335a59
Core: refactor test and each into addTestWithData.
ventuno Apr 7, 2021
07ee1a8
Merge branch 'main' into ftr-1568
ventuno Apr 14, 2021
5502356
Merge branch 'main' into ftr-1568
ventuno Apr 16, 2021
820922f
Merge branch 'main' into ftr-1568
ventuno May 2, 2021
551de8f
Merge branch 'main' into ftr-1568
ventuno May 8, 2021
44c26f7
Merge branch 'main' into ftr-1568
ventuno May 9, 2021
cc91756
Core: remove special handling for 1d arrays in test.each.
ventuno May 9, 2021
34c3f0e
Core: update test function names.
ventuno May 9, 2021
080dbb7
Merge branch 'main' into ftr-1568
ventuno May 9, 2021
e525dd1
Core: update key formatting to use [].
ventuno May 14, 2021
74ab9f0
Core: update doc version_added and categories.
ventuno May 15, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Gruntfile.js
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,8 @@ module.exports = function( grunt ) {
"test/string-filter.html",
"test/module-skip.html",
"test/module-todo.html",
"test/each.html",
"test/only-each.html",

// ensure this is last - it has the potential to drool
// and omit subsequent tests during coverage runs
Expand Down
73 changes: 73 additions & 0 deletions docs/QUnit/test.each.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
---
layout: default
title: QUnit.test.each()
excerpt: Add a parameterized test to run.
categories:
- main
version_added: "unreleased"
---

`QUnit.test.each( name, data, callback )`

Add a parameterized test to run.

| parameter | description |
|-----------|-------------|
| `name` (string) | Title of unit being tested |
| `data` (Array) | Array of arrays of parameters to be passed as input to each test. This can also be specified as a 1D array of primitives |
| `callback` (function) | Function to close over assertions |

#### Callback parameters: `callback( assert, args )`:

| parameter | description |
|-----------|-------------|
| `assert` (object) | A new instance object with the [assertion methods](../assert/index.md) |
| `args` (any) | All input parameters. The original array is passed. Array destructuring can be used to unpack input values |

### Description

Add a parameterized test to run using `QUnit.test.each()`. `QUnit.test.each()` generates multiple calls to `QUnit.test()` so `then`-able behavior is maintained.


The `assert` argument to the callback contains all of QUnit's [assertion methods](../assert/index.md). Use this argument to call your test assertions.
`QUnit.test.only.each`, `QUnit.test.skip.each` and `QUnit.test.todo.each` are also available.

See also:
* [`QUnit.test.only()`](./test.only.md)
* [`QUnit.test.skip()`](./test.skip.md)
* [`QUnit.test.todo()`](./test.todo.md)


### Examples

A practical example, using the assert argument and no globals.

```js
function square( x ) {
return x * x;
}

function isEven( x ) {
return x % 2 === 0;
}

function isAsyncEven( x ) {
return new Promise( resolve => {
return resolve( isEven( x ) );
} );
}

QUnit.test.each( "square()", [ [ 2, 4 ], [ 3, 9 ] ], ( assert, [ value, expected ] ) => {
assert.equal( square( value ), expected, `square(${value})` );
});

QUnit.test.each( "isEven()", [ 2, 4 ], ( assert, value ) => {
assert.true( isEven( value ), `isEven(${value})` );
});

QUnit.test.each( "isAsyncEven()", [ 2, 4 ], ( assert, value ) => {
return isAsyncEven( value ).then( ( value ) => {
assert.true( isAsyncEven( value ), `isAsyncEven(${value})` );
} );
});
```
132 changes: 94 additions & 38 deletions src/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,7 @@ Test.prototype = {
}

function runTest( test ) {
const promise = test.callback.call( test.testEnvironment, test.assert );
const promise = test.callback.call( test.testEnvironment, test.assert, test.params );
test.resolvePromise( promise );

// If the test has a "lock" on it, but the timeout is 0, then we push a
Expand Down Expand Up @@ -674,66 +674,122 @@ function checkPollution() {
}
}

function addTestWithData( data ) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like this abstraction a lot, did you mean to make more use of it? I went ahead and did so as part of #1620.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This actually came from @smcclure15's recommendation. Glad it's useful!

if ( focused || config.currentModule.ignored ) {
return;
}

const newTest = new Test( data );

newTest.queue();
}

let focused = false; // indicates that the "only" filter was used

// Will be exposed as QUnit.test
export function test( testName, callback ) {
addTestWithData( {
testName: testName,
callback: callback
} );
}

function todo( testName, data, callback ) {
if ( focused || config.currentModule.ignored ) {
return;
}

const newTest = new Test( {
testName: testName,
callback: callback
testName,
callback,
todo: true,
params: data
} );

newTest.queue();
}

extend( test, {
todo: function todo( testName, callback ) {
if ( focused || config.currentModule.ignored ) {
return;
}
function skip( testName ) {
if ( focused || config.currentModule.ignored ) {
return;
}

const newTest = new Test( {
testName,
callback,
todo: true
} );
const test = new Test( {
testName: testName,
skip: true
} );

newTest.queue();
},
skip: function skip( testName ) {
if ( focused || config.currentModule.ignored ) {
return;
}
test.queue();
}
function only( testName, data, callback ) {
if ( config.currentModule.ignored ) {
return;
}
if ( !focused ) {
config.queue.length = 0;
focused = true;
}

const test = new Test( {
testName: testName,
skip: true
} );
const newTest = new Test( {
testName: testName,
callback: callback,
params: data
} );

test.queue();
},
only: function only( testName, callback ) {
if ( config.currentModule.ignored ) {
return;
}
if ( !focused ) {
config.queue.length = 0;
focused = true;
}
newTest.queue();
}

const newTest = new Test( {
testName: testName,
callback: callback
} );
function makeEachTestName( testName, argument ) {
return `${testName} [${argument}]`;
}

function runEach( data, eachFn ) {
if ( Array.isArray( data ) ) {
data.forEach( eachFn );
} else {
throw new Error(
`test.each expects an array of arrays or an array of primitives as the expected input.
${typeof data} was found instead.`
);
}
}

newTest.queue();
extend( test, {
todo: function( testName, callback ) {
todo( testName, undefined, callback );
},
skip,
only: function( testName, callback ) {
only( testName, undefined, callback );
},
each: function( testName, data, callback ) {
runEach( data, ( datum, i ) => {
addTestWithData( {
testName: makeEachTestName( testName, i ),
callback: callback,
params: datum
} );
} );
}
} );

test.todo.each = function( testName, data, callback ) {
runEach( data, ( datum, i ) => {
todo( makeEachTestName( testName, i ), datum, callback );
} );
};
test.skip.each = function( testName, data ) {
runEach( data, ( _, i ) => {
skip( makeEachTestName( testName, i ) );
} );
};

test.only.each = function( testName, data, callback ) {
runEach( data, ( datum, i ) => {
only( makeEachTestName( testName, i ), datum, callback );
} );
};

// Resets config.timeout with a new timeout duration.
export function resetTestTimeout( timeoutDuration ) {
clearTimeout( config.timeout );
Expand Down
14 changes: 14 additions & 0 deletions test/each.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>QUnit.test.each Suite</title>
<link rel="stylesheet" href="../qunit/qunit.css">
<script src="../qunit/qunit.js"></script>
<script src="main/each.js"></script>
</head>
<body>
<div id="qunit"></div>
<div id="qunit-fixture">test markup</div>
</body>
</html>
32 changes: 32 additions & 0 deletions test/main/each.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
QUnit.module( "test.each", function() {
QUnit.test.each( "test.each", [ [ 1, 2, 3 ], [ 1, 1, 2 ] ], function( assert, data ) {
assert.strictEqual( data[ 0 ] + data[ 1 ], data[ 2 ] );
} );
QUnit.test.each( "test.each returning a Promise", [ [ 1, 2, 3 ], [ 1, 1, 2 ] ], function( assert, data ) {
function sum( a, b ) {
return new Promise( function( resolve ) {
resolve( a + b );
} );
}
return sum( data[ 0 ], data[ 1 ] ).then( function( result ) { assert.strictEqual( result, data[ 2 ] ); } );
} );
QUnit.test.each( "test.each 1D", [ 1, [], "some" ], function( assert, value ) {
assert.true( Boolean( value ) );
} );
QUnit.test.each( "test.each fails with non-array input", [ "something", 1, undefined, null, {} ], function( assert, value ) {
assert.throws( function() {
QUnit.test.each( "test.each 1D", value, function() { } );
} );
} );
} );
QUnit.module( "test.skip.each", function() {
QUnit.test( "do run", function( assert ) { assert.true( true ); } );
QUnit.test.skip.each( "test.skip.each", [ [ 1, 2, 3 ], [ 1, 1, 2 ] ], function( assert ) {
assert.true( false );
} );
} );
QUnit.module( "test.todo.each", function() {
QUnit.test.todo.each( "test.todo.each", [ [ 1, 2, 3 ], [ 1, 1, 2 ] ], function( assert ) {
assert.true( false );
} );
} );
9 changes: 9 additions & 0 deletions test/main/only-each.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
QUnit.module.only( "test.each.only", function() {
QUnit.test( "don't run", function( assert ) { assert.true( false ); } );
QUnit.test.only.each( "test.each.only", [ [ 1, 2, 3 ], [ 1, 1, 2 ] ], function( assert, data ) {
assert.strictEqual( data[ 0 ] + data[ 1 ], data[ 2 ] );
} );
QUnit.test.only.each( "test.each.only 1D", [ 1, [], "some" ], function( assert, value ) {
assert.true( Boolean( value ) );
} );
} );
14 changes: 14 additions & 0 deletions test/only-each.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>QUnit.test.each.only Suite</title>
<link rel="stylesheet" href="../qunit/qunit.css">
<script src="../qunit/qunit.js"></script>
<script src="main/only-each.js"></script>
</head>
<body>
<div id="qunit"></div>
<div id="qunit-fixture">test markup</div>
</body>
</html>