Commit 52fa6591 by Valera Rozuvan Committed by Vasyl Nakvasiuk

Corrected all isues found during refactoring #2.

Added missing file.~
Undid a mistake.
Copied Python tests from video_alpha1.
moves TestLogic to __init__.py
reorginizes xmodule logic tests
adds docstrings for poll tests
adds docstring for word cloud and conditional tests
adds docstrings for video alpha tests
adds videoalphafactory for tests, not finished
adds imports
Bug fixing.
fix video/videoalpha tests
Updated lettuce test. Now it is aware of the fact that Video and Video Alpha players have different base CSS classes.
Removed REFACTOR comments.
Turn off autoplay for Video Alpha in Studio.
Carry over fix for bug where in Firefox changing to speed 1.0 has no effect.
Carry over JavaScript Jasmine tests from jmclaus/videoalpha2_js branch.
Exporting state object from main function of Video Alpha.
More stuff from jmclaus/videoalpha2_js branch.
Specs in html5_video.js all pass except ten of them. Cleaned code a bit and moved it out of display_spec.js
One more spec passes
Fixed remaning tests in spec/../html5_video.js test suite. Removed test video files.
Added JavaScript Jasmine tests for main of Video Alpha 2.
adds test for volume control and updates helper file for videoalpha
parent 7c59947a
......@@ -3,7 +3,7 @@
<div id="example">
<div
id="video_id"
class="video"
class="videoalpha"
data-streams="0.75:slowerSpeedYoutubeId,1.0:normalSpeedYoutubeId"
data-show-captions="true"
data-start=""
......
......@@ -3,7 +3,7 @@
<div id="example">
<div
id="video_id"
class="video"
class="videoalpha"
data-show-captions="true"
data-start=""
data-end=""
......
<div class="course-content">
<div id="video_example">
<div id="example">
<div
id="video_id"
class="videoalpha"
data-streams="0.75:slowerSpeedYoutubeId,1.0:normalSpeedYoutubeId"
data-show-captions="false"
data-start=""
data-end=""
data-caption-asset-path="/static/subs/">
<div class="tc-wrapper">
<article class="video-wrapper">
<section class="video-player">
<div id="id"></div>
</section>
<section class="video-controls"></section>
</article>
</div>
</div>
</div>
</div>
</div>
\ No newline at end of file
*.js
# Tests for videoalpha are written in pure JavaScript.
!videoalpha/*.js
......@@ -8,6 +8,26 @@ window.YT =
BUFFERING: 3
CUED: 5
window.TYPES =
'undefined' : 'undefined'
'number' : 'number'
'boolean' : 'boolean'
'string' : 'string'
'[object Function]': 'function'
'[object RegExp]' : 'regexp'
'[object Array]' : 'array'
'[object Date]' : 'date'
'[object Error]' : 'error'
window.TOSTRING = Object.prototype.toString
window.STATUS = window.YT.PlayerState
window.whatType = (o) ->
TYPES[typeof o] || TYPES[TOSTRING.call(o)] || (o ? 'object' : 'null');
# Time waitsFor() should wait for before failing a test.
window.WAIT_TIMEOUT = 1000
jasmine.getFixtures().fixturesPath = 'xmodule/js/fixtures'
jasmine.stubbedMetadata =
......@@ -78,7 +98,8 @@ jasmine.stubVideoPlayer = (context, enableParts, createPlayer=true) ->
if createPlayer
return new VideoPlayer(video: context.video)
jasmine.stubVideoPlayerAlpha = (context, enableParts, createPlayer=true, html5=false) ->
jasmine.stubVideoPlayerAlpha = (context, enableParts, html5=false) ->
console.log('stubVideoPlayerAlpha called')
suite = context.suite
currentPartName = suite.description while suite = suite.parentSuite
if html5 == false
......@@ -88,10 +109,8 @@ jasmine.stubVideoPlayerAlpha = (context, enableParts, createPlayer=true, html5=f
jasmine.stubRequests()
YT.Player = undefined
window.OldVideoPlayerAlpha = undefined
context.video = new VideoAlpha '#example', '.75:slowerSpeedYoutubeId,1.0:normalSpeedYoutubeId'
jasmine.stubYoutubePlayer()
if createPlayer
return new VideoPlayerAlpha(video: context.video)
return new VideoAlpha '#example', '.75:slowerSpeedYoutubeId,1.0:normalSpeedYoutubeId'
# Stub jQuery.cookie
......
describe 'VideoAlpha HTML5Video', ->
playbackRates = [0.75, 1.0, 1.25, 1.5]
STATUS = window.YT.PlayerState
playerVars =
controls: 0
wmode: 'transparent'
rel: 0
showinfo: 0
enablejsapi: 1
modestbranding: 1
html5: 1
file = window.location.href.replace(/\/common(.*)$/, '') + '/test_root/data/videoalpha/gizmo'
html5Sources =
mp4: "#{file}.mp4"
webm: "#{file}.webm"
ogg: "#{file}.ogv"
onReady = jasmine.createSpy 'onReady'
onStateChange = jasmine.createSpy 'onStateChange'
beforeEach ->
loadFixtures 'videoalpha_html5.html'
@el = $('#example').find('.video')
@player = new window.HTML5Video.Player @el,
playerVars: playerVars,
videoSources: html5Sources,
events:
onReady: onReady
onStateChange: onStateChange
@videoEl = @el.find('.video-player video').get(0)
it 'PlayerState', ->
expect(HTML5Video.PlayerState).toEqual STATUS
describe 'constructor', ->
it 'create an html5 video element', ->
expect(@el.find('.video-player div')).toContain 'video'
it 'check if sources are created in correct way', ->
sources = $(@videoEl).find('source')
videoTypes = []
videoSources = []
$.each html5Sources, (index, source) ->
videoTypes.push index
videoSources.push source
$.each sources, (index, source) ->
s = $(source)
expect($.inArray(s.attr('src'), videoSources)).not.toEqual -1
expect($.inArray(s.attr('type').replace('video/', ''), videoTypes))
.not.toEqual -1
it 'check if click event is handled on the player', ->
expect(@videoEl).toHandle 'click'
# NOTE: According to
#
# https://github.com/ariya/phantomjs/wiki/Supported-Web-Standards#unsupported-features
#
# Video and Audio (due to the nature of PhantomJS) are not supported. After discussion
# with William Daly, some tests are disabled (Jenkins uses phantomjs for running tests
# and those tests fail).
#
# During code review, please enable the test below (change "xdescribe" to "describe"
# to enable the test).
xdescribe 'events:', ->
beforeEach ->
spyOn(@player, 'callStateChangeCallback').andCallThrough()
describe 'click', ->
describe 'when player is paused', ->
beforeEach ->
spyOn(@videoEl, 'play').andCallThrough()
@player.playerState = STATUS.PAUSED
$(@videoEl).trigger('click')
it 'native play event was called', ->
expect(@videoEl.play).toHaveBeenCalled()
it 'player state was changed', ->
expect(@player.playerState).toBe STATUS.PLAYING
it 'callback was called', ->
expect(@player.callStateChangeCallback).toHaveBeenCalled()
describe 'when player is played', ->
beforeEach ->
spyOn(@videoEl, 'pause').andCallThrough()
@player.playerState = STATUS.PLAYING
$(@videoEl).trigger('click')
it 'native pause event was called', ->
expect(@videoEl.pause).toHaveBeenCalled()
it 'player state was changed', ->
expect(@player.playerState).toBe STATUS.PAUSED
it 'callback was called', ->
expect(@player.callStateChangeCallback).toHaveBeenCalled()
describe 'play', ->
beforeEach ->
spyOn(@videoEl, 'play').andCallThrough()
@player.playerState = STATUS.PAUSED
@videoEl.play()
it 'native event was called', ->
expect(@videoEl.play).toHaveBeenCalled()
it 'player state was changed', ->
waitsFor ( ->
@player.playerState != HTML5Video.PlayerState.PAUSED
), 'Player state should be changed', 1000
runs ->
expect(@player.playerState).toBe STATUS.PLAYING
it 'callback was called', ->
waitsFor ( ->
@player.playerState != STATUS.PAUSED
), 'Player state should be changed', 1000
runs ->
expect(@player.callStateChangeCallback).toHaveBeenCalled()
describe 'pause', ->
beforeEach ->
spyOn(@videoEl, 'pause').andCallThrough()
@videoEl.play()
@videoEl.pause()
it 'native event was called', ->
expect(@videoEl.pause).toHaveBeenCalled()
it 'player state was changed', ->
waitsFor ( ->
@player.playerState != STATUS.UNSTARTED
), 'Player state should be changed', 1000
runs ->
expect(@player.playerState).toBe STATUS.PAUSED
it 'callback was called', ->
waitsFor ( ->
@player.playerState != HTML5Video.PlayerState.UNSTARTED
), 'Player state should be changed', 1000
runs ->
expect(@player.callStateChangeCallback).toHaveBeenCalled()
describe 'canplay', ->
beforeEach ->
waitsFor ( ->
@player.playerState != STATUS.UNSTARTED
), 'Video cannot be played', 1000
it 'player state was changed', ->
runs ->
expect(@player.playerState).toBe STATUS.PAUSED
it 'end property was defined', ->
runs ->
expect(@player.end).not.toBeNull()
it 'start position was defined', ->
runs ->
expect(@videoEl.currentTime).toBe(@player.start)
it 'callback was called', ->
runs ->
expect(@player.config.events.onReady).toHaveBeenCalled()
describe 'ended', ->
beforeEach ->
waitsFor ( ->
@player.playerState != STATUS.UNSTARTED
), 'Video cannot be played', 1000
it 'player state was changed', ->
runs ->
jasmine.fireEvent @videoEl, "ended"
expect(@player.playerState).toBe STATUS.ENDED
it 'callback was called', ->
jasmine.fireEvent @videoEl, "ended"
expect(@player.callStateChangeCallback).toHaveBeenCalled()
describe 'timeupdate', ->
beforeEach ->
spyOn(@videoEl, 'pause').andCallThrough()
waitsFor ( ->
@player.playerState != STATUS.UNSTARTED
), 'Video cannot be played', 1000
it 'player should be paused', ->
runs ->
@player.end = 3
@videoEl.currentTime = 5
jasmine.fireEvent @videoEl, "timeupdate"
expect(@videoEl.pause).toHaveBeenCalled()
it 'end param should be re-defined', ->
runs ->
@player.end = 3
@videoEl.currentTime = 5
jasmine.fireEvent @videoEl, "timeupdate"
expect(@player.end).toBe @videoEl.duration
# NOTE: According to
#
# https://github.com/ariya/phantomjs/wiki/Supported-Web-Standards#unsupported-features
#
# Video and Audio (due to the nature of PhantomJS) are not supported. After discussion
# with William Daly, some tests are disabled (Jenkins uses phantomjs for running tests
# and those tests fail).
#
# During code review, please enable the test below (change "xdescribe" to "describe"
# to enable the test).
xdescribe 'methods:', ->
beforeEach ->
waitsFor ( ->
@volume = @videoEl.volume
@seek = @videoEl.currentTime
@player.playerState == STATUS.PAUSED
), 'Video cannot be played', 1000
it 'pauseVideo', ->
spyOn(@videoEl, 'pause').andCallThrough()
@player.pauseVideo()
expect(@videoEl.pause).toHaveBeenCalled()
describe 'seekTo', ->
it 'set new correct value', ->
runs ->
@player.seekTo(2)
expect(@videoEl.currentTime).toBe 2
it 'set new inccorrect values', ->
runs ->
@player.seekTo(-50)
expect(@videoEl.currentTime).toBe @seek
@player.seekTo('5')
expect(@videoEl.currentTime).toBe @seek
@player.seekTo(500000)
expect(@videoEl.currentTime).toBe @seek
describe 'setVolume', ->
it 'set new correct value', ->
runs ->
@player.setVolume(50)
expect(@videoEl.volume).toBe 50*0.01
it 'set new inccorrect values', ->
runs ->
@player.setVolume(-50)
expect(@videoEl.volume).toBe @volume
@player.setVolume('5')
expect(@videoEl.volume).toBe @volume
@player.setVolume(500000)
expect(@videoEl.volume).toBe @volume
it 'getCurrentTime', ->
runs ->
@videoEl.currentTime = 3
expect(@player.getCurrentTime()).toBe @videoEl.currentTime
it 'playVideo', ->
runs ->
spyOn(@videoEl, 'play').andCallThrough()
@player.playVideo()
expect(@videoEl.play).toHaveBeenCalled()
it 'getPlayerState', ->
runs ->
@player.playerState = STATUS.PLAYING
expect(@player.getPlayerState()).toBe STATUS.PLAYING
@player.playerState = STATUS.ENDED
expect(@player.getPlayerState()).toBe STATUS.ENDED
it 'getVolume', ->
runs ->
@volume = @videoEl.volume = 0.5
expect(@player.getVolume()).toBe @volume
it 'getDuration', ->
runs ->
@duration = @videoEl.duration
expect(@player.getDuration()).toBe @duration
describe 'setPlaybackRate', ->
it 'set a correct value', ->
@playbackRate = 1.5
@player.setPlaybackRate @playbackRate
expect(@videoEl.playbackRate).toBe @playbackRate
it 'set NaN value', ->
@playbackRate = NaN
@player.setPlaybackRate @playbackRate
expect(@videoEl.playbackRate).toBe 1.0
it 'getAvailablePlaybackRates', ->
expect(@player.getAvailablePlaybackRates()).toEqual playbackRates
describe 'VideoControlAlpha', ->
beforeEach ->
window.onTouchBasedDevice = jasmine.createSpy('onTouchBasedDevice').andReturn false
loadFixtures 'videoalpha.html'
$('.video-controls').html ''
describe 'constructor', ->
it 'render the video controls', ->
@control = new window.VideoControlAlpha(el: $('.video-controls'))
expect($('.video-controls')).toContain
['.slider', 'ul.vcr', 'a.play', '.vidtime', '.add-fullscreen'].join(',')
expect($('.video-controls').find('.vidtime')).toHaveText '0:00 / 0:00'
it 'bind the playback button', ->
@control = new window.VideoControlAlpha(el: $('.video-controls'))
expect($('.video_control')).toHandleWith 'click', @control.togglePlayback
describe 'when on a touch based device', ->
beforeEach ->
window.onTouchBasedDevice.andReturn true
@control = new window.VideoControlAlpha(el: $('.video-controls'))
it 'does not add the play class to video control', ->
expect($('.video_control')).not.toHaveClass 'play'
expect($('.video_control')).not.toHaveHtml 'Play'
describe 'when on a non-touch based device', ->
beforeEach ->
@control = new window.VideoControlAlpha(el: $('.video-controls'))
it 'add the play class to video control', ->
expect($('.video_control')).toHaveClass 'play'
expect($('.video_control')).toHaveHtml 'Play'
describe 'play', ->
beforeEach ->
@control = new window.VideoControlAlpha(el: $('.video-controls'))
@control.play()
it 'switch playback button to play state', ->
expect($('.video_control')).not.toHaveClass 'play'
expect($('.video_control')).toHaveClass 'pause'
expect($('.video_control')).toHaveHtml 'Pause'
describe 'pause', ->
beforeEach ->
@control = new window.VideoControlAlpha(el: $('.video-controls'))
@control.pause()
it 'switch playback button to pause state', ->
expect($('.video_control')).not.toHaveClass 'pause'
expect($('.video_control')).toHaveClass 'play'
expect($('.video_control')).toHaveHtml 'Play'
describe 'togglePlayback', ->
beforeEach ->
@control = new window.VideoControlAlpha(el: $('.video-controls'))
describe 'when the control does not have play or pause class', ->
beforeEach ->
$('.video_control').removeClass('play').removeClass('pause')
describe 'when the video is playing', ->
beforeEach ->
$('.video_control').addClass('play')
spyOnEvent @control, 'pause'
@control.togglePlayback jQuery.Event('click')
it 'does not trigger the pause event', ->
expect('pause').not.toHaveBeenTriggeredOn @control
describe 'when the video is paused', ->
beforeEach ->
$('.video_control').addClass('pause')
spyOnEvent @control, 'play'
@control.togglePlayback jQuery.Event('click')
it 'does not trigger the play event', ->
expect('play').not.toHaveBeenTriggeredOn @control
describe 'when the video is playing', ->
beforeEach ->
spyOnEvent @control, 'pause'
$('.video_control').addClass 'pause'
@control.togglePlayback jQuery.Event('click')
it 'trigger the pause event', ->
expect('pause').toHaveBeenTriggeredOn @control
describe 'when the video is paused', ->
beforeEach ->
spyOnEvent @control, 'play'
$('.video_control').addClass 'play'
@control.togglePlayback jQuery.Event('click')
it 'trigger the play event', ->
expect('play').toHaveBeenTriggeredOn @control
// Generated by CoffeeScript 1.6.3
(function() {
xdescribe('VideoControlAlpha', function() {
beforeEach(function() {
window.onTouchBasedDevice = jasmine.createSpy('onTouchBasedDevice').andReturn(false);
loadFixtures('videoalpha.html');
return $('.video-controls').html('');
});
describe('constructor', function() {
it('render the video controls', function() {
this.control = new window.VideoControlAlpha({
el: $('.video-controls')
});
expect($('.video-controls')).toContain;
['.slider', 'ul.vcr', 'a.play', '.vidtime', '.add-fullscreen'].join(',');
return expect($('.video-controls').find('.vidtime')).toHaveText('0:00 / 0:00');
});
it('bind the playback button', function() {
this.control = new window.VideoControlAlpha({
el: $('.video-controls')
});
return expect($('.video_control')).toHandleWith('click', this.control.togglePlayback);
});
describe('when on a touch based device', function() {
beforeEach(function() {
window.onTouchBasedDevice.andReturn(true);
return this.control = new window.VideoControlAlpha({
el: $('.video-controls')
});
});
return it('does not add the play class to video control', function() {
expect($('.video_control')).not.toHaveClass('play');
return expect($('.video_control')).not.toHaveHtml('Play');
});
});
return describe('when on a non-touch based device', function() {
beforeEach(function() {
return this.control = new window.VideoControlAlpha({
el: $('.video-controls')
});
});
return it('add the play class to video control', function() {
expect($('.video_control')).toHaveClass('play');
return expect($('.video_control')).toHaveHtml('Play');
});
});
});
describe('play', function() {
beforeEach(function() {
this.control = new window.VideoControlAlpha({
el: $('.video-controls')
});
return this.control.play();
});
return it('switch playback button to play state', function() {
expect($('.video_control')).not.toHaveClass('play');
expect($('.video_control')).toHaveClass('pause');
return expect($('.video_control')).toHaveHtml('Pause');
});
});
describe('pause', function() {
beforeEach(function() {
this.control = new window.VideoControlAlpha({
el: $('.video-controls')
});
return this.control.pause();
});
return it('switch playback button to pause state', function() {
expect($('.video_control')).not.toHaveClass('pause');
expect($('.video_control')).toHaveClass('play');
return expect($('.video_control')).toHaveHtml('Play');
});
});
return describe('togglePlayback', function() {
beforeEach(function() {
return this.control = new window.VideoControlAlpha({
el: $('.video-controls')
});
});
return describe('when the control does not have play or pause class', function() {
beforeEach(function() {
return $('.video_control').removeClass('play').removeClass('pause');
});
describe('when the video is playing', function() {
beforeEach(function() {
$('.video_control').addClass('play');
spyOnEvent(this.control, 'pause');
return this.control.togglePlayback(jQuery.Event('click'));
});
return it('does not trigger the pause event', function() {
return expect('pause').not.toHaveBeenTriggeredOn(this.control);
});
});
describe('when the video is paused', function() {
beforeEach(function() {
$('.video_control').addClass('pause');
spyOnEvent(this.control, 'play');
return this.control.togglePlayback(jQuery.Event('click'));
});
return it('does not trigger the play event', function() {
return expect('play').not.toHaveBeenTriggeredOn(this.control);
});
});
describe('when the video is playing', function() {
beforeEach(function() {
spyOnEvent(this.control, 'pause');
$('.video_control').addClass('pause');
return this.control.togglePlayback(jQuery.Event('click'));
});
return it('trigger the pause event', function() {
return expect('pause').toHaveBeenTriggeredOn(this.control);
});
});
return describe('when the video is paused', function() {
beforeEach(function() {
spyOnEvent(this.control, 'play');
$('.video_control').addClass('play');
return this.control.togglePlayback(jQuery.Event('click'));
});
return it('trigger the play event', function() {
return expect('play').toHaveBeenTriggeredOn(this.control);
});
});
});
});
});
}).call(this);
describe 'VideoProgressSliderAlpha', ->
beforeEach ->
window.onTouchBasedDevice = jasmine.createSpy('onTouchBasedDevice').andReturn false
describe 'constructor', ->
describe 'on a non-touch based device', ->
beforeEach ->
spyOn($.fn, 'slider').andCallThrough()
@player = jasmine.stubVideoPlayerAlpha @
@progressSlider = @player.progressSlider
it 'build the slider', ->
expect(@progressSlider.slider).toBe '.slider'
expect($.fn.slider).toHaveBeenCalledWith
range: 'min'
change: @progressSlider.onChange
slide: @progressSlider.onSlide
stop: @progressSlider.onStop
it 'build the seek handle', ->
expect(@progressSlider.handle).toBe '.slider .ui-slider-handle'
expect($.fn.qtip).toHaveBeenCalledWith
content: "0:00"
position:
my: 'bottom center'
at: 'top center'
container: @progressSlider.handle
hide:
delay: 700
style:
classes: 'ui-tooltip-slider'
widget: true
describe 'on a touch-based device', ->
beforeEach ->
window.onTouchBasedDevice.andReturn true
spyOn($.fn, 'slider').andCallThrough()
@player = jasmine.stubVideoPlayerAlpha @
@progressSlider = @player.progressSlider
it 'does not build the slider', ->
expect(@progressSlider.slider).toBeUndefined
expect($.fn.slider).not.toHaveBeenCalled()
describe 'play', ->
beforeEach ->
spyOn(VideoProgressSliderAlpha.prototype, 'buildSlider').andCallThrough()
@player = jasmine.stubVideoPlayerAlpha @
@progressSlider = @player.progressSlider
describe 'when the slider was already built', ->
beforeEach ->
@progressSlider.play()
it 'does not build the slider', ->
expect(@progressSlider.buildSlider.calls.length).toEqual 1
describe 'when the slider was not already built', ->
beforeEach ->
spyOn($.fn, 'slider').andCallThrough()
@progressSlider.slider = null
@progressSlider.play()
it 'build the slider', ->
expect(@progressSlider.slider).toBe '.slider'
expect($.fn.slider).toHaveBeenCalledWith
range: 'min'
change: @progressSlider.onChange
slide: @progressSlider.onSlide
stop: @progressSlider.onStop
it 'build the seek handle', ->
expect(@progressSlider.handle).toBe '.ui-slider-handle'
expect($.fn.qtip).toHaveBeenCalledWith
content: "0:00"
position:
my: 'bottom center'
at: 'top center'
container: @progressSlider.handle
hide:
delay: 700
style:
classes: 'ui-tooltip-slider'
widget: true
describe 'updatePlayTime', ->
beforeEach ->
@player = jasmine.stubVideoPlayerAlpha @
@progressSlider = @player.progressSlider
describe 'when frozen', ->
beforeEach ->
spyOn($.fn, 'slider').andCallThrough()
@progressSlider.frozen = true
@progressSlider.updatePlayTime 20, 120
it 'does not update the slider', ->
expect($.fn.slider).not.toHaveBeenCalled()
describe 'when not frozen', ->
beforeEach ->
spyOn($.fn, 'slider').andCallThrough()
@progressSlider.frozen = false
@progressSlider.updatePlayTime 20, 120
it 'update the max value of the slider', ->
expect($.fn.slider).toHaveBeenCalledWith 'option', 'max', 120
it 'update current value of the slider', ->
expect($.fn.slider).toHaveBeenCalledWith 'value', 20
describe 'onSlide', ->
beforeEach ->
@player = jasmine.stubVideoPlayerAlpha @
@progressSlider = @player.progressSlider
spyOnEvent @progressSlider, 'slide_seek'
@progressSlider.onSlide {}, value: 20
it 'freeze the slider', ->
expect(@progressSlider.frozen).toBeTruthy()
it 'update the tooltip', ->
expect($.fn.qtip).toHaveBeenCalled()
it 'trigger seek event', ->
expect('slide_seek').toHaveBeenTriggeredOn @progressSlider
expect(@player.currentTime).toEqual 20
describe 'onChange', ->
beforeEach ->
@player = jasmine.stubVideoPlayerAlpha @
@progressSlider = @player.progressSlider
@progressSlider.onChange {}, value: 20
it 'update the tooltip', ->
expect($.fn.qtip).toHaveBeenCalled()
describe 'onStop', ->
beforeEach ->
@player = jasmine.stubVideoPlayerAlpha @
@progressSlider = @player.progressSlider
spyOnEvent @progressSlider, 'slide_seek'
@progressSlider.onStop {}, value: 20
it 'freeze the slider', ->
expect(@progressSlider.frozen).toBeTruthy()
it 'trigger seek event', ->
expect('slide_seek').toHaveBeenTriggeredOn @progressSlider
expect(@player.currentTime).toEqual 20
it 'set timeout to unfreeze the slider', ->
expect(window.setTimeout).toHaveBeenCalledWith jasmine.any(Function), 200
window.setTimeout.mostRecentCall.args[0]()
expect(@progressSlider.frozen).toBeFalsy()
describe 'updateTooltip', ->
beforeEach ->
@player = jasmine.stubVideoPlayerAlpha @
@progressSlider = @player.progressSlider
@progressSlider.updateTooltip 90
it 'set the tooltip value', ->
expect($.fn.qtip).toHaveBeenCalledWith 'option', 'content.text', '1:30'
// Generated by CoffeeScript 1.6.3
(function() {
xdescribe('VideoProgressSliderAlpha', function() {
beforeEach(function() {
return window.onTouchBasedDevice = jasmine.createSpy('onTouchBasedDevice').andReturn(false);
});
describe('constructor', function() {
describe('on a non-touch based device', function() {
beforeEach(function() {
spyOn($.fn, 'slider').andCallThrough();
this.player = jasmine.stubVideoPlayerAlpha(this);
return this.progressSlider = this.player.progressSlider;
});
it('build the slider', function() {
expect(this.progressSlider.slider).toBe('.slider');
return expect($.fn.slider).toHaveBeenCalledWith({
range: 'min',
change: this.progressSlider.onChange,
slide: this.progressSlider.onSlide,
stop: this.progressSlider.onStop
});
});
return it('build the seek handle', function() {
expect(this.progressSlider.handle).toBe('.slider .ui-slider-handle');
return expect($.fn.qtip).toHaveBeenCalledWith({
content: "0:00",
position: {
my: 'bottom center',
at: 'top center',
container: this.progressSlider.handle
},
hide: {
delay: 700
},
style: {
classes: 'ui-tooltip-slider',
widget: true
}
});
});
});
return describe('on a touch-based device', function() {
beforeEach(function() {
window.onTouchBasedDevice.andReturn(true);
spyOn($.fn, 'slider').andCallThrough();
this.player = jasmine.stubVideoPlayerAlpha(this);
return this.progressSlider = this.player.progressSlider;
});
return it('does not build the slider', function() {
expect(this.progressSlider.slider).toBeUndefined;
return expect($.fn.slider).not.toHaveBeenCalled();
});
});
});
describe('play', function() {
beforeEach(function() {
spyOn(VideoProgressSliderAlpha.prototype, 'buildSlider').andCallThrough();
this.player = jasmine.stubVideoPlayerAlpha(this);
return this.progressSlider = this.player.progressSlider;
});
describe('when the slider was already built', function() {
beforeEach(function() {
return this.progressSlider.play();
});
return it('does not build the slider', function() {
return expect(this.progressSlider.buildSlider.calls.length).toEqual(1);
});
});
return describe('when the slider was not already built', function() {
beforeEach(function() {
spyOn($.fn, 'slider').andCallThrough();
this.progressSlider.slider = null;
return this.progressSlider.play();
});
it('build the slider', function() {
expect(this.progressSlider.slider).toBe('.slider');
return expect($.fn.slider).toHaveBeenCalledWith({
range: 'min',
change: this.progressSlider.onChange,
slide: this.progressSlider.onSlide,
stop: this.progressSlider.onStop
});
});
return it('build the seek handle', function() {
expect(this.progressSlider.handle).toBe('.ui-slider-handle');
return expect($.fn.qtip).toHaveBeenCalledWith({
content: "0:00",
position: {
my: 'bottom center',
at: 'top center',
container: this.progressSlider.handle
},
hide: {
delay: 700
},
style: {
classes: 'ui-tooltip-slider',
widget: true
}
});
});
});
});
describe('updatePlayTime', function() {
beforeEach(function() {
this.player = jasmine.stubVideoPlayerAlpha(this);
return this.progressSlider = this.player.progressSlider;
});
describe('when frozen', function() {
beforeEach(function() {
spyOn($.fn, 'slider').andCallThrough();
this.progressSlider.frozen = true;
return this.progressSlider.updatePlayTime(20, 120);
});
return it('does not update the slider', function() {
return expect($.fn.slider).not.toHaveBeenCalled();
});
});
return describe('when not frozen', function() {
beforeEach(function() {
spyOn($.fn, 'slider').andCallThrough();
this.progressSlider.frozen = false;
return this.progressSlider.updatePlayTime(20, 120);
});
it('update the max value of the slider', function() {
return expect($.fn.slider).toHaveBeenCalledWith('option', 'max', 120);
});
return it('update current value of the slider', function() {
return expect($.fn.slider).toHaveBeenCalledWith('value', 20);
});
});
});
describe('onSlide', function() {
beforeEach(function() {
this.player = jasmine.stubVideoPlayerAlpha(this);
this.progressSlider = this.player.progressSlider;
spyOnEvent(this.progressSlider, 'slide_seek');
return this.progressSlider.onSlide({}, {
value: 20
});
});
it('freeze the slider', function() {
return expect(this.progressSlider.frozen).toBeTruthy();
});
it('update the tooltip', function() {
return expect($.fn.qtip).toHaveBeenCalled();
});
return it('trigger seek event', function() {
expect('slide_seek').toHaveBeenTriggeredOn(this.progressSlider);
return expect(this.player.currentTime).toEqual(20);
});
});
describe('onChange', function() {
beforeEach(function() {
this.player = jasmine.stubVideoPlayerAlpha(this);
this.progressSlider = this.player.progressSlider;
return this.progressSlider.onChange({}, {
value: 20
});
});
return it('update the tooltip', function() {
return expect($.fn.qtip).toHaveBeenCalled();
});
});
describe('onStop', function() {
beforeEach(function() {
this.player = jasmine.stubVideoPlayerAlpha(this);
this.progressSlider = this.player.progressSlider;
spyOnEvent(this.progressSlider, 'slide_seek');
return this.progressSlider.onStop({}, {
value: 20
});
});
it('freeze the slider', function() {
return expect(this.progressSlider.frozen).toBeTruthy();
});
it('trigger seek event', function() {
expect('slide_seek').toHaveBeenTriggeredOn(this.progressSlider);
return expect(this.player.currentTime).toEqual(20);
});
return it('set timeout to unfreeze the slider', function() {
expect(window.setTimeout).toHaveBeenCalledWith(jasmine.any(Function), 200);
window.setTimeout.mostRecentCall.args[0]();
return expect(this.progressSlider.frozen).toBeFalsy();
});
});
return describe('updateTooltip', function() {
beforeEach(function() {
this.player = jasmine.stubVideoPlayerAlpha(this);
this.progressSlider = this.player.progressSlider;
return this.progressSlider.updateTooltip(90);
});
return it('set the tooltip value', function() {
return expect($.fn.qtip).toHaveBeenCalledWith('option', 'content.text', '1:30');
});
});
});
}).call(this);
describe 'VideoSpeedControlAlpha', ->
beforeEach ->
window.onTouchBasedDevice = jasmine.createSpy('onTouchBasedDevice').andReturn false
jasmine.stubVideoPlayerAlpha @
$('.speeds').remove()
describe 'constructor', ->
describe 'always', ->
beforeEach ->
@speedControl = new VideoSpeedControlAlpha el: $('.secondary-controls'), speeds: @video.speeds, currentSpeed: '1.0'
it 'add the video speed control to player', ->
secondaryControls = $('.secondary-controls')
li = secondaryControls.find('.video_speeds li')
expect(secondaryControls).toContain '.speeds'
expect(secondaryControls).toContain '.video_speeds'
expect(secondaryControls.find('p.active').text()).toBe '1.0x'
expect(li.filter('.active')).toHaveData 'speed', @speedControl.currentSpeed
expect(li.length).toBe @speedControl.speeds.length
$.each li.toArray().reverse(), (index, link) =>
expect($(link)).toHaveData 'speed', @speedControl.speeds[index]
expect($(link).find('a').text()).toBe @speedControl.speeds[index] + 'x'
it 'bind to change video speed link', ->
expect($('.video_speeds a')).toHandleWith 'click', @speedControl.changeVideoSpeed
describe 'when running on touch based device', ->
beforeEach ->
window.onTouchBasedDevice.andReturn true
$('.speeds').removeClass 'open'
@speedControl = new VideoSpeedControlAlpha el: $('.secondary-controls'), speeds: @video.speeds, currentSpeed: '1.0'
it 'open the speed toggle on click', ->
$('.speeds').click()
expect($('.speeds')).toHaveClass 'open'
$('.speeds').click()
expect($('.speeds')).not.toHaveClass 'open'
describe 'when running on non-touch based device', ->
beforeEach ->
$('.speeds').removeClass 'open'
@speedControl = new VideoSpeedControlAlpha el: $('.secondary-controls'), speeds: @video.speeds, currentSpeed: '1.0'
it 'open the speed toggle on hover', ->
$('.speeds').mouseenter()
expect($('.speeds')).toHaveClass 'open'
$('.speeds').mouseleave()
expect($('.speeds')).not.toHaveClass 'open'
it 'close the speed toggle on mouse out', ->
$('.speeds').mouseenter().mouseleave()
expect($('.speeds')).not.toHaveClass 'open'
it 'close the speed toggle on click', ->
$('.speeds').mouseenter().click()
expect($('.speeds')).not.toHaveClass 'open'
describe 'changeVideoSpeed', ->
beforeEach ->
@speedControl = new VideoSpeedControlAlpha el: $('.secondary-controls'), speeds: @video.speeds, currentSpeed: '1.0'
@video.setSpeed '1.0'
describe 'when new speed is the same', ->
beforeEach ->
spyOnEvent @speedControl, 'speedChange'
$('li[data-speed="1.0"] a').click()
it 'does not trigger speedChange event', ->
expect('speedChange').not.toHaveBeenTriggeredOn @speedControl
describe 'when new speed is not the same', ->
beforeEach ->
@newSpeed = null
$(@speedControl).bind 'speedChange', (event, newSpeed) => @newSpeed = newSpeed
spyOnEvent @speedControl, 'speedChange'
$('li[data-speed="0.75"] a').click()
it 'trigger speedChange event', ->
expect('speedChange').toHaveBeenTriggeredOn @speedControl
expect(@newSpeed).toEqual 0.75
describe 'onSpeedChange', ->
beforeEach ->
@speedControl = new VideoSpeedControlAlpha el: $('.secondary-controls'), speeds: @video.speeds, currentSpeed: '1.0'
$('li[data-speed="1.0"] a').addClass 'active'
@speedControl.setSpeed '0.75'
it 'set the new speed as active', ->
expect($('.video_speeds li[data-speed="1.0"]')).not.toHaveClass 'active'
expect($('.video_speeds li[data-speed="0.75"]')).toHaveClass 'active'
expect($('.speeds p.active')).toHaveHtml '0.75x'
// Generated by CoffeeScript 1.6.3
(function() {
xdescribe('VideoSpeedControlAlpha', function() {
beforeEach(function() {
window.onTouchBasedDevice = jasmine.createSpy('onTouchBasedDevice').andReturn(false);
jasmine.stubVideoPlayerAlpha(this);
return $('.speeds').remove();
});
describe('constructor', function() {
describe('always', function() {
beforeEach(function() {
return this.speedControl = new VideoSpeedControlAlpha({
el: $('.secondary-controls'),
speeds: this.video.speeds,
currentSpeed: '1.0'
});
});
it('add the video speed control to player', function() {
var li, secondaryControls,
_this = this;
secondaryControls = $('.secondary-controls');
li = secondaryControls.find('.video_speeds li');
expect(secondaryControls).toContain('.speeds');
expect(secondaryControls).toContain('.video_speeds');
expect(secondaryControls.find('p.active').text()).toBe('1.0x');
expect(li.filter('.active')).toHaveData('speed', this.speedControl.currentSpeed);
expect(li.length).toBe(this.speedControl.speeds.length);
return $.each(li.toArray().reverse(), function(index, link) {
expect($(link)).toHaveData('speed', _this.speedControl.speeds[index]);
return expect($(link).find('a').text()).toBe(_this.speedControl.speeds[index] + 'x');
});
});
return it('bind to change video speed link', function() {
return expect($('.video_speeds a')).toHandleWith('click', this.speedControl.changeVideoSpeed);
});
});
describe('when running on touch based device', function() {
beforeEach(function() {
window.onTouchBasedDevice.andReturn(true);
$('.speeds').removeClass('open');
return this.speedControl = new VideoSpeedControlAlpha({
el: $('.secondary-controls'),
speeds: this.video.speeds,
currentSpeed: '1.0'
});
});
return it('open the speed toggle on click', function() {
$('.speeds').click();
expect($('.speeds')).toHaveClass('open');
$('.speeds').click();
return expect($('.speeds')).not.toHaveClass('open');
});
});
return describe('when running on non-touch based device', function() {
beforeEach(function() {
$('.speeds').removeClass('open');
return this.speedControl = new VideoSpeedControlAlpha({
el: $('.secondary-controls'),
speeds: this.video.speeds,
currentSpeed: '1.0'
});
});
it('open the speed toggle on hover', function() {
$('.speeds').mouseenter();
expect($('.speeds')).toHaveClass('open');
$('.speeds').mouseleave();
return expect($('.speeds')).not.toHaveClass('open');
});
it('close the speed toggle on mouse out', function() {
$('.speeds').mouseenter().mouseleave();
return expect($('.speeds')).not.toHaveClass('open');
});
return it('close the speed toggle on click', function() {
$('.speeds').mouseenter().click();
return expect($('.speeds')).not.toHaveClass('open');
});
});
});
describe('changeVideoSpeed', function() {
beforeEach(function() {
this.speedControl = new VideoSpeedControlAlpha({
el: $('.secondary-controls'),
speeds: this.video.speeds,
currentSpeed: '1.0'
});
return this.video.setSpeed('1.0');
});
describe('when new speed is the same', function() {
beforeEach(function() {
spyOnEvent(this.speedControl, 'speedChange');
return $('li[data-speed="1.0"] a').click();
});
return it('does not trigger speedChange event', function() {
return expect('speedChange').not.toHaveBeenTriggeredOn(this.speedControl);
});
});
return describe('when new speed is not the same', function() {
beforeEach(function() {
var _this = this;
this.newSpeed = null;
$(this.speedControl).bind('speedChange', function(event, newSpeed) {
return _this.newSpeed = newSpeed;
});
spyOnEvent(this.speedControl, 'speedChange');
return $('li[data-speed="0.75"] a').click();
});
return it('trigger speedChange event', function() {
expect('speedChange').toHaveBeenTriggeredOn(this.speedControl);
return expect(this.newSpeed).toEqual(0.75);
});
});
});
return describe('onSpeedChange', function() {
beforeEach(function() {
this.speedControl = new VideoSpeedControlAlpha({
el: $('.secondary-controls'),
speeds: this.video.speeds,
currentSpeed: '1.0'
});
$('li[data-speed="1.0"] a').addClass('active');
return this.speedControl.setSpeed('0.75');
});
return it('set the new speed as active', function() {
expect($('.video_speeds li[data-speed="1.0"]')).not.toHaveClass('active');
expect($('.video_speeds li[data-speed="0.75"]')).toHaveClass('active');
return expect($('.speeds p.active')).toHaveHtml('0.75x');
});
});
});
}).call(this);
describe 'VideoVolumeControlAlpha', ->
beforeEach ->
jasmine.stubVideoPlayerAlpha @
$('.volume').remove()
describe 'constructor', ->
beforeEach ->
spyOn($.fn, 'slider')
@volumeControl = new VideoVolumeControlAlpha el: $('.secondary-controls')
it 'initialize currentVolume to 100', ->
expect(@volumeControl.currentVolume).toEqual 100
it 'render the volume control', ->
expect($('.secondary-controls').html()).toContain """
<div class="volume">
<a href="#"></a>
<div class="volume-slider-container">
<div class="volume-slider"></div>
</div>
</div>
"""
it 'create the slider', ->
expect($.fn.slider).toHaveBeenCalledWith
orientation: "vertical"
range: "min"
min: 0
max: 100
value: 100
change: @volumeControl.onChange
slide: @volumeControl.onChange
it 'bind the volume control', ->
expect($('.volume>a')).toHandleWith 'click', @volumeControl.toggleMute
expect($('.volume')).not.toHaveClass 'open'
$('.volume').mouseenter()
expect($('.volume')).toHaveClass 'open'
$('.volume').mouseleave()
expect($('.volume')).not.toHaveClass 'open'
describe 'onChange', ->
beforeEach ->
spyOnEvent @volumeControl, 'volumeChange'
@newVolume = undefined
@volumeControl = new VideoVolumeControlAlpha el: $('.secondary-controls')
$(@volumeControl).bind 'volumeChange', (event, volume) => @newVolume = volume
describe 'when the new volume is more than 0', ->
beforeEach ->
@volumeControl.onChange undefined, value: 60
it 'set the player volume', ->
expect(@newVolume).toEqual 60
it 'remote muted class', ->
expect($('.volume')).not.toHaveClass 'muted'
describe 'when the new volume is 0', ->
beforeEach ->
@volumeControl.onChange undefined, value: 0
it 'set the player volume', ->
expect(@newVolume).toEqual 0
it 'add muted class', ->
expect($('.volume')).toHaveClass 'muted'
describe 'toggleMute', ->
beforeEach ->
@newVolume = undefined
@volumeControl = new VideoVolumeControlAlpha el: $('.secondary-controls')
$(@volumeControl).bind 'volumeChange', (event, volume) => @newVolume = volume
describe 'when the current volume is more than 0', ->
beforeEach ->
@volumeControl.currentVolume = 60
@volumeControl.toggleMute()
it 'save the previous volume', ->
expect(@volumeControl.previousVolume).toEqual 60
it 'set the player volume', ->
expect(@newVolume).toEqual 0
describe 'when the current volume is 0', ->
beforeEach ->
@volumeControl.currentVolume = 0
@volumeControl.previousVolume = 60
@volumeControl.toggleMute()
it 'set the player volume to previous volume', ->
expect(@newVolume).toEqual 60
(function() {
describe('VideoVolumeControlAlpha', function() {
var state, player;
describe('constructor', function() {
beforeEach(function() {
// spyOn($.fn, 'slider');
state = jasmine.stubVideoPlayerAlpha(this);
player = state.videoPlayer.player;
});
it('initialize currentVolume to 100', function() {
return expect(state.videoVolumeControl.currentVolume).toEqual(1);
});
it('render the volume control', function() {
return expect($('.secondary-controls').html()).toContain("<div class=\"volume\">\n <a href=\"#\"></a>\n <div class=\"volume-slider-container\">\n <div class=\"volume-slider\"></div>\n </div>\n</div>");
});
it('create the slider', function() {
return expect($.fn.slider).toHaveBeenCalledWith({
orientation: "vertical",
range: "min",
min: 0,
max: 100,
value: 100,
change: this.volumeControl.onChange,
slide: this.volumeControl.onChange
});
});
return it('bind the volume control', function() {
expect($('.volume>a')).toHandleWith('click', this.volumeControl.toggleMute);
expect($('.volume')).not.toHaveClass('open');
$('.volume').mouseenter();
expect($('.volume')).toHaveClass('open');
$('.volume').mouseleave();
return expect($('.volume')).not.toHaveClass('open');
});
});
describe('onChange', function() {
beforeEach(function() {
var _this = this;
spyOnEvent(this.volumeControl, 'volumeChange');
this.newVolume = void 0;
this.volumeControl = new VideoVolumeControlAlpha({
el: $('.secondary-controls')
});
return $(this.volumeControl).bind('volumeChange', function(event, volume) {
return _this.newVolume = volume;
});
});
describe('when the new volume is more than 0', function() {
beforeEach(function() {
return this.volumeControl.onChange(void 0, {
value: 60
});
});
it('set the player volume', function() {
return expect(this.newVolume).toEqual(60);
});
return it('remote muted class', function() {
return expect($('.volume')).not.toHaveClass('muted');
});
});
return describe('when the new volume is 0', function() {
beforeEach(function() {
return this.volumeControl.onChange(void 0, {
value: 0
});
});
it('set the player volume', function() {
return expect(this.newVolume).toEqual(0);
});
return it('add muted class', function() {
return expect($('.volume')).toHaveClass('muted');
});
});
});
return describe('toggleMute', function() {
beforeEach(function() {
var _this = this;
this.newVolume = void 0;
this.volumeControl = new VideoVolumeControlAlpha({
el: $('.secondary-controls')
});
return $(this.volumeControl).bind('volumeChange', function(event, volume) {
return _this.newVolume = volume;
});
});
describe('when the current volume is more than 0', function() {
beforeEach(function() {
this.volumeControl.currentVolume = 60;
return this.volumeControl.toggleMute();
});
it('save the previous volume', function() {
return expect(this.volumeControl.previousVolume).toEqual(60);
});
return it('set the player volume', function() {
return expect(this.newVolume).toEqual(0);
});
});
return describe('when the current volume is 0', function() {
beforeEach(function() {
this.volumeControl.currentVolume = 0;
this.volumeControl.previousVolume = 60;
return this.volumeControl.toggleMute();
});
return it('set the player volume to previous volume', function() {
return expect(this.newVolume).toEqual(60);
});
});
});
});
}).call(this);
describe 'VideoAlpha', ->
metadata =
slowerSpeedYoutubeId:
id: @slowerSpeedYoutubeId
duration: 300
normalSpeedYoutubeId:
id: @normalSpeedYoutubeId
duration: 200
beforeEach ->
jasmine.stubRequests()
window.onTouchBasedDevice = jasmine.createSpy('onTouchBasedDevice').andReturn false
@videosDefinition = '0.75:slowerSpeedYoutubeId,1.0:normalSpeedYoutubeId'
@slowerSpeedYoutubeId = 'slowerSpeedYoutubeId'
@normalSpeedYoutubeId = 'normalSpeedYoutubeId'
afterEach ->
window.OldVideoPlayerAlpha = undefined
window.onYouTubePlayerAPIReady = undefined
window.onHTML5PlayerAPIReady = undefined
describe 'constructor', ->
describe 'YT', ->
beforeEach ->
loadFixtures 'videoalpha.html'
@stubVideoPlayerAlpha = jasmine.createSpy('VideoPlayerAlpha')
$.cookie.andReturn '0.75'
describe 'by default', ->
beforeEach ->
spyOn(window.VideoAlpha.prototype, 'fetchMetadata').andCallFake ->
@metadata = metadata
@video = new VideoAlpha '#example', @videosDefinition
it 'check videoType', ->
expect(@video.videoType).toEqual('youtube')
it 'reset the current video player', ->
expect(window.OldVideoPlayerAlpha).toBeUndefined()
it 'set the elements', ->
expect(@video.el).toBe '#video_id'
it 'parse the videos', ->
expect(@video.videos).toEqual
'0.75': @slowerSpeedYoutubeId
'1.0': @normalSpeedYoutubeId
it 'fetch the video metadata', ->
expect(@video.fetchMetadata).toHaveBeenCalled
expect(@video.metadata).toEqual metadata
it 'parse available video speeds', ->
expect(@video.speeds).toEqual ['0.75', '1.0']
it 'set current video speed via cookie', ->
expect(@video.speed).toEqual '0.75'
it 'store a reference for this video player in the element', ->
expect($('.video').data('video')).toEqual @video
describe 'when the Youtube API is already available', ->
beforeEach ->
@originalYT = window.YT
window.YT = { Player: true }
spyOn(window, 'VideoPlayerAlpha').andReturn(@stubVideoPlayerAlpha)
@video = new VideoAlpha '#example', @videosDefinition
afterEach ->
window.YT = @originalYT
it 'create the Video Player', ->
expect(window.VideoPlayerAlpha).toHaveBeenCalledWith(video: @video)
expect(@video.player).toEqual @stubVideoPlayerAlpha
describe 'when the Youtube API is not ready', ->
beforeEach ->
@originalYT = window.YT
window.YT = {}
@video = new VideoAlpha '#example', @videosDefinition
afterEach ->
window.YT = @originalYT
it 'set the callback on the window object', ->
expect(window.onYouTubePlayerAPIReady).toEqual jasmine.any(Function)
describe 'when the Youtube API becoming ready', ->
beforeEach ->
@originalYT = window.YT
window.YT = {}
spyOn(window, 'VideoPlayerAlpha').andReturn(@stubVideoPlayerAlpha)
@video = new VideoAlpha '#example', @videosDefinition
window.onYouTubePlayerAPIReady()
afterEach ->
window.YT = @originalYT
it 'create the Video Player for all video elements', ->
expect(window.VideoPlayerAlpha).toHaveBeenCalledWith(video: @video)
expect(@video.player).toEqual @stubVideoPlayerAlpha
describe 'HTML5', ->
beforeEach ->
loadFixtures 'videoalpha_html5.html'
@stubVideoPlayerAlpha = jasmine.createSpy('VideoPlayerAlpha')
$.cookie.andReturn '0.75'
describe 'by default', ->
beforeEach ->
@originalHTML5 = window.HTML5Video.Player
window.HTML5Video.Player = undefined
@video = new VideoAlpha '#example', @videosDefinition
afterEach ->
window.HTML5Video.Player = @originalHTML5
it 'check videoType', ->
expect(@video.videoType).toEqual('html5')
it 'reset the current video player', ->
expect(window.OldVideoPlayerAlpha).toBeUndefined()
it 'set the elements', ->
expect(@video.el).toBe '#video_id'
it 'parse the videos if subtitles exist', ->
sub = 'test_name_of_the_subtitles'
expect(@video.videos).toEqual
'0.75': sub
'1.0': sub
'1.25': sub
'1.5': sub
it 'parse the videos if subtitles doesn\'t exist', ->
$('#example').find('.video').data('sub', '')
@video = new VideoAlpha '#example', @videosDefinition
sub = ''
expect(@video.videos).toEqual
'0.75': sub
'1.0': sub
'1.25': sub
'1.5': sub
it 'parse Html5 sources', ->
html5Sources =
mp4: 'test.mp4'
webm: 'test.webm'
ogg: 'test.ogv'
expect(@video.html5Sources).toEqual html5Sources
it 'parse available video speeds', ->
speeds = jasmine.stubbedHtml5Speeds
expect(@video.speeds).toEqual speeds
it 'set current video speed via cookie', ->
expect(@video.speed).toEqual '0.75'
it 'store a reference for this video player in the element', ->
expect($('.video').data('video')).toEqual @video
describe 'when the HTML5 API is already available', ->
beforeEach ->
@originalHTML5Video = window.HTML5Video
window.HTML5Video = { Player: true }
spyOn(window, 'VideoPlayerAlpha').andReturn(@stubVideoPlayerAlpha)
@video = new VideoAlpha '#example', @videosDefinition
afterEach ->
window.HTML5Video = @originalHTML5Video
it 'create the Video Player', ->
expect(window.VideoPlayerAlpha).toHaveBeenCalledWith(video: @video)
expect(@video.player).toEqual @stubVideoPlayerAlpha
describe 'when the HTML5 API is not ready', ->
beforeEach ->
@originalHTML5Video = window.HTML5Video
window.HTML5Video = {}
@video = new VideoAlpha '#example', @videosDefinition
afterEach ->
window.HTML5Video = @originalHTML5Video
it 'set the callback on the window object', ->
expect(window.onHTML5PlayerAPIReady).toEqual jasmine.any(Function)
describe 'when the HTML5 API becoming ready', ->
beforeEach ->
@originalHTML5Video = window.HTML5Video
window.HTML5Video = {}
spyOn(window, 'VideoPlayerAlpha').andReturn(@stubVideoPlayerAlpha)
@video = new VideoAlpha '#example', @videosDefinition
window.onHTML5PlayerAPIReady()
afterEach ->
window.HTML5Video = @originalHTML5Video
it 'create the Video Player for all video elements', ->
expect(window.VideoPlayerAlpha).toHaveBeenCalledWith(video: @video)
expect(@video.player).toEqual @stubVideoPlayerAlpha
describe 'youtubeId', ->
beforeEach ->
loadFixtures 'videoalpha.html'
$.cookie.andReturn '1.0'
@video = new VideoAlpha '#example', @videosDefinition
describe 'with speed', ->
it 'return the video id for given speed', ->
expect(@video.youtubeId('0.75')).toEqual @slowerSpeedYoutubeId
expect(@video.youtubeId('1.0')).toEqual @normalSpeedYoutubeId
describe 'without speed', ->
it 'return the video id for current speed', ->
expect(@video.youtubeId()).toEqual @normalSpeedYoutubeId
describe 'setSpeed', ->
describe 'YT', ->
beforeEach ->
loadFixtures 'videoalpha.html'
@video = new VideoAlpha '#example', @videosDefinition
describe 'when new speed is available', ->
beforeEach ->
@video.setSpeed '0.75'
it 'set new speed', ->
expect(@video.speed).toEqual '0.75'
it 'save setting for new speed', ->
expect($.cookie).toHaveBeenCalledWith 'video_speed', '0.75', expires: 3650, path: '/'
describe 'when new speed is not available', ->
beforeEach ->
@video.setSpeed '1.75'
it 'set speed to 1.0x', ->
expect(@video.speed).toEqual '1.0'
describe 'HTML5', ->
beforeEach ->
loadFixtures 'videoalpha_html5.html'
@video = new VideoAlpha '#example', @videosDefinition
describe 'when new speed is available', ->
beforeEach ->
@video.setSpeed '0.75'
it 'set new speed', ->
expect(@video.speed).toEqual '0.75'
it 'save setting for new speed', ->
expect($.cookie).toHaveBeenCalledWith 'video_speed', '0.75', expires: 3650, path: '/'
describe 'when new speed is not available', ->
beforeEach ->
@video.setSpeed '1.75'
it 'set speed to 1.0x', ->
expect(@video.speed).toEqual '1.0'
describe 'getDuration', ->
beforeEach ->
loadFixtures 'videoalpha.html'
@video = new VideoAlpha '#example', @videosDefinition
it 'return duration for current video', ->
expect(@video.getDuration()).toEqual 200
describe 'log', ->
beforeEach ->
loadFixtures 'videoalpha.html'
@video = new VideoAlpha '#example', @videosDefinition
spyOn Logger, 'log'
@video.log 'someEvent', {
currentTime: 25,
speed: '1.0'
}
it 'call the logger with valid extra parameters', ->
expect(Logger.log).toHaveBeenCalledWith 'someEvent',
id: 'id'
code: @normalSpeedYoutubeId
currentTime: 25
speed: '1.0'
// IE browser supports Function.bind() only starting with version 9.
//
// The bind function is a recent addition to ECMA-262, 5th edition; as such it may not be present in all
// browsers. You can partially work around this by inserting the following code at the beginning of your
// scripts, allowing use of much of the functionality of bind() in implementations that do not natively support
// it.
//
// https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Function/bind
if (!Function.prototype.bind) {
Function.prototype.bind = function (oThis) {
var aArgs, fToBind, fNOP, fBound;
if (typeof this !== 'function') {
// closest thing possible to the ECMAScript 5 internal IsCallable function
throw new TypeError('Function.prototype.bind - what is trying to be bound is not callable');
}
aArgs = Array.prototype.slice.call(arguments, 1);
fToBind = this;
fNOP = function () {};
fBound = function () {
return fToBind.apply(
this instanceof fNOP && oThis ? this : oThis,
aArgs.concat(Array.prototype.slice.call(arguments))
);
};
fNOP.prototype = this.prototype;
fBound.prototype = new fNOP();
return fBound;
};
}
// IE browser supports Array.indexOf() only starting with version 9.
//
// indexOf is a recent addition to the ECMA-262 standard; as such it may not be present in all browsers. You can work
// around this by utilizing the following code at the beginning of your scripts. This will allow you to use indexOf
// when there is still no native support. This algorithm matches the one specified in ECMA-262, 5th edition, assuming
// Object, TypeError, Number, Math.floor, Math.abs, and Math.max have their original values.
//
// https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Array/indexOf
if (!Array.prototype.indexOf) {
Array.prototype.indexOf = function (searchElement /*, fromIndex */ ) {
'use strict';
if (this == null) {
throw new TypeError();
}
var t = Object(this);
var len = t.length >>> 0;
if (len === 0) {
return -1;
}
var n = 0;
if (arguments.length > 1) {
n = Number(arguments[1]);
if (n != n) { // shortcut for verifying if it's NaN
n = 0;
} else if (n != 0 && n != Infinity && n != -Infinity) {
n = (n > 0 || -1) * Math.floor(Math.abs(n));
}
}
if (n >= len) {
return -1;
}
var k = n >= 0 ? n : Math.max(len - Math.abs(n), 0);
for (; k < len; k++) {
if (k in t && t[k] === searchElement) {
return k;
}
}
return -1;
}
}
......@@ -23,7 +23,7 @@ function () {
Player.prototype.callStateChangeCallback = function () {
if ($.isFunction(this.config.events.onStateChange)) {
this.config.events.onStateChange({
'data': this.playerState
data: this.playerState
});
}
};
......@@ -96,61 +96,47 @@ function () {
*
* config = {
*
* 'videoSources': {}, // An object with properties being video sources. The property name is the
* videoSources: {}, // An object with properties being video sources. The property name is the
* // video format of the source. Supported video formats are: 'mp4', 'webm', and
* // 'ogg'.
*
* 'playerVars': { // Object's properties identify player parameters.
* 'start': 0, // Possible values: positive integer. Position from which to start playing the
* playerVars: { // Object's properties identify player parameters.
* start: 0, // Possible values: positive integer. Position from which to start playing the
* // video. Measured in seconds. If value is non-numeric, or 'start' property is
* // not specified, the video will start playing from the beginning.
*
* 'end': null // Possible values: positive integer. Position when to stop playing the
* end: null // Possible values: positive integer. Position when to stop playing the
* // video. Measured in seconds. If value is null, or 'end' property is not
* // specified, the video will end playing at the end.
*
* },
*
* 'events': { // Object's properties identify the events that the API fires, and the
* events: { // Object's properties identify the events that the API fires, and the
* // functions (event listeners) that the API will call when those events occur.
* // If value is null, or property is not specified, then no callback will be
* // called for that event.
*
* 'onReady': null,
* 'onStateChange': null
* onReady: null,
* onStateChange: null
* }
* }
*/
function Player(el, config) {
var sourceStr, _this;
// If el is string, we assume it is an ID of a DOM element. Get the element, and check that the ID
// really belongs to an element. If we didn't get a DOM element, return. At this stage, nothing will
// break because other parts of the video player are waiting for 'onReady' callback to be called.
// REFACTOR: Use .length === 0
this.el = $(el);
// REFACTOR: Simplify chck.
if (this.el.length === 0) {
return;
}
if (typeof el === 'string') {
this.el = $(el);
// REFACTOR: Simplify chck.
// Initially we assume that el is a DOM element. If jQuery selector fails to select something, we
// assume that el is an ID of a DOM element. We try to select by ID. If jQuery fails this time,
// we return. Nothing breaks because the player 'onReady' event will never be fired.
this.el = $(el);
if (this.el.length === 0) {
this.el = $('#' + el);
if (this.el.length === 0) {
return;
}
} else if (el instanceof jQuery) {
this.el = el;
} else {
return;
}
// A simple test to see that the 'config' is a normal object.
if ($.isPlainObject(config)) {
this.config = config;
......@@ -165,9 +151,9 @@ function () {
// From the start, all sources are empty. We will populate this object below.
sourceStr = {
'mp4': ' ',
'webm': ' ',
'ogg': ' '
mp4: ' ',
webm: ' ',
ogg: ' '
};
// Will be used in inner functions to point to the current object.
......@@ -304,14 +290,16 @@ function () {
}
}());
// REFACTOR: Doc.
// The YouTube API presents several constants which describe the player's state at a given moment.
// HTML5Video API will copy these constats so that code which uses both the YouTube API and this API
// doesn't have to change.
HTML5Video.PlayerState = {
'UNSTARTED': -1,
'ENDED': 0,
'PLAYING': 1,
'PAUSED': 2,
'BUFFERING': 3,
'CUED': 5
UNSTARTED: -1,
ENDED: 0,
PLAYING: 1,
PAUSED: 2,
BUFFERING: 3,
CUED: 5
};
// HTML5Video object - what this module exports.
......
......@@ -25,7 +25,6 @@ function (VideoPlayer) {
* @param {DOM element} element Container of the entire Video Alpha DOM element.
*/
return function (state, element) {
checkForNativeFunctions();
makeFunctionsPublic(state);
renderElements(state, element);
};
......@@ -57,6 +56,9 @@ function (VideoPlayer) {
function renderElements(state, element) {
var onPlayerReadyFunc;
// This is used in places where we instead would have to check if an element has a CSS class 'fullscreen'.
state.isFullScreen = false;
// The parent element of the video, and the ID.
state.el = $(element).find('.videoalpha');
state.id = state.el.attr('id').replace(/video_/, '');
......@@ -77,7 +79,18 @@ function (VideoPlayer) {
sub: state.el.data('sub'),
mp4Source: state.el.data('mp4-source'),
webmSource: state.el.data('webm-source'),
oggSource: state.el.data('ogg-source')
oggSource: state.el.data('ogg-source'),
fadeOutTimeout: 1400,
availableQualities: ['hd720', 'hd1080', 'highres'],
qTipConfig: {
position: {
my: 'top right',
at: 'top center'
}
}
};
// Try to parse YouTube stream ID's. If
......@@ -95,9 +108,9 @@ function (VideoPlayer) {
parseVideoSources(
state,
{
'mp4': state.config.mp4Source,
'webm': state.config.webmSource,
'ogg': state.config.oggSource
mp4: state.config.mp4Source,
webm: state.config.webmSource,
ogg: state.config.oggSource
}
);
......@@ -136,8 +149,8 @@ function (VideoPlayer) {
state.hide_captions = true;
$.cookie('hide_captions', state.hide_captions, {
'expires': 3650,
'path': '/'
expires: 3650,
path: '/'
});
state.el.addClass('closed');
......@@ -153,8 +166,8 @@ function (VideoPlayer) {
state.currentPlayerMode = currentPlayerMode;
} else {
$.cookie('current_player_mode', 'html5', {
'expires': 3650,
'path': '/'
expires: 3650,
path: '/'
});
state.currentPlayerMode = 'html5';
}
......@@ -196,7 +209,7 @@ function (VideoPlayer) {
function parseYoutubeStreams(state, youtubeStreams) {
if (!youtubeStreams.length) {
if (typeof youtubeStreams === 'undefined' || youtubeStreams.length === 0) {
return false;
}
......@@ -219,7 +232,11 @@ function (VideoPlayer) {
// Take the HTML5 sources (URLs of videos), and make them available explictly for each type
// of video format (mp4, webm, ogg).
function parseVideoSources(state, sources) {
state.html5Sources = { 'mp4': null, 'webm': null, 'ogg': null };
state.html5Sources = {
mp4: null,
webm: null,
ogg: null
};
$.each(sources, function (name, source) {
if (source && source.length) {
......@@ -254,48 +271,6 @@ function (VideoPlayer) {
state.setSpeed($.cookie('video_speed'));
}
function checkForNativeFunctions() {
// REFACTOR:
// 1.) IE8 doc.
// 2.) Move to separate file.
// 3.) Write about a generic soluction system wide.
//
// IE browser supports Function.bind() only starting with version 8.
//
// The bind function is a recent addition to ECMA-262, 5th edition; as such it may not be present in all
// browsers. You can partially work around this by inserting the following code at the beginning of your
// scripts, allowing use of much of the functionality of bind() in implementations that do not natively support
// it.
//
// https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Function/bind
if (!Function.prototype.bind) {
Function.prototype.bind = function (oThis) {
var aArgs, fToBind, fNOP, fBound;
if (typeof this !== 'function') {
// closest thing possible to the ECMAScript 5 internal IsCallable function
throw new TypeError('Function.prototype.bind - what is trying to be bound is not callable');
}
aArgs = Array.prototype.slice.call(arguments, 1);
fToBind = this;
fNOP = function () {};
fBound = function () {
return fToBind.apply(
this instanceof fNOP && oThis ? this : oThis,
aArgs.concat(Array.prototype.slice.call(arguments))
);
};
fNOP.prototype = this.prototype;
fBound.prototype = new fNOP();
return fBound;
};
}
}
// ***************************************************************
// Public functions start here.
// These are available via the 'state' object. Their context ('this' keyword) is the 'state' object.
......@@ -311,8 +286,8 @@ function (VideoPlayer) {
if (updateCookie) {
$.cookie('video_speed', this.speed, {
'expires': 3650,
'path': '/'
expires: 3650,
path: '/'
});
}
}
......
(function (requirejs, require, define) {
// REFACTOR. Build JS doc. Add docs on how to build docs.
// Main module.
require(
[
......@@ -38,7 +36,7 @@ function (
// Check for existance of previous state, uninitialize it if necessary, and create a new state.
// Store new state for future invocation of this module consturctor function.
if (previousState !== null) {
if (previousState !== null && typeof previousState.videoPlayer !== 'undefined') {
previousState.videoPlayer.onPause();
}
state = {};
......@@ -55,6 +53,11 @@ function (
if (state.config.show_captions) {
VideoCaption(state);
}
// Because the 'state' object is only available inside this closure, we will also make
// it available to the caller by returning it. This is necessary so that we can test
// VideoAlpha with Jasmine.
return state;
};
});
......
......@@ -60,14 +60,13 @@ function () {
state.el.find('.video-controls .secondary-controls').append(state.videoCaption.hideSubtitlesEl);
state.el.find('.subtitles').css({
'maxHeight': state.el.find('.video-wrapper').height() - 5
maxHeight: state.el.find('.video-wrapper').height() - 5
});
fetchCaption(state);
// REFACTOR. Const.
if (state.videoType === 'html5') {
state.videoCaption.fadeOutTimeout = 1400;
state.videoCaption.fadeOutTimeout = state.config.fadeOutTimeout;
state.videoCaption.subtitlesEl.addClass('html5');
state.captionHideTimeout = setTimeout(state.videoCaption.autoHideCaptions, state.videoCaption.fadeOutTimeout);
......@@ -80,21 +79,24 @@ function () {
function bindHandlers(state) {
$(window).bind('resize', state.videoCaption.resize);
state.videoCaption.hideSubtitlesEl.click(state.videoCaption.toggle);
// REFACTOR: Use .on()
state.videoCaption.subtitlesEl.mouseenter(
state.videoCaption.onMouseEnter
).mouseleave(
state.videoCaption.onMouseLeave
).mousemove(
state.videoCaption.onMovement
).bind(
'mousewheel',
state.videoCaption.onMovement
).bind(
'DOMMouseScroll',
state.videoCaption.onMovement
);
state.videoCaption.subtitlesEl
.on(
'mouseenter',
state.videoCaption.onMouseEnter
).on(
'mouseleave',
state.videoCaption.onMouseLeave
).on(
'mousemove',
state.videoCaption.onMovement
).on(
'mousewheel',
state.videoCaption.onMovement
).on(
'DOMMouseScroll',
state.videoCaption.onMovement
);
if (state.videoType === 'html5') {
state.el.on('mousemove', state.videoCaption.autoShowCaptions)
......@@ -137,23 +139,18 @@ function () {
this.captionsShowLock = true;
// REFACTOR.
if (this.captionState === 'invisible') {
this.videoCaption.subtitlesEl.show();
this.captionState = 'visible';
this.captionHideTimeout = setTimeout(this.videoCaption.autoHideCaptions, this.videoCaption.fadeOutTimeout);
} else if (this.captionState === 'hiding') {
this.videoCaption.subtitlesEl.stop(true, false);
this.videoCaption.subtitlesEl.css('opacity', 1);
this.videoCaption.subtitlesEl.show();
this.videoCaption.subtitlesEl.stop(true, false).css('opacity', 1).show();
this.captionState = 'visible';
this.captionHideTimeout = setTimeout(this.videoCaption.autoHideCaptions, this.videoCaption.fadeOutTimeout);
} else if (this.captionState === 'visible') {
clearTimeout(this.captionHideTimeout);
this.captionHideTimeout = setTimeout(this.videoCaption.autoHideCaptions, this.videoCaption.fadeOutTimeout);
}
this.captionHideTimeout = setTimeout(this.videoCaption.autoHideCaptions, this.videoCaption.fadeOutTimeout);
this.captionsShowLock = false;
}
}
......@@ -171,20 +168,19 @@ function () {
_this = this;
// REFACTOR.
this.videoCaption.subtitlesEl.fadeOut(1000, function () {
this.videoCaption.subtitlesEl.fadeOut(this.videoCaption.fadeOutTimeout, function () {
_this.captionState = 'invisible';
});
}
function resize() {
this.videoCaption.subtitlesEl.css({
'maxHeight': this.videoCaption.captionHeight()
maxHeight: this.videoCaption.captionHeight()
});
// REFACTOR: Chain.
this.videoCaption.subtitlesEl.find('.spacing:first').height(this.videoCaption.topSpacingHeight());
this.videoCaption.subtitlesEl.find('.spacing:last').height(this.videoCaption.bottomSpacingHeight());
this.videoCaption.subtitlesEl
.find('.spacing:first').height(this.videoCaption.topSpacingHeight())
.find('.spacing:last').height(this.videoCaption.bottomSpacingHeight());
this.videoCaption.scrollCaption();
}
......@@ -194,7 +190,6 @@ function () {
clearTimeout(this.videoCaption.frozen);
}
// REFACTOR.
this.videoCaption.frozen = setTimeout(this.videoCaption.onMouseLeave, 10000);
}
......@@ -219,37 +214,34 @@ function () {
container = $('<ol>');
$.each(this.videoCaption.captions, function(index, text) {
// REFACTOR: Use .data()
container.append($('<li>').html(text).attr({
'data-index': index,
'data-start': _this.videoCaption.start[index]
}));
container.append(
$('<li>').html(text)
.data('index', index)
.data('start', _this.videoCaption.start[index])
);
});
// REFACTOR: Chain.
this.videoCaption.subtitlesEl.html(container.html());
this.videoCaption.subtitlesEl.find('li[data-index]').on('click', this.videoCaption.seekPlayer);
this.videoCaption.subtitlesEl
.prepend(
$('<li class="spacing">').height(this.videoCaption.topSpacingHeight())
)
.append(
$('<li class="spacing">').height(this.videoCaption.bottomSpacingHeight())
);
.html(container.html())
.find('li[data-index]').on('click', this.videoCaption.seekPlayer)
.prepend(
$('<li class="spacing">').height(this.videoCaption.topSpacingHeight())
)
.append(
$('<li class="spacing">').height(this.videoCaption.bottomSpacingHeight())
);
this.videoCaption.rendered = true;
}
function scrollCaption() {
// REFACTOR: Cache current:first
if (
!this.videoCaption.frozen &&
this.videoCaption.subtitlesEl.find('.current:first').length
) {
var el = this.videoCaption.subtitlesEl.find('.current:first');
if (!this.videoCaption.frozen && el.length) {
this.videoCaption.subtitlesEl.scrollTo(
this.videoCaption.subtitlesEl.find('.current:first'),
el,
{
'offset': -this.videoCaption.calculateOffset(this.videoCaption.subtitlesEl.find('.current:first'))
offset: -this.videoCaption.calculateOffset(el)
}
);
}
......@@ -323,7 +315,7 @@ function () {
event.preventDefault();
time = Math.round(Time.convert($(event.target).data('start'), '1.0', this.speed) / 1000);
this.trigger(['videoPlayer', 'onSeek'], time);
this.trigger(['videoPlayer', 'onCaptionSeek'], time);
}
function calculateOffset(element) {
......@@ -361,14 +353,13 @@ function () {
}
$.cookie('hide_captions', hide_captions, {
'expires': 3650,
'path': '/'
expires: 3650,
path: '/'
});
}
function captionHeight() {
// REFACTOR: Use property instead of class.
if (this.el.hasClass('fullscreen')) {
if (this.isFullScreen) {
return $(window).height() - this.el.find('.video-controls').height();
} else {
return this.el.find('.video-wrapper').height();
......
......@@ -40,8 +40,6 @@ function () {
// make the created DOM elements available via the 'state' object. Much easier to work this
// way - you don't have to do repeated jQuery element selects.
function renderElements(state) {
var qTipConfig;
state.videoControl.el = state.el.find('.video-controls');
// state.videoControl.el.append(el);
......@@ -56,23 +54,14 @@ function () {
if (!onTouchBasedDevice()) {
state.videoControl.pause();
qTipConfig = {
'position': {
'my': 'top right',
'at': 'top center'
}
};
state.videoControl.playPauseEl.qtip(qTipConfig);
state.videoControl.fullScreenEl.qtip(qTipConfig);
state.videoControl.playPauseEl.qtip(state.config.qTipConfig);
state.videoControl.fullScreenEl.qtip(state.config.qTipConfig);
} else {
state.videoControl.play();
}
if (state.videoType === 'html5') {
// REFACTOR: constants move to initialize()
state.videoControl.fadeOutTimeout = 1400;
state.videoControl.fadeOutTimeout = state.config.fadeOutTimeout;
state.videoControl.el.addClass('html5');
state.controlHideTimeout = setTimeout(state.videoControl.hideControls, state.videoControl.fadeOutTimeout);
......@@ -97,7 +86,6 @@ function () {
// These are available via the 'state' object. Their context ('this' keyword) is the 'state' object.
// The magic private function that makes them available and sets up their context is makeFunctionsPublic().
// ***************************************************************
// REFACTOR document
function showControls(event) {
if (!this.controlShowLock) {
if (!this.captionsHidden) {
......@@ -106,25 +94,18 @@ function () {
this.controlShowLock = true;
// Refactor: separate UI state in object. No code duplication.
// REFACTOR:
// 1.) Chain jQuery calls.
// 2.) Drop out common code.
if (this.controlState === 'invisible') {
this.videoControl.el.show();
this.controlState = 'visible';
this.controlHideTimeout = setTimeout(this.videoControl.hideControls, this.videoControl.fadeOutTimeout);
} else if (this.controlState === 'hiding') {
this.videoControl.el.stop(true, false);
this.videoControl.el.css('opacity', 1);
this.videoControl.el.show();
this.videoControl.el.stop(true, false).css('opacity', 1).show();
this.controlState = 'visible';
this.controlHideTimeout = setTimeout(this.videoControl.hideControls, this.videoControl.fadeOutTimeout);
} else if (this.controlState === 'visible') {
clearTimeout(this.controlHideTimeout);
this.controlHideTimeout = setTimeout(this.videoControl.hideControls, this.videoControl.fadeOutTimeout);
}
this.controlHideTimeout = setTimeout(this.videoControl.hideControls, this.videoControl.fadeOutTimeout);
this.controlShowLock = false;
}
}
......@@ -142,28 +123,27 @@ function () {
_this = this;
this.videoControl.el.fadeOut(1000, function () {
this.videoControl.el.fadeOut(this.videoControl.fadeOutTimeout, function () {
_this.controlState = 'invisible';
});
}
function play() {
// REFACTOR: this.videoControl.playPauseState should be bool.
this.videoControl.playPauseEl.removeClass('play').addClass('pause').attr('title', 'Pause');
this.videoControl.playPauseState = 'playing';
this.videoControl.isPlaying = true;
}
function pause() {
this.videoControl.playPauseEl.removeClass('pause').addClass('play').attr('title', 'Play');
this.videoControl.playPauseState = 'paused';
this.videoControl.isPlaying = false;
}
function togglePlayback(event) {
event.preventDefault();
if (this.videoControl.playPauseState === 'playing') {
if (this.videoControl.isPlaying) {
this.trigger(['videoPlayer', 'pause'], null);
} else { // if (this.videoControl.playPauseState === 'paused') {
} else {
this.trigger(['videoPlayer', 'play'], null);
}
}
......@@ -174,10 +154,12 @@ function () {
if (this.videoControl.fullScreenState) {
this.videoControl.fullScreenState = false;
this.el.removeClass('fullscreen');
this.isFullScreen = false;
this.videoControl.fullScreenEl.attr('title', 'Fullscreen');
} else {
this.videoControl.fullScreenState = true;
this.el.addClass('fullscreen');
this.isFullScreen = true;
this.videoControl.fullScreenEl.attr('title', 'Exit fullscreen');
}
......@@ -185,8 +167,7 @@ function () {
}
function exitFullScreen(event) {
// REFACTOR: Add variable instead of class.
if ((this.el.hasClass('fullscreen')) && (event.keyCode === 27)) {
if ((this.isFullScreen) && (event.keyCode === 27)) {
this.videoControl.toggleFullScreen(event);
}
}
......
......@@ -28,7 +28,8 @@ function (HTML5Video) {
state.videoPlayer.play = play.bind(state);
state.videoPlayer.update = update.bind(state);
state.videoPlayer.onSpeedChange = onSpeedChange.bind(state);
state.videoPlayer.onSeek = onSeek.bind(state);
state.videoPlayer.onCaptionSeek = onSeek.bind(state);
state.videoPlayer.onSlideSeek = onSeek.bind(state);
state.videoPlayer.onEnded = onEnded.bind(state);
state.videoPlayer.onPause = onPause.bind(state);
state.videoPlayer.onPlay = onPlay.bind(state);
......@@ -62,12 +63,12 @@ function (HTML5Video) {
state.videoPlayer.currentTime = 0;
state.videoPlayer.playerVars = {
'controls': 0,
'wmode': 'transparent',
'rel': 0,
'showinfo': 0,
'enablejsapi': 1,
'modestbranding': 1
controls: 0,
wmode: 'transparent',
rel: 0,
showinfo: 0,
enablejsapi: 1,
modestbranding: 1
};
if (state.currentPlayerMode !== 'flash') {
......@@ -82,13 +83,26 @@ function (HTML5Video) {
state.videoPlayer.playerVars.end = state.config.end;
}
// There is a bug which prevents YouTube API to correctly set the speed
// to 1.0 from another speed in Firefox when in HTML5 mode. There is a
// fix which basically reloads the video at speed 1.0 when this change
// is requested (instead of simply requesting a speed change to 1.0).
// This has to be done only when the video is being watched in Firefox.
// We need to figure out what browser is currently executing this code.
//
// TODO: Check the status of
// http://code.google.com/p/gdata-issues/issues/detail?id=4654
// When the YouTube team fixes the API bug, we can remove this temporary
// bug fix.
state.browserIsFirefox = navigator.userAgent.toLowerCase().indexOf('firefox') > -1;
if (state.videoType === 'html5') {
state.videoPlayer.player = new HTML5Video.Player(state.el, {
'playerVars': state.videoPlayer.playerVars,
'videoSources': state.html5Sources,
'events': {
'onReady': state.videoPlayer.onReady,
'onStateChange': state.videoPlayer.onStateChange
playerVars: state.videoPlayer.playerVars,
videoSources: state.html5Sources,
events: {
onReady: state.videoPlayer.onReady,
onStateChange: state.videoPlayer.onStateChange
}
});
} else { // if (state.videoType === 'youtube') {
......@@ -98,12 +112,12 @@ function (HTML5Video) {
youTubeId = state.youtubeId('1.0');
}
state.videoPlayer.player = new YT.Player(state.id, {
'playerVars': state.videoPlayer.playerVars,
'videoId': youTubeId,
'events': {
'onReady': state.videoPlayer.onReady,
'onStateChange': state.videoPlayer.onStateChange,
'onPlaybackQualityChange': state.videoPlayer.onPlaybackQualityChange
playerVars: state.videoPlayer.playerVars,
videoId: youTubeId,
events: {
onReady: state.videoPlayer.onReady,
onStateChange: state.videoPlayer.onStateChange,
onPlaybackQualityChange: state.videoPlayer.onPlaybackQualityChange
}
});
}
......@@ -173,6 +187,11 @@ function (HTML5Video) {
}
}
// We request the reloading of the video in the case when YouTube is in
// Flash player mode, or when we are in Firefox, and the new speed is 1.0.
// The second case is necessary to avoid the bug where in Firefox speed
// switching to 1.0 in HTML5 player mode is handled incorrectly by YouTube
// API.
function onSpeedChange(newSpeed, updateCookie) {
if (this.currentPlayerMode === 'flash') {
this.videoPlayer.currentTime = Time.convert(
......@@ -182,9 +201,19 @@ function (HTML5Video) {
);
}
newSpeed = parseFloat(newSpeed).toFixed(2).replace(/\.00$/, '.0');
this.videoPlayer.log(
'speed_change_video',
{
current_time: this.videoPlayer.currentTime,
old_speed: this.speed,
new_speed: newSpeed
}
);
this.setSpeed(newSpeed, updateCookie);
if (this.currentPlayerMode === 'html5') {
if (this.currentPlayerMode === 'html5' && !(state.browserIsFirefox && newSpeed === '1.0')) {
this.videoPlayer.player.setPlaybackRate(newSpeed);
} else { // if (this.currentPlayerMode === 'flash') {
if (this.videoPlayer.isPlaying()) {
......@@ -197,7 +226,16 @@ function (HTML5Video) {
}
}
function onSeek(time) {
function onSeek(event, time) {
this.videoPlayer.log(
'seek_video',
{
old_time: this.videoPlayer.currentTime,
new_time: time,
type: event.type
}
);
this.videoPlayer.player.seekTo(time, true);
if (this.videoPlayer.isPlaying()) {
......@@ -215,7 +253,12 @@ function (HTML5Video) {
}
function onPause() {
this.videoPlayer.log('pause_video');
this.videoPlayer.log(
'pause_video',
{
'currentTime': this.videoPlayer.currentTime
}
);
clearInterval(this.videoPlayer.updateInterval);
delete this.videoPlayer.updateInterval;
......@@ -224,7 +267,12 @@ function (HTML5Video) {
}
function onPlay() {
this.videoPlayer.log('play_video');
this.videoPlayer.log(
'play_video',
{
'currentTime': this.videoPlayer.currentTime
}
);
if (!this.videoPlayer.updateInterval) {
this.videoPlayer.updateInterval = setInterval(this.videoPlayer.update, 200);
......@@ -249,8 +297,8 @@ function (HTML5Video) {
function onReady() {
var availablePlaybackRates, baseSpeedSubs, _this;
// REFACTOR: Check if logic.
this.videoPlayer.log('load_video');
availablePlaybackRates = this.videoPlayer.player.getAvailablePlaybackRates();
if ((this.currentPlayerMode === 'html5') && (this.videoType === 'youtube')) {
......@@ -283,7 +331,7 @@ function (HTML5Video) {
this.videoPlayer.player.setPlaybackRate(this.speed);
}
if (!onTouchBasedDevice()) {
if (!onTouchBasedDevice() && $('.video:first').data('autoplay') === 'True') {
this.videoPlayer.play();
}
}
......@@ -330,16 +378,22 @@ function (HTML5Video) {
return duration;
}
function log(eventName) {
function log(eventName, data) {
var logInfo;
// Default parameters that always get logged.
logInfo = {
'id': this.id,
'code': this.youtubeId(),
'currentTime': this.videoPlayer.currentTime,
'speed': this.speed
'id': this.id,
'code': this.youtubeId()
};
// If extra parameters were passed to the log.
if (data) {
$.each(data, function(paramName, value) {
logInfo[paramName] = value;
});
}
if (this.videoType === 'youtube') {
logInfo.code = this.youtubeId();
} else {
......
......@@ -61,10 +61,10 @@ function () {
function buildSlider(state) {
state.videoProgressSlider.slider = state.videoProgressSlider.el.slider({
'range': 'min',
'change': state.videoProgressSlider.onChange,
'slide': state.videoProgressSlider.onSlide,
'stop': state.videoProgressSlider.onStop
range: 'min',
change: state.videoProgressSlider.onChange,
slide: state.videoProgressSlider.onSlide,
stop: state.videoProgressSlider.onStop
});
}
......@@ -72,18 +72,18 @@ function () {
state.videoProgressSlider.handle = state.videoProgressSlider.el.find('.ui-slider-handle');
state.videoProgressSlider.handle.qtip({
'content': '' + Time.format(state.videoProgressSlider.slider.slider('value')),
'position': {
'my': 'bottom center',
'at': 'top center',
'container': state.videoProgressSlider.handle
content: '' + Time.format(state.videoProgressSlider.slider.slider('value')),
position: {
my: 'bottom center',
at: 'top center',
container: state.videoProgressSlider.handle
},
'hide': {
'delay': 700
hide: {
delay: 700
},
'style': {
'classes': 'ui-tooltip-slider',
'widget': true
style: {
classes: 'ui-tooltip-slider',
widget: true
}
});
}
......@@ -97,7 +97,7 @@ function () {
function onSlide(event, ui) {
this.videoProgressSlider.frozen = true;
this.videoProgressSlider.updateTooltip(ui.value);
this.trigger(['videoPlayer', 'onSeek'], ui.value);
this.trigger(['videoPlayer', 'onSlideSeek'], ui.value);
}
function onChange(event, ui) {
......@@ -109,7 +109,7 @@ function () {
this.videoProgressSlider.frozen = true;
this.trigger(['videoPlayer', 'onSeek'], ui.value);
this.trigger(['videoPlayer', 'onSlideSeek'], ui.value);
setTimeout(function() {
_this.videoProgressSlider.frozen = false;
......@@ -122,9 +122,9 @@ function () {
function updatePlayTime(params) {
if ((this.videoProgressSlider.slider) && (!this.videoProgressSlider.frozen)) {
// REFACTOR: Check if you can chain.
this.videoProgressSlider.slider.slider('option', 'max', params.duration);
this.videoProgressSlider.slider.slider('value', params.time);
this.videoProgressSlider.slider
.slider('option', 'max', params.duration)
.slider('value', params.time);
}
}
......
......@@ -44,13 +44,7 @@ function () {
state.videoQualityControl.quality = null;
if (!onTouchBasedDevice()) {
// REFACTOR: Move qtip config to state.config
state.videoQualityControl.el.qtip({
'position': {
'my': 'top right',
'at': 'top center'
}
});
state.videoQualityControl.el.qtip(state.config.qTipConfig);
}
}
......@@ -70,8 +64,7 @@ function () {
function onQualityChange(value) {
this.videoQualityControl.quality = value;
// refactor: Move constants to state.config.
if ((value === 'hd720') || (value === 'hd1080') || (value === 'highres')) {
if (this.config.availableQualities.indexOf(value) !== -1) {
this.videoQualityControl.el.addClass('active');
} else {
this.videoQualityControl.el.removeClass('active');
......@@ -79,14 +72,12 @@ function () {
}
function toggleQuality(event) {
var newQuality, _ref;
var newQuality,
value = this.videoQualityControl.quality;
event.preventDefault();
_ref = this.videoQualityControl.quality;
// refactor: Move constants to state.config.
if ((_ref === 'hd720') || (_ref === 'hd1080') || (_ref === 'highres')) {
if (this.config.availableQualities.indexOf(value) !== -1) {
newQuality = 'large';
} else {
newQuality = 'hd720';
......
......@@ -44,13 +44,9 @@ function () {
state.videoControl.secondaryControlsEl.prepend(state.videoSpeedControl.el);
$.each(state.videoSpeedControl.speeds, function(index, speed) {
// REFACTOR: Move all HTML into one function call.
var link = $('<a>').attr({
'href': '#'
}).html('' + speed + 'x');
var link = $('<a href="#">' + speed + 'x</a>');
// REFACTOR: Use jQuery .data()
state.videoSpeedControl.videoSpeedsEl.prepend($('<li>').attr('data-speed', speed).html(link));
state.videoSpeedControl.videoSpeedsEl.prepend($('<li data-speed="' + speed + '">' + link + '</li>'));
});
state.videoSpeedControl.setSpeed(state.speed);
......@@ -68,19 +64,17 @@ function () {
$(this).toggleClass('open');
});
} else {
// REFACTOR: Chain.
state.videoSpeedControl.el.on('mouseenter', function() {
$(this).addClass('open');
});
state.videoSpeedControl.el.on('mouseleave', function() {
$(this).removeClass('open');
});
state.videoSpeedControl.el.on('click', function(event) {
event.preventDefault();
$(this).removeClass('open');
});
state.videoSpeedControl.el
.on('mouseenter', function () {
$(this).addClass('open');
})
.on('mouseleave', function () {
$(this).removeClass('open');
})
.on('click', function (event) {
event.preventDefault();
$(this).removeClass('open');
});
}
}
......@@ -91,18 +85,18 @@ function () {
// ***************************************************************
function setSpeed(speed) {
// REFACTOR: Use chaining.
this.videoSpeedControl.videoSpeedsEl.find('li').removeClass('active');
this.videoSpeedControl.videoSpeedsEl.find("li[data-speed='" + speed + "']").addClass('active');
this.videoSpeedControl.el.find('p.active').html('' + speed + 'x');
}
function changeVideoSpeed(event) {
var parentEl = $(event.target).parent();
event.preventDefault();
// REFACTOR: Cache parent el.
if (!$(event.target).parent().hasClass('active')) {
this.videoSpeedControl.currentSpeed = $(event.target).parent().data('speed');
if (!parentEl.hasClass('active')) {
this.videoSpeedControl.currentSpeed = parentEl.data('speed');
this.videoSpeedControl.setSpeed(
// To meet the API expected format.
......@@ -113,23 +107,19 @@ function () {
}
}
// REFACTOR.
function reRender(params /*newSpeeds, currentSpeed*/) {
var _this;
function reRender(params) {
var _this = this;
this.videoSpeedControl.videoSpeedsEl.empty();
this.videoSpeedControl.videoSpeedsEl.find('li').removeClass('active');
this.videoSpeedControl.speeds = params.newSpeeds;
_this = this;
$.each(this.videoSpeedControl.speeds, function(index, speed) {
var link, listItem;
link = $('<a>').attr({
'href': '#'
}).html('' + speed + 'x');
link = $('<a href="#">' + speed + 'x</a>');
listItem = $('<li>').attr('data-speed', speed).html(link);
listItem = $('<li data-speed="' + speed + '">' + link + '</li>');
if (speed === params.currentSpeed) {
listItem.addClass('active');
......
......@@ -41,28 +41,24 @@ function () {
state.videoControl.secondaryControlsEl.prepend(state.videoVolumeControl.el);
// Figure out what the current volume is. Set it up so that muting/unmuting works correctly.
// If no information about volume level could be retrieved, then we will use the default
// 100 level (full volume).
// REFACTOR: Remove unnecessary checks.
// Figure out what the current volume is. If no information about volume level could be retrieved,
// then we will use the default 100 level (full volume).
state.videoVolumeControl.currentVolume = parseInt($.cookie('video_player_volume_level'), 10);
state.videoVolumeControl.previousVolume = 100;
if (
(!isFinite(state.videoVolumeControl.currentVolume)) ||
(state.videoVolumeControl.currentVolume < 0) ||
(state.videoVolumeControl.currentVolume > 100)
) {
if (!isFinite(state.videoVolumeControl.currentVolume)) {
state.videoVolumeControl.currentVolume = 100;
}
// Set it up so that muting/unmuting works correctly.
state.videoVolumeControl.previousVolume = 100;
state.videoVolumeControl.slider = state.videoVolumeControl.volumeSliderEl.slider({
'orientation': 'vertical',
'range': 'min',
'min': 0,
'max': 100,
'value': state.videoVolumeControl.currentVolume,
'change': state.videoVolumeControl.onChange,
'slide': state.videoVolumeControl.onChange
orientation: 'vertical',
range: 'min',
min: 0,
max: 100,
value: state.videoVolumeControl.currentVolume,
change: state.videoVolumeControl.onChange,
slide: state.videoVolumeControl.onChange
});
state.videoVolumeControl.el.toggleClass('muted', state.videoVolumeControl.currentVolume === 0);
......@@ -94,8 +90,8 @@ function () {
this.videoVolumeControl.el.toggleClass('muted', this.videoVolumeControl.currentVolume === 0);
$.cookie('video_player_volume_level', ui.value, {
'expires': 3650,
'path': '/'
expires: 3650,
path: '/'
});
this.trigger(['videoPlayer', 'onVolumeChange'], ui.value);
......
......@@ -14,11 +14,16 @@ import fs.osfs
import numpy
import json
from lxml import etree
import calc
import xmodule
from xmodule.x_module import ModuleSystem
from mock import Mock
open_ended_grading_interface = {
'url': 'blah/',
'username': 'incorrect_user',
......@@ -111,3 +116,33 @@ class ModelsTest(unittest.TestCase):
except:
exception_happened = True
self.assertTrue(exception_happened)
class PostData(object):
"""Class which emulate postdata."""
def __init__(self, dict_data):
self.dict_data = dict_data
def getlist(self, key):
return self.dict_data.get(key)
class LogicTest(unittest.TestCase):
"""Base class for testing xmodule logic."""
descriptor_class = None
raw_model_data = {}
def setUp(self):
class EmptyClass:
pass
self.system = None
self.location = None
self.descriptor = EmptyClass()
self.xmodule_class = self.descriptor_class.module_class
self.xmodule = self.xmodule_class(
self.system, self.descriptor, self.raw_model_data)
def ajax_request(self, dispatch, get):
return json.loads(self.xmodule.handle_ajax(dispatch, get))
# -*- coding: utf-8 -*-
"""Test for Conditional Xmodule functional logic."""
from xmodule.conditional_module import ConditionalDescriptor
from . import PostData, LogicTest
class ConditionalModuleTest(LogicTest):
descriptor_class = ConditionalDescriptor
def test_ajax_request(self):
"Make shure that ajax request works correctly"
# Mock is_condition_satisfied
self.xmodule.is_condition_satisfied = lambda: True
setattr(self.xmodule.descriptor, 'get_children', lambda: [])
response = self.ajax_request('No', {})
html = response['html']
self.assertEqual(html, [])
# -*- coding: utf-8 -*-
# pylint: disable=W0232
"""Test for Xmodule functional logic."""
import json
import unittest
from xmodule.poll_module import PollDescriptor
from xmodule.conditional_module import ConditionalDescriptor
from xmodule.word_cloud_module import WordCloudDescriptor
from xmodule.tests import get_test_system
class PostData:
"""Class which emulate postdata."""
def __init__(self, dict_data):
self.dict_data = dict_data
def getlist(self, key):
"""Get data by key from `self.dict_data`."""
return self.dict_data.get(key)
class LogicTest(unittest.TestCase):
"""Base class for testing xmodule logic."""
descriptor_class = None
raw_model_data = {}
def setUp(self):
class EmptyClass:
"""Empty object."""
url_name = ''
category = 'test'
self.system = get_test_system()
self.descriptor = EmptyClass()
self.xmodule_class = self.descriptor_class.module_class
self.xmodule = self.xmodule_class(
self.system,
self.descriptor,
self.raw_model_data
)
def ajax_request(self, dispatch, data):
"""Call Xmodule.handle_ajax."""
return json.loads(self.xmodule.handle_ajax(dispatch, data))
class PollModuleTest(LogicTest):
"""Logic tests for Poll Xmodule."""
descriptor_class = PollDescriptor
raw_model_data = {
'poll_answers': {'Yes': 1, 'Dont_know': 0, 'No': 0},
'voted': False,
'poll_answer': ''
}
def test_bad_ajax_request(self):
response = self.ajax_request('bad_answer', {})
self.assertDictEqual(response, {'error': 'Unknown Command!'})
def test_good_ajax_request(self):
response = self.ajax_request('No', {})
poll_answers = response['poll_answers']
total = response['total']
callback = response['callback']
self.assertDictEqual(poll_answers, {'Yes': 1, 'Dont_know': 0, 'No': 1})
self.assertEqual(total, 2)
self.assertDictEqual(callback, {'objectName': 'Conditional'})
self.assertEqual(self.xmodule.poll_answer, 'No')
class ConditionalModuleTest(LogicTest):
"""Logic tests for Conditional Xmodule."""
descriptor_class = ConditionalDescriptor
def test_ajax_request(self):
# Mock is_condition_satisfied
self.xmodule.is_condition_satisfied = lambda: True
setattr(self.xmodule.descriptor, 'get_children', lambda: [])
response = self.ajax_request('No', {})
html = response['html']
self.assertEqual(html, [])
class WordCloudModuleTest(LogicTest):
"""Logic tests for Word Cloud Xmodule."""
descriptor_class = WordCloudDescriptor
raw_model_data = {
'all_words': {'cat': 10, 'dog': 5, 'mom': 1, 'dad': 2},
'top_words': {'cat': 10, 'dog': 5, 'dad': 2},
'submitted': False
}
def test_bad_ajax_request(self):
response = self.ajax_request('bad_dispatch', {})
self.assertDictEqual(response, {
'status': 'fail',
'error': 'Unknown Command!'
})
def test_good_ajax_request(self):
post_data = PostData({'student_words[]': ['cat', 'cat', 'dog', 'sun']})
response = self.ajax_request('submit', post_data)
self.assertEqual(response['status'], 'success')
self.assertEqual(response['submitted'], True)
self.assertEqual(response['total_count'], 22)
self.assertDictEqual(
response['student_words'],
{'sun': 1, 'dog': 6, 'cat': 12}
)
self.assertListEqual(
response['top_words'],
[{'text': 'dad', 'size': 2, 'percent': 9.0},
{'text': 'sun', 'size': 1, 'percent': 5.0},
{'text': 'dog', 'size': 6, 'percent': 27.0},
{'text': 'mom', 'size': 1, 'percent': 5.0},
{'text': 'cat', 'size': 12, 'percent': 54.0}]
)
self.assertEqual(
100.0,
sum(i['percent'] for i in response['top_words']))
# -*- coding: utf-8 -*-
"""Test for Poll Xmodule functional logic."""
from xmodule.poll_module import PollDescriptor
from . import PostData, LogicTest
class PollModuleTest(LogicTest):
descriptor_class = PollDescriptor
raw_model_data = {
'poll_answers': {'Yes': 1, 'Dont_know': 0, 'No': 0},
'voted': False,
'poll_answer': ''
}
def test_bad_ajax_request(self):
"Make sure that answer for incorrect request is error json"
response = self.ajax_request('bad_answer', {})
self.assertDictEqual(response, {'error': 'Unknown Command!'})
def test_good_ajax_request(self):
"Make shure that ajax request works correctly"
response = self.ajax_request('No', {})
poll_answers = response['poll_answers']
total = response['total']
callback = response['callback']
self.assertDictEqual(poll_answers, {'Yes': 1, 'Dont_know': 0, 'No': 1})
self.assertEqual(total, 2)
self.assertDictEqual(callback, {'objectName': 'Conditional'})
self.assertEqual(self.xmodule.poll_answer, 'No')
......@@ -13,15 +13,12 @@ common/lib/xmodule/xmodule/modulestore/tests/factories.py to create the
course, section, subsection, unit, etc.
"""
import json
import unittest
from mock import Mock
from lxml import etree
from xmodule.video_module import VideoDescriptor, VideoModule, _parse_time, _parse_youtube
from xmodule.modulestore import Location
from xmodule.tests import get_test_system
from xmodule.tests.test_logic import LogicTest
from xmodule.tests import LogicTest
class VideoFactory(object):
......
# -*- coding: utf-8 -*-
"""Test for Video Alpha Xmodule functional logic.
These tests data readed from xml, not from mongo.
we have a ModuleStoreTestCase class defined in
common/lib/xmodule/xmodule/modulestore/tests/django_utils.py. You can
search for usages of this in the cms and lms tests for examples. You use
this so that it will do things like point the modulestore setting to mongo,
flush the contentstore before and after, load the templates, etc.
You can then use the CourseFactory and XModuleItemFactory as defined
in common/lib/xmodule/xmodule/modulestore/tests/factories.py to create
the course, section, subsection, unit, etc.
"""
from xmodule.videoalpha_module import VideoAlphaDescriptor
from . import LogicTest, etree
class VideoAlphaModuleTest(LogicTest):
"""Logic tests for VideoAlpha Xmodule."""
descriptor_class = VideoAlphaDescriptor
raw_model_data = {
'data': '<videoalpha />'
}
def test_get_timeframe_no_parameters(self):
"Make sure that timeframe() works correctly w/o parameters"
xmltree = etree.fromstring('<videoalpha>test</videoalpha>')
output = self.xmodule.get_timeframe(xmltree)
self.assertEqual(output, ('', ''))
def test_get_timeframe_with_one_parameter(self):
"Make sure that timeframe() works correctly with one parameter"
xmltree = etree.fromstring(
'<videoalpha start_time="00:04:07">test</videoalpha>'
)
output = self.xmodule.get_timeframe(xmltree)
self.assertEqual(output, (247, ''))
def test_get_timeframe_with_two_parameters(self):
"Make sure that timeframe() works correctly with two parameters"
xmltree = etree.fromstring(
'''<videoalpha
start_time="00:04:07"
end_time="13:04:39"
>test</videoalpha>'''
)
output = self.xmodule.get_timeframe(xmltree)
self.assertEqual(output, (247, 47079))
# -*- coding: utf-8 -*-
"""Test for Word cloud Xmodule functional logic."""
from xmodule.word_cloud_module import WordCloudDescriptor
from . import PostData, LogicTest
class WordCloudModuleTest(LogicTest):
descriptor_class = WordCloudDescriptor
raw_model_data = {
'all_words': {'cat': 10, 'dog': 5, 'mom': 1, 'dad': 2},
'top_words': {'cat': 10, 'dog': 5, 'dad': 2},
'submitted': False
}
def test_bad_ajax_request(self):
"Make sure that answer for incorrect request is error json"
# TODO: move top global test. Formalize all our Xmodule errors.
response = self.ajax_request('bad_dispatch', {})
self.assertDictEqual(response, {
'status': 'fail',
'error': 'Unknown Command!'
})
def test_good_ajax_request(self):
"Make shure that ajax request works correctly"
post_data = PostData({'student_words[]': ['cat', 'cat', 'dog', 'sun']})
response = self.ajax_request('submit', post_data)
self.assertEqual(response['status'], 'success')
self.assertEqual(response['submitted'], True)
self.assertEqual(response['total_count'], 22)
self.assertDictEqual(
response['student_words'],
{'sun': 1, 'dog': 6, 'cat': 12}
)
self.assertListEqual(
response['top_words'],
[{'text': 'dad', 'size': 2, 'percent': 9.0},
{'text': 'sun', 'size': 1, 'percent': 5.0},
{'text': 'dog', 'size': 6, 'percent': 27.0},
{'text': 'mom', 'size': 1, 'percent': 5.0},
{'text': 'cat', 'size': 12, 'percent': 54.0}]
)
self.assertEqual(100.0, sum(i['percent'] for i in response['top_words']) )
......@@ -69,15 +69,16 @@ class VideoAlphaModule(VideoAlphaFields, XModule):
js = {
'js': [
resource_string(__name__, 'js/src/videoalpha/display/initialize.js'),
resource_string(__name__, 'js/src/videoalpha/display/html5_video.js'),
resource_string(__name__, 'js/src/videoalpha/display/video_player.js'),
resource_string(__name__, 'js/src/videoalpha/display/video_control.js'),
resource_string(__name__, 'js/src/videoalpha/display/video_quality_control.js'),
resource_string(__name__, 'js/src/videoalpha/display/video_progress_slider.js'),
resource_string(__name__, 'js/src/videoalpha/display/video_volume_control.js'),
resource_string(__name__, 'js/src/videoalpha/display/video_speed_control.js'),
resource_string(__name__, 'js/src/videoalpha/display/video_caption.js'),
resource_string(__name__, 'js/src/videoalpha/helper_utils.js'),
resource_string(__name__, 'js/src/videoalpha/initialize.js'),
resource_string(__name__, 'js/src/videoalpha/html5_video.js'),
resource_string(__name__, 'js/src/videoalpha/video_player.js'),
resource_string(__name__, 'js/src/videoalpha/video_control.js'),
resource_string(__name__, 'js/src/videoalpha/video_quality_control.js'),
resource_string(__name__, 'js/src/videoalpha/video_progress_slider.js'),
resource_string(__name__, 'js/src/videoalpha/video_volume_control.js'),
resource_string(__name__, 'js/src/videoalpha/video_speed_control.js'),
resource_string(__name__, 'js/src/videoalpha/video_caption.js'),
resource_string(__name__, 'js/src/videoalpha/main.js')
]
}
......@@ -141,11 +142,11 @@ class VideoAlphaModule(VideoAlphaFields, XModule):
if str_time is None:
return ''
else:
obj_time = time.strptime(str_time, '%H:%M:%S')
x = time.strptime(str_time, '%H:%M:%S')
return datetime.timedelta(
hours=obj_time.tm_hour,
minutes=obj_time.tm_min,
seconds=obj_time.tm_sec
hours=x.tm_hour,
minutes=x.tm_min,
seconds=x.tm_sec
).total_seconds()
return parse_time(xmltree.get('start_time')), parse_time(xmltree.get('end_time'))
......
......@@ -7,4 +7,4 @@ Feature: Video component
Scenario: Autoplay is enabled in the LMS for a VideoAlpha component
Given the course has a VideoAlpha component
Then when I view the video it has autoplay enabled
Then when I view the videoalpha it has autoplay enabled
......@@ -8,10 +8,15 @@ from common import i_am_registered_for_the_course, section_location
@step('when I view the video it has autoplay enabled')
def does_autoplay(_step):
def does_autoplay_video(_step):
assert(world.css_find('.video')[0]['data-autoplay'] == 'True')
@step('when I view the videoalpha it has autoplay enabled')
def does_autoplay_videoalpha(_step):
assert(world.css_find('.videoalpha')[0]['data-autoplay'] == 'True')
@step('the course has a Video component')
def view_video(_step):
coursenum = 'test_course'
......
......@@ -23,7 +23,7 @@ from django.conf import settings
from xmodule.videoalpha_module import VideoAlphaDescriptor, VideoAlphaModule
from xmodule.modulestore import Location
from xmodule.tests import get_test_system
from xmodule.tests.test_logic import LogicTest
from xmodule.tests import LogicTest
SOURCE_XML = """
......
......@@ -35,7 +35,7 @@ from .discussionsettings import *
PLATFORM_NAME = "edX"
COURSEWARE_ENABLED = True
ENABLE_JASMINE = True
ENABLE_JASMINE = False
PERFSTATS = False
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment