UNPKG

playable

Version:

Video player based on HTML5Video

304 lines (247 loc) 9.47 kB
import 'jsdom-global/register'; import { expect } from 'chai'; import * as sinon from 'sinon'; import createPlayerTestkit from '../../../../testkit'; import ProgressControl, { UPDATE_PROGRESS_INTERVAL_DELAY } from './progress'; import { VideoEvent, EngineState } from '../../../../constants'; describe('ProgressControl', () => { let testkit; let control: any; let engine: any; let eventEmitter: any; beforeEach(() => { testkit = createPlayerTestkit(); testkit.registerModule('progressControl', ProgressControl); control = testkit.getModule('progressControl'); eventEmitter = testkit.getModule('eventEmitter'); engine = testkit.getModule('engine'); }); describe('constructor', () => { it('should create instance ', () => { expect(control).to.exist; expect(control.view).to.exist; }); }); describe('API', () => { it('should have method for showing whole view', () => { expect(control.show).to.exist; control.show(); expect(control.isHidden).to.be.false; }); it('should have method for hiding whole view', () => { expect(control.hide).to.exist; control.hide(); expect(control.isHidden).to.be.true; }); it('should have method for destroying', () => { const spy = sinon.spy(control, '_unbindEvents'); expect(control.destroy).to.exist; control.destroy(); expect(spy.called).to.be.true; }); describe('for time indicators', () => { const VIDEO_DURATION_TIME = 1000; let engineGetDurationTimeStub: sinon.SinonStub; beforeEach(() => { engineGetDurationTimeStub = (sinon.stub( control._engine, 'getDuration', ) as sinon.SinonStub).callsFake(() => VIDEO_DURATION_TIME); }); afterEach(() => { engineGetDurationTimeStub.restore(); }); it('should have methods for adding/deleting indicators', () => { expect(control.addTimeIndicator, 'addTimeIndicator').to.exist; expect(control.addTimeIndicators, 'addTimeIndicators').to.exist; expect(control.clearTimeIndicators, 'clearTimeIndicators').to.exist; }); describe('before `METADATA_LOADED`', () => { beforeEach(() => { control.clearTimeIndicators(); }); it('should add one indicator', async function() { const timeIndicatorsNode = control.view._$timeIndicators; control.addTimeIndicator(100); expect( control._engine.isMetadataLoaded, '`isMetadataLoaded` before add', ).to.equal(false); expect( timeIndicatorsNode.childNodes.length, 'indicator added before `METADATA_LOADED`', ).to.equal(0); await eventEmitter.emitAsync(VideoEvent.STATE_CHANGED, { nextState: EngineState.METADATA_LOADED, }); expect( timeIndicatorsNode.childNodes.length, 'indicator added after `METADATA_LOADED`', ).to.equal(1); }); it('should add multiple indicators', async function() { const timeIndicatorsNode = control.view._$timeIndicators; control.addTimeIndicators([100, 200, 300]); expect( control._engine.isMetadataLoaded, '`isMetadataLoaded` before add', ).to.equal(false); expect( timeIndicatorsNode.childNodes.length, 'indicator added before `METADATA_LOADED`', ).to.equal(0); await eventEmitter.emitAsync(VideoEvent.STATE_CHANGED, { nextState: EngineState.METADATA_LOADED, }); expect( timeIndicatorsNode.childNodes.length, 'indicators added after `METADATA_LOADED`', ).to.equal(3); }); }); describe('after `METADATA_LOADED`', () => { beforeEach(() => { control.clearTimeIndicators(); Reflect.defineProperty(control._engine, 'isMetadataLoaded', { ...Reflect.getOwnPropertyDescriptor( engine.constructor.prototype, 'isMetadataLoaded', ), get: () => true, }); }); afterEach(() => { Reflect.deleteProperty(engine, 'isMetadataLoaded'); }); it('should add one indicator', () => { const timeIndicatorsNode = control.view._$timeIndicators; expect( timeIndicatorsNode.childNodes.length, 'empty before add', ).to.equal(0); control.addTimeIndicator(100); expect( timeIndicatorsNode.childNodes.length, 'indicators added', ).to.equal(1); }); it('should add multiple indicator', () => { const timeIndicatorsNode = control.view._$timeIndicators; expect( timeIndicatorsNode.childNodes.length, 'empty before add', ).to.equal(0); control.addTimeIndicators([100, 200, 300]); expect( timeIndicatorsNode.childNodes.length, 'indicators added', ).to.equal(3); }); it('should ignore time more then video duration time', () => { const timeIndicatorsNode = control.view._$timeIndicators; expect( timeIndicatorsNode.childNodes.length, 'empty before add', ).to.equal(0); control.addTimeIndicator(VIDEO_DURATION_TIME + 1); expect( timeIndicatorsNode.childNodes.length, 'indicators added', ).to.equal(0); }); it('should delete all added indicators', () => { const timeIndicatorsNode = control.view._$timeIndicators; control.addTimeIndicators([100, 200, 300]); expect( timeIndicatorsNode.childNodes.length, 'indicators added', ).to.equal(3); control.clearTimeIndicators(); expect( timeIndicatorsNode.childNodes.length, 'indicators after clear', ).to.equal(0); }); }); }); }); describe('video events listeners', () => { it('should call callback on playback state change', async function() { const spy = sinon.spy(control, '_processStateChange'); control._bindEvents(); await eventEmitter.emitAsync(VideoEvent.STATE_CHANGED, {}); expect(spy.called).to.be.true; }); it('should call callback on seek', async function() { const spyPlayed = sinon.spy(control, '_updatePlayedIndicator'); const spyBuffered = sinon.spy(control, '_updateBufferIndicator'); control._bindEvents(); await eventEmitter.emitAsync(VideoEvent.STATE_CHANGED, { nextState: EngineState.SEEK_IN_PROGRESS, }); expect(spyPlayed.called).to.be.true; expect(spyBuffered.called).to.be.true; }); it('should call callback on duration update', async function() { const spy = sinon.spy(control, '_updateBufferIndicator'); control._bindEvents(); await eventEmitter.emitAsync(VideoEvent.CHUNK_LOADED); expect(spy.called).to.be.true; }); }); describe('internal methods', () => { it('should toggle playback on manipulation change', () => { const startSpy = sinon.spy(control, '_pauseVideoOnDragStart'); const stopSpy = sinon.spy(control, '_playVideoOnDragEnd'); control._startProcessingUserDrag(); expect(startSpy.called).to.be.true; control._stopProcessingUserDrag(); expect(stopSpy.called).to.be.true; startSpy.restore(); stopSpy.restore(); }); it('should toggle interval updates', () => { const startSpy = sinon.spy(control, '_startIntervalUpdates'); control._processStateChange({ nextState: EngineState.PLAYING }); expect(startSpy.called).to.be.true; const stopSpy = sinon.spy(control, '_stopIntervalUpdates'); control._processStateChange({ nextState: EngineState.PAUSED }); expect(stopSpy.called).to.be.true; }); it('should start interval updates', () => { const spy = sinon.spy(window, 'setInterval'); const stopSpy = sinon.spy(control, '_stopIntervalUpdates'); control._startIntervalUpdates(); expect( spy.calledWith( control._updateAllIndicators, UPDATE_PROGRESS_INTERVAL_DELAY, ), ).to.be.true; expect(stopSpy.called).to.be.false; control._startIntervalUpdates(); expect(stopSpy.called).to.be.true; spy.restore(); }); it('should change current time of video', () => { const spy = sinon.stub(engine, 'seekTo'); control._onChangePlayedPercent(10); expect(spy.called).to.be.true; }); it('should update view', () => { const playedSpy = sinon.spy(control, '_setPlayed'); const bufferSpy = sinon.spy(control, '_setBuffered'); control._updatePlayedIndicator(); expect(playedSpy.called).to.be.true; control._updateBufferIndicator(); expect(bufferSpy.called).to.be.true; }); it('should trigger update of both played and buffered', () => { const playedSpy = sinon.spy(control, '_updatePlayedIndicator'); const bufferSpy = sinon.spy(control, '_updateBufferIndicator'); control._updateAllIndicators(); expect(playedSpy.called).to.be.true; expect(bufferSpy.called).to.be.true; }); }); });