mirror of
https://github.com/videojs/video.js.git
synced 2024-12-04 10:34:51 +02:00
7fd29b4f18
the bug is that objects using the new-ish evented mixin cannot listen to the window object, preventing things like: ``` component.on(window, 'scroll', throttledListener); ``` This fixes that so anything that's a native EventTarget works.
612 lines
17 KiB
JavaScript
612 lines
17 KiB
JavaScript
/* eslint-env qunit */
|
|
import document from 'global/document';
|
|
import window from 'global/window';
|
|
import sinon from 'sinon';
|
|
import evented from '../../../src/js/mixins/evented';
|
|
import * as Dom from '../../../src/js/utils/dom';
|
|
import * as Obj from '../../../src/js/utils/obj';
|
|
|
|
// Common errors thrown by evented objects.
|
|
const errors = {
|
|
type: new Error('Invalid event type; must be a non-empty string or array.'),
|
|
listener: new Error('Invalid listener; must be a function.'),
|
|
target: new Error('Invalid target; must be a DOM node or evented object.')
|
|
};
|
|
|
|
// Cross-browser event creation.
|
|
const createEvent = (type) => {
|
|
let event;
|
|
|
|
try {
|
|
event = new window.Event(type);
|
|
} catch (x) {
|
|
event = document.createEvent('Event');
|
|
event.initEvent(type, true, true);
|
|
}
|
|
|
|
return event;
|
|
};
|
|
|
|
const validateListenerCall = (call, thisValue, eventExpectation) => {
|
|
const eventActual = call.args[0];
|
|
|
|
QUnit.assert.strictEqual(call.thisValue, thisValue, 'the listener had the expected "this" value');
|
|
QUnit.assert.strictEqual(typeof eventActual, 'object', 'the listener was passed an event object');
|
|
|
|
// We don't use `deepEqual` here because we only want to test a subset of
|
|
// properties (designated by the `eventExpectation`).
|
|
Object.keys(eventExpectation).forEach(key => {
|
|
QUnit.assert.strictEqual(eventActual[key], eventExpectation[key], `the event had the expected "${key}"`);
|
|
});
|
|
};
|
|
|
|
QUnit.module('mixins: evented', {
|
|
|
|
beforeEach() {
|
|
this.targets = {};
|
|
},
|
|
|
|
afterEach() {
|
|
Object.keys(this.targets).forEach(k => {
|
|
if (typeof this.targets[k].trigger === 'function') {
|
|
this.targets[k].trigger('dispose');
|
|
} else {
|
|
this.targets[k] = null;
|
|
}
|
|
});
|
|
}
|
|
});
|
|
|
|
QUnit.test('evented() mutates an object as expected', function(assert) {
|
|
const target = this.targets.a = {};
|
|
|
|
assert.strictEqual(typeof evented, 'function', 'the mixin is a function');
|
|
assert.strictEqual(evented(target), target, 'returns the target object');
|
|
|
|
assert.ok(Obj.isObject(target), 'the target is still an object');
|
|
assert.ok(Dom.isEl(target.eventBusEl_), 'the target has an event bus element');
|
|
assert.strictEqual(typeof target.off, 'function', 'the target has an off method');
|
|
assert.strictEqual(typeof target.on, 'function', 'the target has an on method');
|
|
assert.strictEqual(typeof target.one, 'function', 'the target has a one method');
|
|
assert.strictEqual(typeof target.trigger, 'function', 'the target has a trigger method');
|
|
});
|
|
|
|
QUnit.test('evented() with custom element', function(assert) {
|
|
const target = this.targets.a = evented({foo: Dom.createEl('span')}, {eventBusKey: 'foo'});
|
|
|
|
assert.strictEqual(target.eventBusEl_, target.foo, 'the custom DOM element is re-used');
|
|
|
|
assert.throws(
|
|
() => evented({foo: {}}, {eventBusKey: 'foo'}),
|
|
new Error('The eventBusKey "foo" does not refer to an element.'),
|
|
'throws if the target does not have an element at the supplied key'
|
|
);
|
|
});
|
|
|
|
QUnit.test('on() and one() error states', function(assert) {
|
|
const target = this.targets.a = evented({});
|
|
|
|
['on', 'one'].forEach(method => {
|
|
assert.throws(() => target[method](), errors.type, 'the expected error is thrown');
|
|
assert.throws(() => target[method](' '), errors.type, 'the expected error is thrown');
|
|
assert.throws(() => target[method]([]), errors.type, 'the expected error is thrown');
|
|
assert.throws(() => target[method]('x'), errors.listener, 'the expected error is thrown');
|
|
assert.throws(() => target[method]({}, 'x', () => {}), errors.target, 'the expected error is thrown');
|
|
assert.throws(() => target[method](evented({}), 'x', null), errors.listener, 'the expected error is thrown');
|
|
});
|
|
});
|
|
|
|
QUnit.test('off() error states', function(assert) {
|
|
const target = this.targets.a = evented({});
|
|
|
|
// An invalid event actually causes an invalid target error because it
|
|
// gets passed into code that assumes the first argument is the target.
|
|
assert.throws(() => target.off([]), errors.target, 'the expected error is thrown');
|
|
assert.throws(() => target.off({}, 'x', () => {}), errors.target, 'the expected error is thrown');
|
|
assert.throws(() => target.off(evented({}), '', () => {}), errors.type, 'the expected error is thrown');
|
|
assert.throws(() => target.off(evented({}), [], () => {}), errors.type, 'the expected error is thrown');
|
|
assert.throws(() => target.off(evented({}), 'x', null), errors.listener, 'the expected error is thrown');
|
|
});
|
|
|
|
QUnit.test('on() can add a listener to one event type on itself', function(assert) {
|
|
const a = this.targets.a = evented({});
|
|
const spy = sinon.spy();
|
|
|
|
a.on('x', spy);
|
|
a.trigger('x');
|
|
|
|
assert.strictEqual(spy.callCount, 1, 'the listener was called the expected number of times');
|
|
|
|
validateListenerCall(spy.getCall(0), a, {
|
|
type: 'x',
|
|
target: a.eventBusEl_
|
|
});
|
|
});
|
|
|
|
QUnit.test('on() can add a listener to an array of event types on itself', function(assert) {
|
|
const a = this.targets.a = evented({});
|
|
const spy = sinon.spy();
|
|
|
|
a.on(['x', 'y'], spy);
|
|
a.trigger('x');
|
|
a.trigger('y');
|
|
|
|
assert.strictEqual(spy.callCount, 2, 'the listener was called the expected number of times');
|
|
|
|
validateListenerCall(spy.getCall(0), a, {
|
|
type: 'x',
|
|
target: a.eventBusEl_
|
|
});
|
|
|
|
validateListenerCall(spy.getCall(1), a, {
|
|
type: 'y',
|
|
target: a.eventBusEl_
|
|
});
|
|
});
|
|
|
|
QUnit.test('one() can add a listener to one event type on itself', function(assert) {
|
|
const a = this.targets.a = evented({});
|
|
const spy = sinon.spy();
|
|
|
|
a.one('x', spy);
|
|
a.trigger('x');
|
|
a.trigger('x');
|
|
|
|
assert.strictEqual(spy.callCount, 1, 'the listener was called the expected number of times');
|
|
|
|
validateListenerCall(spy.getCall(0), a, {
|
|
type: 'x',
|
|
target: a.eventBusEl_
|
|
});
|
|
});
|
|
|
|
QUnit.test('one() can add a listener to an array of event types on itself', function(assert) {
|
|
const a = this.targets.a = evented({});
|
|
const spy = sinon.spy();
|
|
|
|
a.one(['x', 'y'], spy);
|
|
a.trigger('x');
|
|
a.trigger('y');
|
|
a.trigger('x');
|
|
a.trigger('y');
|
|
|
|
assert.strictEqual(spy.callCount, 2, 'the listener was called the expected number of times');
|
|
|
|
validateListenerCall(spy.getCall(0), a, {
|
|
type: 'x',
|
|
target: a.eventBusEl_
|
|
});
|
|
|
|
validateListenerCall(spy.getCall(1), a, {
|
|
type: 'y',
|
|
target: a.eventBusEl_
|
|
});
|
|
});
|
|
|
|
QUnit.test('on() can add a listener to one event type on a different evented object', function(assert) {
|
|
const a = this.targets.a = evented({});
|
|
const b = this.targets.b = evented({});
|
|
const spy = sinon.spy();
|
|
|
|
a.on(b, 'x', spy);
|
|
b.trigger('x');
|
|
|
|
// Make sure we aren't magically binding a listener to "a".
|
|
a.trigger('x');
|
|
|
|
assert.strictEqual(spy.callCount, 1, 'the listener was called the expected number of times');
|
|
|
|
validateListenerCall(spy.getCall(0), a, {
|
|
type: 'x',
|
|
target: b.eventBusEl_
|
|
});
|
|
});
|
|
|
|
QUnit.test('on() can add a listener to one event type on a node object', function(assert) {
|
|
const a = this.targets.a = evented({});
|
|
const b = this.targets.b = Dom.createEl('div');
|
|
const spy = sinon.spy();
|
|
const event = createEvent('x');
|
|
|
|
a.on(b, 'x', spy);
|
|
b.dispatchEvent(event);
|
|
|
|
// Make sure we aren't magically binding a listener to "a".
|
|
a.trigger('x');
|
|
|
|
assert.strictEqual(spy.callCount, 1, 'the listener was called the expected number of times');
|
|
|
|
validateListenerCall(spy.getCall(0), a, {
|
|
type: 'x',
|
|
target: b
|
|
});
|
|
});
|
|
|
|
QUnit.test('on() can add a listener to one event type on the window object', function(assert) {
|
|
const a = this.targets.a = evented({});
|
|
const b = this.targets.b = window;
|
|
const spy = sinon.spy();
|
|
const event = createEvent('x');
|
|
|
|
a.on(b, 'x', spy);
|
|
b.dispatchEvent(event);
|
|
|
|
// Make sure we aren't magically binding a listener to "a".
|
|
a.trigger('x');
|
|
|
|
assert.strictEqual(spy.callCount, 1, 'the listener was called the expected number of times');
|
|
|
|
validateListenerCall(spy.getCall(0), a, {
|
|
type: 'x',
|
|
target: b
|
|
});
|
|
});
|
|
|
|
QUnit.test('on() can add a listener to an array of event types on a different evented object', function(assert) {
|
|
const a = this.targets.a = evented({});
|
|
const b = this.targets.b = evented({});
|
|
const spy = sinon.spy();
|
|
|
|
a.on(b, ['x', 'y'], spy);
|
|
b.trigger('x');
|
|
b.trigger('y');
|
|
|
|
// Make sure we aren't magically binding a listener to "a".
|
|
a.trigger('x');
|
|
a.trigger('y');
|
|
|
|
assert.strictEqual(spy.callCount, 2, 'the listener was called the expected number of times');
|
|
|
|
validateListenerCall(spy.getCall(0), a, {
|
|
type: 'x',
|
|
target: b.eventBusEl_
|
|
});
|
|
|
|
validateListenerCall(spy.getCall(1), a, {
|
|
type: 'y',
|
|
target: b.eventBusEl_
|
|
});
|
|
});
|
|
|
|
QUnit.test('on() can add a listener to an array of event types on a node object', function(assert) {
|
|
const a = this.targets.a = evented({});
|
|
const b = this.targets.b = Dom.createEl('div');
|
|
const spy = sinon.spy();
|
|
const x = createEvent('x');
|
|
const y = createEvent('y');
|
|
|
|
a.on(b, ['x', 'y'], spy);
|
|
b.dispatchEvent(x);
|
|
b.dispatchEvent(y);
|
|
|
|
// Make sure we aren't magically binding a listener to "a".
|
|
a.trigger('x');
|
|
a.trigger('y');
|
|
|
|
assert.strictEqual(spy.callCount, 2, 'the listener was called the expected number of times');
|
|
|
|
validateListenerCall(spy.getCall(0), a, {
|
|
type: 'x',
|
|
target: b
|
|
});
|
|
|
|
validateListenerCall(spy.getCall(1), a, {
|
|
type: 'y',
|
|
target: b
|
|
});
|
|
});
|
|
|
|
QUnit.test('on() can add a listener to an array of event types on the window object', function(assert) {
|
|
const a = this.targets.a = evented({});
|
|
const b = this.targets.b = window;
|
|
const spy = sinon.spy();
|
|
const x = createEvent('x');
|
|
const y = createEvent('y');
|
|
|
|
a.on(b, ['x', 'y'], spy);
|
|
b.dispatchEvent(x);
|
|
b.dispatchEvent(y);
|
|
|
|
// Make sure we aren't magically binding a listener to "a".
|
|
a.trigger('x');
|
|
a.trigger('y');
|
|
|
|
assert.strictEqual(spy.callCount, 2, 'the listener was called the expected number of times');
|
|
|
|
validateListenerCall(spy.getCall(0), a, {
|
|
type: 'x',
|
|
target: b
|
|
});
|
|
|
|
validateListenerCall(spy.getCall(1), a, {
|
|
type: 'y',
|
|
target: b
|
|
});
|
|
});
|
|
|
|
QUnit.test('one() can add a listener to one event type on a different evented object', function(assert) {
|
|
const a = this.targets.a = evented({});
|
|
const b = this.targets.b = evented({});
|
|
const spy = sinon.spy();
|
|
|
|
a.one(b, 'x', spy);
|
|
b.trigger('x');
|
|
b.trigger('x');
|
|
|
|
// Make sure we aren't magically binding a listener to "a".
|
|
a.trigger('x');
|
|
|
|
assert.strictEqual(spy.callCount, 1, 'the listener was called the expected number of times');
|
|
|
|
validateListenerCall(spy.getCall(0), a, {
|
|
type: 'x',
|
|
target: b.eventBusEl_
|
|
});
|
|
});
|
|
|
|
QUnit.test('one() can add a listener to one event type on a node object', function(assert) {
|
|
const a = this.targets.a = evented({});
|
|
const b = this.targets.b = Dom.createEl('div');
|
|
const spy = sinon.spy();
|
|
const event = createEvent('x');
|
|
|
|
a.one(b, 'x', spy);
|
|
b.dispatchEvent(event);
|
|
b.dispatchEvent(event);
|
|
|
|
// Make sure we aren't magically binding a listener to "a".
|
|
a.trigger('x');
|
|
|
|
assert.strictEqual(spy.callCount, 1, 'the listener was called the expected number of times');
|
|
|
|
validateListenerCall(spy.getCall(0), a, {
|
|
type: 'x',
|
|
target: b
|
|
});
|
|
});
|
|
|
|
QUnit.test('one() can add a listener to one event type on the window object', function(assert) {
|
|
const a = this.targets.a = evented({});
|
|
const b = this.targets.b = window;
|
|
const spy = sinon.spy();
|
|
const event = createEvent('x');
|
|
|
|
a.one(b, 'x', spy);
|
|
b.dispatchEvent(event);
|
|
b.dispatchEvent(event);
|
|
|
|
// Make sure we aren't magically binding a listener to "a".
|
|
a.trigger('x');
|
|
|
|
assert.strictEqual(spy.callCount, 1, 'the listener was called the expected number of times');
|
|
|
|
validateListenerCall(spy.getCall(0), a, {
|
|
type: 'x',
|
|
target: b
|
|
});
|
|
});
|
|
|
|
QUnit.test('one() can add a listener to an array of event types on a different evented object', function(assert) {
|
|
const a = this.targets.a = evented({});
|
|
const b = this.targets.b = evented({});
|
|
const spy = sinon.spy();
|
|
|
|
a.one(b, ['x', 'y'], spy);
|
|
b.trigger('x');
|
|
b.trigger('y');
|
|
b.trigger('x');
|
|
b.trigger('y');
|
|
|
|
// Make sure we aren't magically binding a listener to "a".
|
|
a.trigger('x');
|
|
a.trigger('y');
|
|
|
|
assert.strictEqual(spy.callCount, 1, 'the listener was called the expected number of times');
|
|
|
|
validateListenerCall(spy.getCall(0), a, {
|
|
type: 'x',
|
|
target: b.eventBusEl_
|
|
});
|
|
});
|
|
|
|
QUnit.test('one() can add a listener to an array of event types on a node object', function(assert) {
|
|
const a = this.targets.a = evented({});
|
|
const b = this.targets.b = Dom.createEl('div');
|
|
const spy = sinon.spy();
|
|
const x = createEvent('x');
|
|
const y = createEvent('y');
|
|
|
|
a.one(b, ['x', 'y'], spy);
|
|
b.dispatchEvent(x);
|
|
b.dispatchEvent(y);
|
|
b.dispatchEvent(x);
|
|
b.dispatchEvent(y);
|
|
|
|
// Make sure we aren't magically binding a listener to "a".
|
|
a.trigger('x');
|
|
a.trigger('y');
|
|
|
|
assert.strictEqual(spy.callCount, 1, 'the listener was called the expected number of times');
|
|
|
|
validateListenerCall(spy.getCall(0), a, {
|
|
type: 'x',
|
|
target: b
|
|
});
|
|
});
|
|
|
|
QUnit.test('one() can add a listener to an array of event types on the window object', function(assert) {
|
|
const a = this.targets.a = evented({});
|
|
const b = this.targets.b = window;
|
|
const spy = sinon.spy();
|
|
const x = createEvent('x');
|
|
const y = createEvent('y');
|
|
|
|
a.one(b, ['x', 'y'], spy);
|
|
b.dispatchEvent(x);
|
|
b.dispatchEvent(y);
|
|
b.dispatchEvent(x);
|
|
b.dispatchEvent(y);
|
|
|
|
// Make sure we aren't magically binding a listener to "a".
|
|
a.trigger('x');
|
|
a.trigger('y');
|
|
|
|
assert.strictEqual(spy.callCount, 1, 'the listener was called the expected number of times');
|
|
|
|
validateListenerCall(spy.getCall(0), a, {
|
|
type: 'x',
|
|
target: b
|
|
});
|
|
});
|
|
|
|
QUnit.test('off() with no arguments will remove all listeners from all events on this object', function(assert) {
|
|
const a = this.targets.a = evented({});
|
|
const spyX = sinon.spy();
|
|
const spyY = sinon.spy();
|
|
|
|
a.on('x', spyX);
|
|
a.on('y', spyY);
|
|
a.trigger('x');
|
|
a.trigger('y');
|
|
a.off();
|
|
a.trigger('x');
|
|
a.trigger('y');
|
|
|
|
assert.strictEqual(spyX.callCount, 1, 'the listener was called the expected number of times');
|
|
|
|
validateListenerCall(spyX.getCall(0), a, {
|
|
type: 'x',
|
|
target: a.eventBusEl_
|
|
});
|
|
|
|
assert.strictEqual(spyY.callCount, 1, 'the listener was called the expected number of times');
|
|
|
|
validateListenerCall(spyY.getCall(0), a, {
|
|
type: 'y',
|
|
target: a.eventBusEl_
|
|
});
|
|
});
|
|
|
|
QUnit.test('off() can remove all listeners from a single event on this object', function(assert) {
|
|
const a = this.targets.a = evented({});
|
|
const spyX = sinon.spy();
|
|
const spyY = sinon.spy();
|
|
|
|
a.on('x', spyX);
|
|
a.on('y', spyY);
|
|
a.trigger('x');
|
|
a.trigger('y');
|
|
a.off('x');
|
|
a.trigger('x');
|
|
a.trigger('y');
|
|
|
|
assert.strictEqual(spyX.callCount, 1, 'the listener was called the expected number of times');
|
|
|
|
validateListenerCall(spyX.getCall(0), a, {
|
|
type: 'x',
|
|
target: a.eventBusEl_
|
|
});
|
|
|
|
assert.strictEqual(spyY.callCount, 2, 'the listener was called the expected number of times');
|
|
|
|
validateListenerCall(spyY.getCall(0), a, {
|
|
type: 'y',
|
|
target: a.eventBusEl_
|
|
});
|
|
|
|
validateListenerCall(spyY.getCall(1), a, {
|
|
type: 'y',
|
|
target: a.eventBusEl_
|
|
});
|
|
});
|
|
|
|
QUnit.test('off() can remove a listener from a single event on this object', function(assert) {
|
|
const a = this.targets.a = evented({});
|
|
const spyX1 = sinon.spy();
|
|
const spyX2 = sinon.spy();
|
|
const spyY = sinon.spy();
|
|
|
|
a.on('x', spyX1);
|
|
a.on('x', spyX2);
|
|
a.on('y', spyY);
|
|
a.trigger('x');
|
|
a.trigger('y');
|
|
a.off('x', spyX1);
|
|
a.trigger('x');
|
|
a.trigger('y');
|
|
|
|
assert.strictEqual(spyX1.callCount, 1, 'the listener was called the expected number of times');
|
|
|
|
validateListenerCall(spyX1.getCall(0), a, {
|
|
type: 'x',
|
|
target: a.eventBusEl_
|
|
});
|
|
|
|
assert.strictEqual(spyX2.callCount, 2, 'the listener was called the expected number of times');
|
|
|
|
validateListenerCall(spyX2.getCall(0), a, {
|
|
type: 'x',
|
|
target: a.eventBusEl_
|
|
});
|
|
|
|
validateListenerCall(spyX2.getCall(1), a, {
|
|
type: 'x',
|
|
target: a.eventBusEl_
|
|
});
|
|
|
|
assert.strictEqual(spyY.callCount, 2, 'the listener was called the expected number of times');
|
|
|
|
validateListenerCall(spyY.getCall(0), a, {
|
|
type: 'y',
|
|
target: a.eventBusEl_
|
|
});
|
|
|
|
validateListenerCall(spyY.getCall(1), a, {
|
|
type: 'y',
|
|
target: a.eventBusEl_
|
|
});
|
|
});
|
|
|
|
QUnit.test('off() can remove a listener from a single event on a different target object', function(assert) {
|
|
const a = this.targets.a = evented({});
|
|
const b = this.targets.b = evented({});
|
|
const spy = sinon.spy();
|
|
|
|
a.on(b, 'x', spy);
|
|
b.trigger('x');
|
|
a.off(b, 'x', spy);
|
|
b.trigger('x');
|
|
|
|
assert.strictEqual(spy.callCount, 1, 'the listener was called the expected number of times');
|
|
|
|
validateListenerCall(spy.getCall(0), a, {
|
|
type: 'x',
|
|
target: b.eventBusEl_
|
|
});
|
|
});
|
|
|
|
QUnit.test('off() can remove a listener from an array of events on a different target object', function(assert) {
|
|
const a = this.targets.a = evented({});
|
|
const b = this.targets.b = evented({});
|
|
const spy = sinon.spy();
|
|
|
|
a.on(b, ['x', 'y'], spy);
|
|
b.trigger('x');
|
|
b.trigger('y');
|
|
a.off(b, ['x', 'y'], spy);
|
|
b.trigger('x');
|
|
b.trigger('y');
|
|
|
|
assert.strictEqual(spy.callCount, 2, 'the listener was called the expected number of times');
|
|
|
|
validateListenerCall(spy.getCall(0), a, {
|
|
type: 'x',
|
|
target: b.eventBusEl_
|
|
});
|
|
|
|
validateListenerCall(spy.getCall(1), a, {
|
|
type: 'y',
|
|
target: b.eventBusEl_
|
|
});
|
|
});
|