Add a waitOnEventOrTimeout helper function that allows waiting for an event or a timeout, whichever occurs first

This commit is contained in:
Jonas Jenwald 2017-08-12 16:06:20 +02:00
parent 28ce3b6185
commit 0c4985546a
2 changed files with 175 additions and 2 deletions

View File

@ -14,7 +14,8 @@
*/
import {
binarySearchFirstItem, EventBus, getPDFFileNameFromURL
binarySearchFirstItem, EventBus, getPDFFileNameFromURL, waitOnEventOrTimeout,
WaitOnType
} from '../../web/ui_utils';
import { createObjectURL, isNodeJS } from '../../src/shared/util';
@ -259,4 +260,118 @@ describe('ui_utils', function() {
expect(count).toEqual(2);
});
});
describe('waitOnEventOrTimeout', function() {
let eventBus;
beforeAll(function(done) {
eventBus = new EventBus();
done();
});
afterAll(function() {
eventBus = null;
});
it('should reject invalid parameters', function(done) {
let invalidTarget = waitOnEventOrTimeout({
target: 'window',
name: 'DOMContentLoaded',
}).then(function() {
throw new Error('Should reject invalid parameters.');
}, function(reason) {
expect(reason instanceof Error).toEqual(true);
});
let invalidName = waitOnEventOrTimeout({
target: eventBus,
name: '',
}).then(function() {
throw new Error('Should reject invalid parameters.');
}, function(reason) {
expect(reason instanceof Error).toEqual(true);
});
let invalidDelay = waitOnEventOrTimeout({
target: eventBus,
name: 'pagerendered',
delay: -1000,
}).then(function() {
throw new Error('Should reject invalid parameters.');
}, function(reason) {
expect(reason instanceof Error).toEqual(true);
});
Promise.all([invalidTarget, invalidName, invalidDelay]).then(done,
done.fail);
});
it('should resolve on event, using the DOM', function(done) {
if (isNodeJS()) {
pending('Document in not supported in Node.js.');
}
let button = document.createElement('button');
let buttonClicked = waitOnEventOrTimeout({
target: button,
name: 'click',
delay: 10000,
});
// Immediately dispatch the expected event.
button.click();
buttonClicked.then(function(type) {
expect(type).toEqual(WaitOnType.EVENT);
done();
}, done.fail);
});
it('should resolve on timeout, using the DOM', function(done) {
if (isNodeJS()) {
pending('Document in not supported in Node.js.');
}
let button = document.createElement('button');
let buttonClicked = waitOnEventOrTimeout({
target: button,
name: 'click',
delay: 10,
});
// Do *not* dispatch the event, and wait for the timeout.
buttonClicked.then(function(type) {
expect(type).toEqual(WaitOnType.TIMEOUT);
done();
}, done.fail);
});
it('should resolve on event, using the EventBus', function(done) {
let pageRendered = waitOnEventOrTimeout({
target: eventBus,
name: 'pagerendered',
delay: 10000,
});
// Immediately dispatch the expected event.
eventBus.dispatch('pagerendered');
pageRendered.then(function(type) {
expect(type).toEqual(WaitOnType.EVENT);
done();
}, done.fail);
});
it('should resolve on timeout, using the EventBus', function(done) {
let pageRendered = waitOnEventOrTimeout({
target: eventBus,
name: 'pagerendered',
delay: 10,
});
// Do *not* dispatch the event, and wait for the timeout.
pageRendered.then(function(type) {
expect(type).toEqual(WaitOnType.TIMEOUT);
done();
}, done.fail);
});
});
});

View File

@ -13,7 +13,7 @@
* limitations under the License.
*/
import { PDFJS } from 'pdfjs-lib';
import { createPromiseCapability, PDFJS } from 'pdfjs-lib';
const CSS_UNITS = 96.0 / 72.0;
const DEFAULT_SCALE_VALUE = 'auto';
@ -453,6 +453,62 @@ function cloneObj(obj) {
return result;
}
const WaitOnType = {
EVENT: 'event',
TIMEOUT: 'timeout',
};
/**
* @typedef {Object} WaitOnEventOrTimeoutParameters
* @property {Object} target - The event target, can for example be:
* `window`, `document`, a DOM element, or an {EventBus} instance.
* @property {string} name - The name of the event.
* @property {number} delay - The delay, in milliseconds, after which the
* timeout occurs (if the event wasn't already dispatched).
*/
/**
* Allows waiting for an event or a timeout, whichever occurs first.
* Can be used to ensure that an action always occurs, even when an event
* arrives late or not at all.
*
* @param {WaitOnEventOrTimeoutParameters}
* @returns {Promise} A promise that is resolved with a {WaitOnType} value.
*/
function waitOnEventOrTimeout({ target, name, delay = 0, }) {
if (typeof target !== 'object' || !(name && typeof name === 'string') ||
!(Number.isInteger(delay) && delay >= 0)) {
return Promise.reject(
new Error('waitOnEventOrTimeout - invalid paramaters.'));
}
let capability = createPromiseCapability();
function handler(type) {
if (target instanceof EventBus) {
target.off(name, eventHandler);
} else {
target.removeEventListener(name, eventHandler);
}
if (timeout) {
clearTimeout(timeout);
}
capability.resolve(type);
}
let eventHandler = handler.bind(null, WaitOnType.EVENT);
if (target instanceof EventBus) {
target.on(name, eventHandler);
} else {
target.addEventListener(name, eventHandler);
}
let timeoutHandler = handler.bind(null, WaitOnType.TIMEOUT);
let timeout = setTimeout(timeoutHandler, delay);
return capability.promise;
}
/**
* Promise that is resolved when DOM window becomes visible.
*/
@ -618,4 +674,6 @@ export {
normalizeWheelEventDelta,
animationStarted,
localized,
WaitOnType,
waitOnEventOrTimeout,
};