-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
299fe1f
commit 14648c2
Showing
5 changed files
with
271 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
/node_modules/ | ||
/npm-debug.log* |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,123 @@ | ||
# test-until | ||
|
||
A utility that returns a promise that resolves when the passed function returns true. It works like Jasmine's `waitsFor`, but Promise based. | ||
|
||
## Installation | ||
|
||
Node.js: | ||
|
||
`npm install --save[-dev] test-until` | ||
|
||
## Requirements | ||
|
||
test-until requires a global variable `Promise` which needs to be a A+ compliant promise implementation (such as is available by default in [most modern browsers](http://caniuse.com/#feat=promises)). If you need to polyfill `Promise` for any reason (such as supporting IE 11), I recommend [promise-polyfill](https://github.com/taylorhakes/promise-polyfill). | ||
|
||
## Usage | ||
|
||
```javascript | ||
var promise = until(checkFunc, message, timeout) | ||
``` | ||
|
||
* `checkFunc` - A function that returns a truthy value once the promise should be resolved. `until` will call this function repeatedly until it returns `true` or the timeout elapses. | ||
* `message` *(optional)* - A message to help identify failing cases. For example, setting this to `"value == 42"` will reject with an error of `"timed out waiting until value == 42"` if it times out. Defaults to `"something happens"`. | ||
* `timeout` *(optional)* - The number of milliseconds to wait before timing out and rejecting the promise. Defaults to `1000`. | ||
|
||
The three arguments can be supplied in any order depending on your preferences. For example, putting the message first can make the line read a little more like English: | ||
|
||
```javascript | ||
until('we know the answer to life, the universe, and everything', function () { return val === 42 }, 500) | ||
``` | ||
|
||
## Example with Test Framework | ||
|
||
Here's an example using the [Mocha](https://mochajs.org/) testing framework. | ||
|
||
```javascript | ||
var until = require('test-until') | ||
|
||
describe('something', function () { | ||
it('tests things', function (done) { | ||
var val = 0 | ||
setTimeout(function () { val = 42 }, 100) | ||
var promise = until(function () { val === 42 }) | ||
promise.then(function() { | ||
// after 100ms, `val` will be set to `42` | ||
// and the promise returned from `until` will resolve | ||
done() | ||
}) | ||
}) | ||
}) | ||
``` | ||
|
||
test-until reads and works even better with access to `async`/`await` in your tests, allowing you to wait for multiple async conditions with no callbacks in a manner that reads much like English: | ||
|
||
```javascript | ||
import until from 'test-until' | ||
|
||
describe('something', function () { | ||
it('tests things', async function () { | ||
let val = 0 | ||
let otherVal = 0 | ||
setTimeout(() => val = 42, 100) | ||
setTimeout(() => otherVal = 2048, 200) | ||
// Awaiting a rejected promise will cause a synchronous `throw`, | ||
// which will reject the promise returned from the async function | ||
// and will fail the test. | ||
await until('val is 42', () => val === 42) | ||
await until('otherVal is 2048', () => otherVal === 2048) | ||
}) | ||
}) | ||
``` | ||
|
||
## Advanced Usage | ||
|
||
### Setting the Default Timeout | ||
|
||
Use `until.setDefaultTimeout(ms)` to set the default timeout. You can easily set this to different values in different parts of your test suite by setting it in a `beforeEach` | ||
|
||
```javascript | ||
import until from 'test-until' | ||
|
||
// Set a global default timeout | ||
beforeEach(function () { | ||
until.setDefaultTimeout(500) | ||
}) | ||
|
||
describe('slow stuff', function () { | ||
beforeEach(function () { | ||
// Make it a bit longer for these tests | ||
until.setDefaultTimeout(1000) | ||
}) | ||
// ... | ||
}) | ||
``` | ||
|
||
Passing a falsy `ms` resets to the default of `1000`. | ||
|
||
### Setting the Error Message from Inside the Check Function | ||
|
||
The check function gets called with a special argument called `setError` which allows the check function to specify an error to return if the `until` call times out. This can be useful when integrating `until` with other test assertions; for example, here's a snippet that will wait for `val` to be `42` and will reject with an actual Chai assertion error if it fails. | ||
|
||
```javascript | ||
import until from 'test-until' | ||
import {assert} from 'chai' | ||
|
||
describe('something', function () { | ||
it('tests things', async function () { | ||
let val = 0 | ||
setTimeout(() => val = 42, 100) | ||
await until(setError => { | ||
try { | ||
assert.equal(val, 42) | ||
return true | ||
} catch (err) { | ||
return setError(err) // explicitly set the error | ||
} | ||
}) | ||
}) | ||
}) | ||
``` | ||
|
||
## Development | ||
|
||
The test suite uses newer JavaScript features, so you need Node.js 6+ in order to run it. Run `npm test` to run the suite. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
let defaultTimeout = 1000; | ||
|
||
function until(_latch, _message, _timeout) { | ||
var start = new Date().getTime(); | ||
|
||
var latchFunction = null; | ||
var message = null; | ||
var timeout = null; | ||
|
||
if (arguments.length > 3) { | ||
throw new Error('until only takes up to 3 args'); | ||
} | ||
|
||
for (var i = 0; i < arguments.length; i++) { | ||
switch (typeof arguments[i]) { | ||
case 'function': | ||
latchFunction = arguments[i]; | ||
break; | ||
case 'string': | ||
message = arguments[i]; | ||
break; | ||
case 'number': | ||
timeout = arguments[i]; | ||
break; | ||
} | ||
} | ||
|
||
message = message || 'something happens'; | ||
timeout = timeout || defaultTimeout; | ||
var error; | ||
|
||
var setError = function(err) { | ||
if (typeof err === 'string') { | ||
error = new Error(err); | ||
} else { | ||
error = err; | ||
} | ||
return false; | ||
}; | ||
|
||
return new Promise(function(resolve, reject) { | ||
var checker = function() { | ||
var result = latchFunction(setError); | ||
if (result) { return resolve(result); } | ||
|
||
var now = new Date().getTime(); | ||
var delta = now - start; | ||
if (delta > timeout) { | ||
if (!error) { | ||
error = new Error(`timed out waiting until ${message}`); | ||
} | ||
error.message = 'async(' + timeout + 'ms): ' + error.message; | ||
return reject(error); | ||
} else { | ||
return setTimeout(checker); | ||
} | ||
}; | ||
checker(); | ||
}); | ||
} | ||
|
||
until.setDefaultTimeout = function(ms) { | ||
if (!ms) { | ||
ms = 1000; | ||
} | ||
defaultTimeout = ms; | ||
}; | ||
|
||
module.exports = until; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
const assert = require('chai').assert; | ||
|
||
const until = require('../'); | ||
|
||
describe('until', function() { | ||
it('returns a promise that resolves when the latch function returns a truthy value', function(done) { | ||
let val = null; | ||
const promise = until(() => val === 42); | ||
setTimeout(() => val = 42, 20); | ||
promise.then(() => { | ||
done(); | ||
}); | ||
}); | ||
|
||
it('returns a promise that rejects if the latch function never returns true with the timeout', function(done) { | ||
let val = null; | ||
const promise = until(() => val === 42, 20); | ||
setTimeout(() => val = 42, 50); | ||
promise.catch(err => { | ||
assert.match(err.message, /async.*20.*timed out waiting until something happens/); | ||
done(); | ||
}); | ||
}); | ||
|
||
it('allows mixing the order of the arguments', function(done) { | ||
const val = null; | ||
const promise = until(10, 'value equals 42', () => val === 42); | ||
promise.catch(err => { | ||
assert.match(err.message, /async.*10.*timed out waiting until value equals 42/); | ||
done(); | ||
}); | ||
}); | ||
|
||
it('allows setting the error explicitly', function(done) { | ||
let val = 0; | ||
until(15, setError => { | ||
val++; | ||
return setError(new Error('Failure in pass ' + val)); | ||
}).catch(err => { | ||
assert.equal(err.message, 'async(15ms): Failure in pass ' + val); | ||
assert.operator(val, '>', 0); | ||
done(); | ||
}); | ||
}); | ||
|
||
describe('the default timeout', function() { | ||
beforeEach(function() { | ||
until.setDefaultTimeout(); | ||
}); | ||
|
||
it('starts at 1000ms', function(done) { | ||
until(() => false).catch(err => { | ||
assert.match(err.message, /async.*1000.*timed out/); | ||
done(); | ||
}); | ||
}); | ||
|
||
describe('it can be set', function() { | ||
beforeEach(function() { | ||
until.setDefaultTimeout(20); | ||
}); | ||
|
||
it('and is sticky', function(done) { | ||
until(() => false).catch(err => { | ||
assert.match(err.message, /async.*20.*timed out/); | ||
done(); | ||
}); | ||
}); | ||
}); | ||
}); | ||
}); |