videojs-errors
Version:
A VideoJS plugin for custom error reporting
402 lines (325 loc) • 11.4 kB
JavaScript
import document from 'global/document';
import QUnit from 'qunit';
import sinon from 'sinon';
import videojs from 'video.js';
import plugin from '../src/plugin';
const Player = videojs.getComponent('Player');
const sources = [{
src: 'movie.mp4',
type: 'video/mp4'
}, {
src: 'movie.webm',
type: 'video/webm'
}];
QUnit.test('the environment is sane', function(assert) {
assert.strictEqual(typeof Array.isArray, 'function', 'es5 exists');
assert.strictEqual(typeof sinon, 'object', 'sinon exists');
assert.strictEqual(typeof videojs, 'function', 'videojs exists');
assert.strictEqual(typeof plugin, 'function', 'plugin is a function');
});
QUnit.module('videojs-errors', {
beforeEach() {
// Mock the environment's timers because certain things - particularly
// player readiness - are asynchronous in video.js 5.
this.clock = sinon.useFakeTimers();
this.fixture = document.getElementById('qunit-fixture');
this.video = document.createElement('video');
this.fixture.appendChild(this.video);
this.player = videojs(this.video);
this.player.buffered = function() {
return videojs.createTimeRange(0, 0);
};
this.player.paused = function() {
return false;
};
this.player.pause = function() {
return false;
};
// initialize the plugin with the default options
this.player.errors();
// Tick forward so the player is ready.
this.clock.tick(1);
},
afterEach() {
this.player.dispose();
this.clock.restore();
}
});
QUnit.test('registers itself with video.js', function(assert) {
assert.expect(2);
assert.strictEqual(
Player.prototype.errors,
plugin,
'videojs-errors plugin was registered'
);
this.player.errors();
// Tick the clock forward enough to trigger the player to be "ready".
this.clock.tick(1);
assert.ok(
this.player.hasClass('vjs-errors'),
'the plugin adds a class to the player'
);
});
QUnit.test('play() without a src is an error', function(assert) {
let errors = 0;
this.player.on('error', function() {
errors++;
});
this.player.trigger('play');
assert.strictEqual(errors, 1, 'emitted an error');
assert.strictEqual(this.player.error().code, -1, 'error code is -1');
assert.strictEqual(this.player.error().type,
'PLAYER_ERR_NO_SRC',
'error type is no source');
});
QUnit.test('no progress for 45 seconds is an error', function(assert) {
let errors = 0;
this.player.on('error', function() {
errors++;
});
this.player.src(sources);
this.player.trigger('play');
this.clock.tick(45 * 1000);
assert.strictEqual(errors, 1, 'emitted an error');
assert.strictEqual(this.player.error().code, -2, 'error code is -2');
assert.strictEqual(this.player.error().type, 'PLAYER_ERR_TIMEOUT');
});
QUnit.test('Flash API is unavailable when using Flash is an error', function(assert) {
this.player.tech_.el_.type = 'application/x-shockwave-flash';
// when Flash dies the object methods go away
/* eslint-disable camelcase */
this.player.tech_.el_.vjs_getProperty = null;
/* eslint-enable camelcase */
this.player.paused = function() {
return true;
};
let errors = 0;
this.player.on('error', function() {
errors++;
});
this.player.src(sources);
this.player.trigger('play');
this.player.trigger('timeupdate');
assert.strictEqual(errors, 1, 'emitted an error');
assert.strictEqual(this.player.error().code, -2, 'error code is -2');
assert.strictEqual(this.player.error().type, 'PLAYER_ERR_TIMEOUT');
});
QUnit.test('the plugin cleans up after its previous incarnation when called again',
function(assert) {
let errors = 0;
this.player.on('error', () => errors++);
// Call plugin multiple times
this.player.errors();
this.player.errors();
// Tick the clock forward enough to trigger the player to be "ready".
this.clock.tick(1);
this.player.trigger('play');
assert.strictEqual(errors, 1, 'emitted a single error');
assert.strictEqual(this.player.error().code, -1, 'error code is -1');
assert.strictEqual(this.player.error().type, 'PLAYER_ERR_NO_SRC');
});
QUnit.test('when dispose is triggered should not throw error ', function(assert) {
this.player.src(sources);
this.player.trigger('play');
this.player.trigger('dispose');
this.clock.tick(45 * 1000);
assert.ok(!this.player.error(),
'should not throw player error when dispose is called.');
});
QUnit.test('progress clears player timeout errors', function(assert) {
let errors = 0;
this.player.on('error', function() {
errors++;
});
this.player.src(sources);
this.player.trigger('play');
this.clock.tick(45 * 1000);
assert.strictEqual(errors, 1, 'emitted an error');
assert.strictEqual(this.player.error().code, -2, 'error code is -2');
assert.strictEqual(this.player.error().type, 'PLAYER_ERR_TIMEOUT');
this.player.trigger('progress');
assert.strictEqual(this.player.error(), null, 'error removed');
});
// safari 7 on OSX can emit stalls when playback is just fine
QUnit.test('stalling by itself is not an error', function(assert) {
this.player.src(sources);
this.player.trigger('play');
this.player.trigger('stalled');
assert.ok(!this.player.error(), 'no error fired');
});
QUnit.test('timing out multiple times only throws a single error', function(assert) {
let errors = 0;
this.player.on('error', function() {
errors++;
});
this.player.src(sources);
this.player.trigger('play');
// trigger a player timeout
this.clock.tick(45 * 1000);
assert.strictEqual(errors, 1, 'one error fired');
// wait long enough for another timeout
this.clock.tick(50 * 1000);
assert.strictEqual(errors, 1, 'only one error fired');
});
QUnit.test('progress events while playing reset the player timeout', function(assert) {
let errors = 0;
this.player.on('error', function() {
errors++;
});
this.player.src(sources);
this.player.trigger('play');
// stalled for awhile
this.clock.tick(44 * 1000);
// but playback resumes!
this.player.trigger('progress');
this.clock.tick(44 * 1000);
assert.strictEqual(errors, 0, 'no errors emitted');
});
QUnit.test('no signs of playback triggers a player timeout', function(assert) {
let errors = 0;
this.player.src(sources);
this.player.on('error', function() {
errors++;
});
// swallow any timeupdate events
this.player.on('timeupdate', function(event) {
event.stopImmediatePropagation();
});
this.player.trigger('play');
this.clock.tick(45 * 1000);
assert.strictEqual(errors, 1, 'emitted a single error');
assert.strictEqual(this.player.error().code, -2, 'error code is -2');
assert.strictEqual(this.player.error().type,
'PLAYER_ERR_TIMEOUT',
'type is player timeout');
});
QUnit.test('time changes while playing reset the player timeout', function(assert) {
let errors = 0;
this.player.src(sources);
this.player.on('error', function() {
errors++;
});
this.player.trigger('play');
this.clock.tick(44 * 1000);
this.player.currentTime = function() {
return 1;
};
this.player.trigger('timeupdate');
this.clock.tick(10 * 1000);
assert.strictEqual(errors, 0, 'no error emitted');
});
QUnit.test('time changes while playing ads reset the player timeout', function(assert) {
let errors = 0;
this.player.src(sources);
this.player.on('error', function() {
errors++;
});
this.player.trigger('play');
this.clock.tick(44 * 1000);
this.player.currentTime = function() {
return 1;
};
this.player.trigger('adtimeupdate');
this.clock.tick(10 * 1000);
assert.strictEqual(errors, 0, 'no error emitted');
});
QUnit.test('time changes after a player timeout clears the error', function(assert) {
this.player.src(sources);
this.player.trigger('play');
this.clock.tick(45 * 1000);
this.player.currentTime = function() {
return 1;
};
this.player.trigger('timeupdate');
assert.ok(!this.player.error(), 'cleared the timeout');
});
QUnit.test('player timeouts do not occur if the player is paused', function(assert) {
let errors = 0;
this.player.src(sources);
this.player.on('error', function() {
errors++;
});
this.player.on('timeupdate', function(event) {
event.stopImmediatePropagation();
});
this.player.trigger('play');
// simulate a misbehaving player that doesn't fire `paused`
this.player.paused = function() {
return true;
};
this.clock.tick(45 * 1000);
assert.strictEqual(errors, 0, 'no error emitted');
});
// video.paused is false at the end of a video on IE11, Win8 RT
QUnit.test('player timeouts do not occur if the video is ended', function(assert) {
let errors = 0;
this.player.src(sources);
this.player.on('error', function() {
errors++;
});
this.player.on('timeupdate', function(event) {
event.stopImmediatePropagation();
});
this.player.trigger('play');
// simulate a misbehaving player that doesn't fire `ended`
this.player.ended = function() {
return true;
};
this.clock.tick(45 * 1000);
assert.strictEqual(errors, 0, 'no error emitted');
});
QUnit.test('player timeouts do not overwrite existing errors', function(assert) {
this.player.src(sources);
this.player.trigger('play');
this.player.error({
type: 'custom',
code: -7
});
this.clock.tick(45 * 1000);
assert.strictEqual(-7, this.player.error().code, 'error was not overwritten');
});
QUnit.test('unrecognized error codes do not cause exceptions', function(assert) {
let errors = 0;
this.player.on('error', function() {
errors++;
});
this.player.error({
code: 'something-custom-that-no-one-could-have-predicted',
type: 'NOT_AN_ERROR_CONSTANT'
});
assert.ok(true, 'does not throw an exception');
assert.strictEqual(errors, 1, 'emitted an error');
// intentionally missing properties
this.player.error({});
assert.ok(true, 'does not throw an exception');
assert.strictEqual(errors, 2, 'emitted an error');
});
QUnit.test('custom error details should override defaults', function(assert) {
let customError = {headline: 'test headline', message: 'test details'};
// initialize the plugin with custom options
this.player.errors({errors: {4: customError}});
// tick forward enough to ready the player
this.clock.tick(1);
// trigger the error in question
this.player.error(4);
// confirm results
assert.strictEqual(document.querySelector('.vjs-errors-headline').textContent,
customError.headline, 'headline should match custom override value');
assert.strictEqual(document.querySelector('.vjs-errors-message').textContent,
customError.message, 'message should match custom override value');
});
QUnit.test('Append Flash error details when flash is not supported', function(assert) {
let oldIsSupported = videojs.getComponent('Flash').isSupported;
// Mock up isSupported to be false
videojs.getComponent('Flash').isSupported = () => false;
// tick forward enough to ready the player
this.clock.tick(1);
// trigger the error in question
this.player.error(4);
// confirm results
assert.equal(document.querySelector('.vjs-errors-flashmessage').textContent,
' * If you are using an older browser please try upgrading or installing Flash.',
'Flash Error message should be displayed');
// Restoring isSupported to the old value
videojs.getComponent('Flash').isSupported = oldIsSupported;
});