Merge branch 'master' into jonahstanley/add-courseteam-tests

This commit is contained in:
JonahStanley
2013-06-18 13:20:13 -04:00
37 changed files with 2636 additions and 140 deletions

View File

@@ -10,7 +10,6 @@ import dateutil.parser
from xmodule.modulestore import Location
from xmodule.seq_module import SequenceDescriptor, SequenceModule
from xmodule.timeparse import parse_time
from xmodule.util.decorators import lazyproperty
from xmodule.graders import grader_from_conf
import json
@@ -645,8 +644,11 @@ class CourseDescriptor(CourseFields, SequenceDescriptor):
def start_date_text(self):
def try_parse_iso_8601(text):
try:
result = datetime.strptime(text, "%Y-%m-%dT%H:%M")
result = result.strftime("%b %d, %Y")
result = Date().from_json(text)
if result is None:
result = text.title()
else:
result = result.strftime("%b %d, %Y")
except ValueError:
result = text.title()
@@ -670,8 +672,10 @@ class CourseDescriptor(CourseFields, SequenceDescriptor):
@property
def forum_posts_allowed(self):
date_proxy = Date()
try:
blackout_periods = [(parse_time(start), parse_time(end))
blackout_periods = [(date_proxy.from_json(start),
date_proxy.from_json(end))
for start, end
in self.discussion_blackouts]
now = datetime.now(UTC())
@@ -701,7 +705,7 @@ class CourseDescriptor(CourseFields, SequenceDescriptor):
if self.last_eligible_appointment_date is None:
raise ValueError("Last appointment date must be specified")
self.registration_start_date = (self._try_parse_time('Registration_Start_Date') or
datetime.utcfromtimestamp(0))
datetime.fromtimestamp(0, UTC()))
self.registration_end_date = self._try_parse_time('Registration_End_Date') or self.last_eligible_appointment_date
# do validation within the exam info:
if self.registration_start_date > self.registration_end_date:
@@ -720,7 +724,7 @@ class CourseDescriptor(CourseFields, SequenceDescriptor):
"""
if key in self.exam_info:
try:
return parse_time(self.exam_info[key])
return Date().from_json(self.exam_info[key])
except ValueError as e:
msg = "Exam {0} in course {1} loaded with a bad exam_info key '{2}': '{3}'".format(self.exam_name, self.course_id, self.exam_info[key], e)
log.warning(msg)

View File

@@ -6,7 +6,7 @@ from xblock.core import ModelType
import datetime
import dateutil.parser
from django.utils.timezone import UTC
from pytz import UTC
log = logging.getLogger(__name__)
@@ -15,6 +15,28 @@ class Date(ModelType):
'''
Date fields know how to parse and produce json (iso) compatible formats. Converts to tz aware datetimes.
'''
# See note below about not defaulting these
CURRENT_YEAR = datetime.datetime.now(UTC).year
PREVENT_DEFAULT_DAY_MON_SEED1 = datetime.datetime(CURRENT_YEAR, 1, 1, tzinfo=UTC)
PREVENT_DEFAULT_DAY_MON_SEED2 = datetime.datetime(CURRENT_YEAR, 2, 2, tzinfo=UTC)
def _parse_date_wo_default_month_day(self, field):
"""
Parse the field as an iso string but prevent dateutils from defaulting the day or month while
allowing it to default the other fields.
"""
# It's not trivial to replace dateutil b/c parsing timezones as Z, +03:30, -400 is hard in python
# however, we don't want dateutil to default the month or day (but some tests at least expect
# us to default year); so, we'll see if dateutil uses the defaults for these the hard way
result = dateutil.parser.parse(field, default=self.PREVENT_DEFAULT_DAY_MON_SEED1)
result_other = dateutil.parser.parse(field, default=self.PREVENT_DEFAULT_DAY_MON_SEED2)
if result != result_other:
log.warning("Field {0} is missing month or day".format(self._name, field))
return None
if result.tzinfo is None:
result = result.replace(tzinfo=UTC)
return result
def from_json(self, field):
"""
Parse an optional metadata key containing a time: if present, complain
@@ -26,14 +48,11 @@ class Date(ModelType):
elif field is "":
return None
elif isinstance(field, basestring):
result = dateutil.parser.parse(field)
if result.tzinfo is None:
result = result.replace(tzinfo=UTC())
return result
return self._parse_date_wo_default_month_day(field)
elif isinstance(field, (int, long, float)):
return datetime.datetime.fromtimestamp(field / 1000, UTC())
return datetime.datetime.fromtimestamp(field / 1000, UTC)
elif isinstance(field, time.struct_time):
return datetime.datetime.fromtimestamp(time.mktime(field), UTC())
return datetime.datetime.fromtimestamp(time.mktime(field), UTC)
elif isinstance(field, datetime.datetime):
return field
else:

View File

@@ -0,0 +1,23 @@
<div class="course-content">
<div id="video_example">
<div id="example">
<div
id="video_id"
class="video"
data-streams="0.75:slowerSpeedYoutubeId,1.0:normalSpeedYoutubeId"
data-show-captions="true"
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>

View File

@@ -0,0 +1,27 @@
<div class="course-content">
<div id="video_example">
<div id="example">
<div
id="video_id"
class="video"
data-show-captions="true"
data-start=""
data-end=""
data-caption-asset-path="/static/subs/"
data-sub="test_name_of_the_subtitles"
data-mp4-source="test.mp4"
data-webm-source="test.webm"
data-ogg-source="test.ogv"
>
<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>

View File

@@ -20,10 +20,25 @@ jasmine.stubbedMetadata =
bogus:
duration: 100
jasmine.fireEvent = (el, eventName) ->
if document.createEvent
event = document.createEvent "HTMLEvents"
event.initEvent eventName, true, true
else
event = document.createEventObject()
event.eventType = eventName
event.eventName = eventName
if document.createEvent
el.dispatchEvent(event)
else
el.fireEvent("on" + event.eventType, event)
jasmine.stubbedCaption =
start: [0, 10000, 20000, 30000]
text: ['Caption at 0', 'Caption at 10000', 'Caption at 20000', 'Caption at 30000']
jasmine.stubbedHtml5Speeds = ['0.75', '1.0', '1.25', '1.50']
jasmine.stubRequests = ->
spyOn($, 'ajax').andCallFake (settings) ->
if match = settings.url.match /youtube\.com\/.+\/videos\/(.+)\?v=2&alt=jsonc/
@@ -41,9 +56,12 @@ jasmine.stubRequests = ->
throw "External request attempted for #{settings.url}, which is not defined."
jasmine.stubYoutubePlayer = ->
YT.Player = -> jasmine.createSpyObj 'YT.Player', ['cueVideoById', 'getVideoEmbedCode',
YT.Player = ->
obj = jasmine.createSpyObj 'YT.Player', ['cueVideoById', 'getVideoEmbedCode',
'getCurrentTime', 'getPlayerState', 'getVolume', 'setVolume', 'loadVideoById',
'playVideo', 'pauseVideo', 'seekTo']
'playVideo', 'pauseVideo', 'seekTo', 'getDuration', 'getAvailablePlaybackRates', 'setPlaybackRate']
obj['getAvailablePlaybackRates'] = jasmine.createSpy('getAvailablePlaybackRates').andReturn [0.75, 1.0, 1.25, 1.5]
obj
jasmine.stubVideoPlayer = (context, enableParts, createPlayer=true) ->
enableParts = [enableParts] unless $.isArray(enableParts)
@@ -60,6 +78,21 @@ jasmine.stubVideoPlayer = (context, enableParts, createPlayer=true) ->
if createPlayer
return new VideoPlayer(video: context.video)
jasmine.stubVideoPlayerAlpha = (context, enableParts, createPlayer=true, html5=false) ->
suite = context.suite
currentPartName = suite.description while suite = suite.parentSuite
if html5 == false
loadFixtures 'videoalpha.html'
else
loadFixtures 'videoalpha_html5.html'
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)
# Stub jQuery.cookie
$.cookie = jasmine.createSpy('jQuery.cookie').andReturn '1.0'

View File

@@ -0,0 +1,311 @@
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

View File

@@ -0,0 +1,373 @@
describe 'VideoCaptionAlpha', ->
beforeEach ->
spyOn(VideoCaptionAlpha.prototype, 'fetchCaption').andCallThrough()
spyOn($, 'ajaxWithPrefix').andCallThrough()
window.onTouchBasedDevice = jasmine.createSpy('onTouchBasedDevice').andReturn false
afterEach ->
YT.Player = undefined
$.fn.scrollTo.reset()
$('.subtitles').remove()
describe 'constructor', ->
describe 'always', ->
beforeEach ->
@player = jasmine.stubVideoPlayerAlpha @
@caption = @player.caption
it 'set the youtube id', ->
expect(@caption.youtubeId).toEqual 'normalSpeedYoutubeId'
it 'create the caption element', ->
expect($('.video')).toContain 'ol.subtitles'
it 'add caption control to video player', ->
expect($('.video')).toContain 'a.hide-subtitles'
it 'fetch the caption', ->
expect(@caption.loaded).toBeTruthy()
expect(@caption.fetchCaption).toHaveBeenCalled()
expect($.ajaxWithPrefix).toHaveBeenCalledWith
url: @caption.captionURL()
notifyOnError: false
success: jasmine.any(Function)
it 'bind window resize event', ->
expect($(window)).toHandleWith 'resize', @caption.resize
it 'bind the hide caption button', ->
expect($('.hide-subtitles')).toHandleWith 'click', @caption.toggle
it 'bind the mouse movement', ->
expect($('.subtitles')).toHandleWith 'mouseover', @caption.onMouseEnter
expect($('.subtitles')).toHandleWith 'mouseout', @caption.onMouseLeave
expect($('.subtitles')).toHandleWith 'mousemove', @caption.onMovement
expect($('.subtitles')).toHandleWith 'mousewheel', @caption.onMovement
expect($('.subtitles')).toHandleWith 'DOMMouseScroll', @caption.onMovement
describe 'when on a non touch-based device', ->
beforeEach ->
@player = jasmine.stubVideoPlayerAlpha @
@caption = @player.caption
it 'render the caption', ->
captionsData = jasmine.stubbedCaption
$('.subtitles li[data-index]').each (index, link) =>
expect($(link)).toHaveData 'index', index
expect($(link)).toHaveData 'start', captionsData.start[index]
expect($(link)).toHaveText captionsData.text[index]
it 'add a padding element to caption', ->
expect($('.subtitles li:first')).toBe '.spacing'
expect($('.subtitles li:last')).toBe '.spacing'
it 'bind all the caption link', ->
$('.subtitles li[data-index]').each (index, link) =>
expect($(link)).toHandleWith 'click', @caption.seekPlayer
it 'set rendered to true', ->
expect(@caption.rendered).toBeTruthy()
describe 'when on a touch-based device', ->
beforeEach ->
window.onTouchBasedDevice.andReturn true
@player = jasmine.stubVideoPlayerAlpha @
@caption = @player.caption
it 'show explaination message', ->
expect($('.subtitles li')).toHaveHtml "Caption will be displayed when you start playing the video."
it 'does not set rendered to true', ->
expect(@caption.rendered).toBeFalsy()
describe 'mouse movement', ->
beforeEach ->
@player = jasmine.stubVideoPlayerAlpha @
@caption = @player.caption
window.setTimeout.andReturn(100)
spyOn window, 'clearTimeout'
describe 'when cursor is outside of the caption box', ->
beforeEach ->
$(window).trigger jQuery.Event 'mousemove'
it 'does not set freezing timeout', ->
expect(@caption.frozen).toBeFalsy()
describe 'when cursor is in the caption box', ->
beforeEach ->
$('.subtitles').trigger jQuery.Event 'mouseenter'
it 'set the freezing timeout', ->
expect(@caption.frozen).toEqual 100
describe 'when the cursor is moving', ->
beforeEach ->
$('.subtitles').trigger jQuery.Event 'mousemove'
it 'reset the freezing timeout', ->
expect(window.clearTimeout).toHaveBeenCalledWith 100
describe 'when the mouse is scrolling', ->
beforeEach ->
$('.subtitles').trigger jQuery.Event 'mousewheel'
it 'reset the freezing timeout', ->
expect(window.clearTimeout).toHaveBeenCalledWith 100
describe 'when cursor is moving out of the caption box', ->
beforeEach ->
@caption.frozen = 100
$.fn.scrollTo.reset()
describe 'always', ->
beforeEach ->
$('.subtitles').trigger jQuery.Event 'mouseout'
it 'reset the freezing timeout', ->
expect(window.clearTimeout).toHaveBeenCalledWith 100
it 'unfreeze the caption', ->
expect(@caption.frozen).toBeNull()
describe 'when the player is playing', ->
beforeEach ->
@caption.playing = true
$('.subtitles li[data-index]:first').addClass 'current'
$('.subtitles').trigger jQuery.Event 'mouseout'
it 'scroll the caption', ->
expect($.fn.scrollTo).toHaveBeenCalled()
describe 'when the player is not playing', ->
beforeEach ->
@caption.playing = false
$('.subtitles').trigger jQuery.Event 'mouseout'
it 'does not scroll the caption', ->
expect($.fn.scrollTo).not.toHaveBeenCalled()
describe 'search', ->
beforeEach ->
@player = jasmine.stubVideoPlayerAlpha @
@caption = @player.caption
it 'return a correct caption index', ->
expect(@caption.search(0)).toEqual 0
expect(@caption.search(9999)).toEqual 0
expect(@caption.search(10000)).toEqual 1
expect(@caption.search(15000)).toEqual 1
expect(@caption.search(30000)).toEqual 3
expect(@caption.search(30001)).toEqual 3
describe 'play', ->
describe 'when the caption was not rendered', ->
beforeEach ->
window.onTouchBasedDevice.andReturn true
@player = jasmine.stubVideoPlayerAlpha @
@caption = @player.caption
@caption.play()
it 'render the caption', ->
captionsData = jasmine.stubbedCaption
$('.subtitles li[data-index]').each (index, link) =>
expect($(link)).toHaveData 'index', index
expect($(link)).toHaveData 'start', captionsData.start[index]
expect($(link)).toHaveText captionsData.text[index]
it 'add a padding element to caption', ->
expect($('.subtitles li:first')).toBe '.spacing'
expect($('.subtitles li:last')).toBe '.spacing'
it 'bind all the caption link', ->
$('.subtitles li[data-index]').each (index, link) =>
expect($(link)).toHandleWith 'click', @caption.seekPlayer
it 'set rendered to true', ->
expect(@caption.rendered).toBeTruthy()
it 'set playing to true', ->
expect(@caption.playing).toBeTruthy()
describe 'pause', ->
beforeEach ->
@player = jasmine.stubVideoPlayerAlpha @
@caption = @player.caption
@caption.playing = true
@caption.pause()
it 'set playing to false', ->
expect(@caption.playing).toBeFalsy()
describe 'updatePlayTime', ->
beforeEach ->
@player = jasmine.stubVideoPlayerAlpha @
@caption = @player.caption
describe 'when the video speed is 1.0x', ->
beforeEach ->
@caption.currentSpeed = '1.0'
@caption.updatePlayTime 25.000
it 'search the caption based on time', ->
expect(@caption.currentIndex).toEqual 2
describe 'when the video speed is not 1.0x', ->
beforeEach ->
@caption.currentSpeed = '0.75'
@caption.updatePlayTime 25.000
it 'search the caption based on 1.0x speed', ->
expect(@caption.currentIndex).toEqual 1
describe 'when the index is not the same', ->
beforeEach ->
@caption.currentIndex = 1
$('.subtitles li[data-index=1]').addClass 'current'
@caption.updatePlayTime 25.000
it 'deactivate the previous caption', ->
expect($('.subtitles li[data-index=1]')).not.toHaveClass 'current'
it 'activate new caption', ->
expect($('.subtitles li[data-index=2]')).toHaveClass 'current'
it 'save new index', ->
expect(@caption.currentIndex).toEqual 2
it 'scroll caption to new position', ->
expect($.fn.scrollTo).toHaveBeenCalled()
describe 'when the index is the same', ->
beforeEach ->
@caption.currentIndex = 1
$('.subtitles li[data-index=1]').addClass 'current'
@caption.updatePlayTime 15.000
it 'does not change current subtitle', ->
expect($('.subtitles li[data-index=1]')).toHaveClass 'current'
describe 'resize', ->
beforeEach ->
@player = jasmine.stubVideoPlayerAlpha @
@caption = @player.caption
$('.subtitles li[data-index=1]').addClass 'current'
@caption.resize()
it 'set the height of caption container', ->
expect(parseInt($('.subtitles').css('maxHeight'))).toBeCloseTo $('.video-wrapper').height(), 2
it 'set the height of caption spacing', ->
firstSpacing = Math.abs(parseInt($('.subtitles .spacing:first').css('height')))
lastSpacing = Math.abs(parseInt($('.subtitles .spacing:last').css('height')))
expect(firstSpacing - @caption.topSpacingHeight()).toBeLessThan 1
expect(lastSpacing - @caption.bottomSpacingHeight()).toBeLessThan 1
it 'scroll caption to new position', ->
expect($.fn.scrollTo).toHaveBeenCalled()
describe 'scrollCaption', ->
beforeEach ->
@player = jasmine.stubVideoPlayerAlpha @
@caption = @player.caption
describe 'when frozen', ->
beforeEach ->
@caption.frozen = true
$('.subtitles li[data-index=1]').addClass 'current'
@caption.scrollCaption()
it 'does not scroll the caption', ->
expect($.fn.scrollTo).not.toHaveBeenCalled()
describe 'when not frozen', ->
beforeEach ->
@caption.frozen = false
describe 'when there is no current caption', ->
beforeEach ->
@caption.scrollCaption()
it 'does not scroll the caption', ->
expect($.fn.scrollTo).not.toHaveBeenCalled()
describe 'when there is a current caption', ->
beforeEach ->
$('.subtitles li[data-index=1]').addClass 'current'
@caption.scrollCaption()
it 'scroll to current caption', ->
offset = -0.5 * ($('.video-wrapper').height() - $('.subtitles .current:first').height())
expect($.fn.scrollTo).toHaveBeenCalledWith $('.subtitles .current:first', @caption.el),
offset: offset
describe 'seekPlayer', ->
beforeEach ->
@player = jasmine.stubVideoPlayerAlpha @
@caption = @player.caption
$(@caption).bind 'seek', (event, time) => @time = time
describe 'when the video speed is 1.0x', ->
beforeEach ->
@caption.currentSpeed = '1.0'
$('.subtitles li[data-start="30000"]').trigger('click')
it 'trigger seek event with the correct time', ->
expect(@player.currentTime).toEqual 30.000
describe 'when the video speed is not 1.0x', ->
beforeEach ->
@caption.currentSpeed = '0.75'
$('.subtitles li[data-start="30000"]').trigger('click')
it 'trigger seek event with the correct time', ->
expect(@player.currentTime).toEqual 40.000
describe 'toggle', ->
beforeEach ->
@player = jasmine.stubVideoPlayerAlpha @
spyOn @video, 'log'
@caption = @player.caption
$('.subtitles li[data-index=1]').addClass 'current'
describe 'when the caption is visible', ->
beforeEach ->
@caption.el.removeClass 'closed'
@caption.toggle jQuery.Event('click')
it 'log the hide_transcript event', ->
expect(@video.log).toHaveBeenCalledWith 'hide_transcript',
currentTime: @player.currentTime
it 'hide the caption', ->
expect(@caption.el).toHaveClass 'closed'
describe 'when the caption is hidden', ->
beforeEach ->
@caption.el.addClass 'closed'
@caption.toggle jQuery.Event('click')
it 'log the show_transcript event', ->
expect(@video.log).toHaveBeenCalledWith 'show_transcript',
currentTime: @player.currentTime
it 'show the caption', ->
expect(@caption.el).not.toHaveClass 'closed'
it 'scroll the caption', ->
expect($.fn.scrollTo).toHaveBeenCalled()

View File

@@ -0,0 +1,103 @@
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

View File

@@ -0,0 +1,561 @@
describe 'VideoPlayerAlpha', ->
playerVars =
controls: 0
wmode: 'transparent'
rel: 0
showinfo: 0
enablejsapi: 1
modestbranding: 1
html5: 1
beforeEach ->
window.onTouchBasedDevice = jasmine.createSpy('onTouchBasedDevice').andReturn false
# It tries to call methods of VideoProgressSlider on Spy
for part in ['VideoCaptionAlpha', 'VideoSpeedControlAlpha', 'VideoVolumeControlAlpha', 'VideoProgressSliderAlpha', 'VideoControlAlpha']
spyOn(window[part].prototype, 'initialize').andCallThrough()
afterEach ->
YT.Player = undefined
describe 'constructor', ->
beforeEach ->
$.fn.qtip.andCallFake ->
$(this).data('qtip', true)
describe 'always', ->
beforeEach ->
jasmine.stubVideoPlayerAlpha @, [], false
$('.video').append $('<div class="add-fullscreen" /><div class="hide-subtitles" />')
@player = new VideoPlayerAlpha video: @video
it 'instanticate current time to zero', ->
expect(@player.currentTime).toEqual 0
it 'set the element', ->
expect(@player.el).toHaveId 'video_id'
it 'create video control', ->
expect(window.VideoControlAlpha.prototype.initialize).toHaveBeenCalled()
expect(@player.control).toBeDefined()
expect(@player.control.el).toBe $('.video-controls', @player.el)
it 'create video caption', ->
expect(window.VideoCaptionAlpha.prototype.initialize).toHaveBeenCalled()
expect(@player.caption).toBeDefined()
expect(@player.caption.el).toBe @player.el
expect(@player.caption.youtubeId).toEqual 'normalSpeedYoutubeId'
expect(@player.caption.currentSpeed).toEqual '1.0'
expect(@player.caption.captionAssetPath).toEqual '/static/subs/'
it 'create video speed control', ->
expect(window.VideoSpeedControlAlpha.prototype.initialize).toHaveBeenCalled()
expect(@player.speedControl).toBeDefined()
expect(@player.speedControl.el).toBe $('.secondary-controls', @player.el)
expect(@player.speedControl.speeds).toEqual ['0.75', '1.0']
expect(@player.speedControl.currentSpeed).toEqual '1.0'
it 'create video progress slider', ->
expect(window.VideoSpeedControlAlpha.prototype.initialize).toHaveBeenCalled()
expect(@player.progressSlider).toBeDefined()
expect(@player.progressSlider.el).toBe $('.slider', @player.el)
it 'bind to video control play event', ->
expect($(@player.control)).toHandleWith 'play', @player.play
it 'bind to video control pause event', ->
expect($(@player.control)).toHandleWith 'pause', @player.pause
it 'bind to video caption seek event', ->
expect($(@player.caption)).toHandleWith 'caption_seek', @player.onSeek
it 'bind to video speed control speedChange event', ->
expect($(@player.speedControl)).toHandleWith 'speedChange', @player.onSpeedChange
it 'bind to video progress slider seek event', ->
expect($(@player.progressSlider)).toHandleWith 'slide_seek', @player.onSeek
it 'bind to video volume control volumeChange event', ->
expect($(@player.volumeControl)).toHandleWith 'volumeChange', @player.onVolumeChange
it 'bind to key press', ->
expect($(document.documentElement)).toHandleWith 'keyup', @player.bindExitFullScreen
it 'bind to fullscreen switching button', ->
expect($('.add-fullscreen')).toHandleWith 'click', @player.toggleFullScreen
it 'create Youtube player', ->
jasmine.stubVideoPlayerAlpha @, [], false
$('.video').append $('<div class="add-fullscreen" /><div class="hide-subtitles" />')
spyOn YT, 'Player'
@player = new VideoPlayerAlpha video: @video
expect(YT.Player).toHaveBeenCalledWith('id', {
playerVars: playerVars
videoId: 'normalSpeedYoutubeId'
events:
onReady: @player.onReady
onStateChange: @player.onStateChange
onPlaybackQualityChange: @player.onPlaybackQualityChange
})
it 'create HTML5 player', ->
jasmine.stubVideoPlayerAlpha @, [], false, true
spyOn HTML5Video, 'Player'
$('.video').append $('<div class="add-fullscreen" /><div class="hide-subtitles" />')
@player = new VideoPlayerAlpha video: @video
expect(HTML5Video.Player).toHaveBeenCalledWith @video.el,
playerVars: playerVars
videoSources: @video.html5Sources
events:
onReady: @player.onReady
onStateChange: @player.onStateChange
describe 'when not on a touch based device', ->
beforeEach ->
jasmine.stubVideoPlayerAlpha @, [], false
$('.video').append $('<div class="add-fullscreen" /><div class="hide-subtitles" />')
$('.add-fullscreen, .hide-subtitles').removeData 'qtip'
@player = new VideoPlayerAlpha video: @video
it 'add the tooltip to fullscreen and subtitle button', ->
expect($('.add-fullscreen')).toHaveData 'qtip'
expect($('.hide-subtitles')).toHaveData 'qtip'
it 'create video volume control', ->
expect(window.VideoVolumeControlAlpha.prototype.initialize).toHaveBeenCalled()
expect(@player.volumeControl).toBeDefined()
expect(@player.volumeControl.el).toBe $('.secondary-controls', @player.el)
describe 'when on a touch based device', ->
beforeEach ->
jasmine.stubVideoPlayerAlpha @, [], false
$('.video').append $('<div class="add-fullscreen" /><div class="hide-subtitles" />')
window.onTouchBasedDevice.andReturn true
$('.add-fullscreen, .hide-subtitles').removeData 'qtip'
@player = new VideoPlayerAlpha video: @video
it 'does not add the tooltip to fullscreen and subtitle button', ->
expect($('.add-fullscreen')).not.toHaveData 'qtip'
expect($('.hide-subtitles')).not.toHaveData 'qtip'
it 'does not create video volume control', ->
expect(window.VideoVolumeControlAlpha.prototype.initialize).not.toHaveBeenCalled()
expect(@player.volumeControl).not.toBeDefined()
describe 'onReady', ->
beforeEach ->
jasmine.stubVideoPlayerAlpha @, [], false
spyOn @video, 'log'
$('.video').append $('<div class="add-fullscreen" /><div class="hide-subtitles" />')
@video.embed()
@player = @video.player
spyOnEvent @player, 'ready'
spyOnEvent @player, 'updatePlayTime'
@player.onReady()
it 'log the load_video event', ->
expect(@video.log).toHaveBeenCalledWith 'load_video'
describe 'when not on a touch based device', ->
beforeEach ->
spyOn @player, 'play'
@player.onReady()
it 'autoplay the first video', ->
expect(@player.play).toHaveBeenCalled()
describe 'when on a touch based device', ->
beforeEach ->
window.onTouchBasedDevice.andReturn true
spyOn @player, 'play'
@player.onReady()
it 'does not autoplay the first video', ->
expect(@player.play).not.toHaveBeenCalled()
describe 'onStateChange', ->
beforeEach ->
jasmine.stubVideoPlayerAlpha @, [], false
$('.video').append $('<div class="add-fullscreen" /><div class="hide-subtitles" />')
describe 'when the video is unstarted', ->
beforeEach ->
@player = new VideoPlayerAlpha video: @video
spyOn @player.control, 'pause'
@player.caption.pause = jasmine.createSpy('VideoCaptionAlpha.pause')
@player.onStateChange data: YT.PlayerState.UNSTARTED
it 'pause the video control', ->
expect(@player.control.pause).toHaveBeenCalled()
it 'pause the video caption', ->
expect(@player.caption.pause).toHaveBeenCalled()
describe 'when the video is playing', ->
beforeEach ->
@anotherPlayer = jasmine.createSpyObj 'AnotherPlayer', ['onPause']
window.OldVideoPlayerAlpha = @anotherPlayer
@player = new VideoPlayerAlpha video: @video
spyOn @video, 'log'
spyOn(window, 'setInterval').andReturn 100
spyOn @player.control, 'play'
@player.caption.play = jasmine.createSpy('VideoCaptionAlpha.play')
@player.progressSlider.play = jasmine.createSpy('VideoProgressSliderAlpha.play')
@player.player.getVideoEmbedCode.andReturn 'embedCode'
@player.onStateChange data: YT.PlayerState.PLAYING
it 'log the play_video event', ->
expect(@video.log).toHaveBeenCalledWith 'play_video', {currentTime: 0}
it 'pause other video player', ->
expect(@anotherPlayer.onPause).toHaveBeenCalled()
it 'set current video player as active player', ->
expect(window.OldVideoPlayerAlpha).toEqual @player
it 'set update interval', ->
expect(window.setInterval).toHaveBeenCalledWith @player.update, 200
expect(@player.player.interval).toEqual 100
it 'play the video control', ->
expect(@player.control.play).toHaveBeenCalled()
it 'play the video caption', ->
expect(@player.caption.play).toHaveBeenCalled()
it 'play the video progress slider', ->
expect(@player.progressSlider.play).toHaveBeenCalled()
describe 'when the video is paused', ->
beforeEach ->
@player = new VideoPlayerAlpha video: @video
spyOn @video, 'log'
spyOn window, 'clearInterval'
spyOn @player.control, 'pause'
@player.caption.pause = jasmine.createSpy('VideoCaptionAlpha.pause')
@player.player.interval = 100
@player.player.getVideoEmbedCode.andReturn 'embedCode'
@player.onStateChange data: YT.PlayerState.PAUSED
it 'log the pause_video event', ->
expect(@video.log).toHaveBeenCalledWith 'pause_video', {currentTime: 0}
it 'clear update interval', ->
expect(window.clearInterval).toHaveBeenCalledWith 100
expect(@player.player.interval).toBeNull()
it 'pause the video control', ->
expect(@player.control.pause).toHaveBeenCalled()
it 'pause the video caption', ->
expect(@player.caption.pause).toHaveBeenCalled()
describe 'when the video is ended', ->
beforeEach ->
@player = new VideoPlayerAlpha video: @video
spyOn @player.control, 'pause'
@player.caption.pause = jasmine.createSpy('VideoCaptionAlpha.pause')
@player.onStateChange data: YT.PlayerState.ENDED
it 'pause the video control', ->
expect(@player.control.pause).toHaveBeenCalled()
it 'pause the video caption', ->
expect(@player.caption.pause).toHaveBeenCalled()
describe 'onSeek', ->
conf = [{
desc : 'check if seek_video is logged with slide_seek type',
type: 'slide_seek',
obj: 'progressSlider'
},{
desc : 'check if seek_video is logged with caption_seek type',
type: 'caption_seek',
obj: 'caption'
}]
beforeEach ->
jasmine.stubVideoPlayerAlpha @, [], false
$('.video').append $('<div class="add-fullscreen" /><div class="hide-subtitles" />')
@player = new VideoPlayerAlpha video: @video
spyOn window, 'clearInterval'
@player.player.interval = 100
spyOn @player, 'updatePlayTime'
spyOn @video, 'log'
$.each conf, (key, value) ->
it value.desc, ->
type = value.type
old_time = 0
new_time = 60
$(@player[value.obj]).trigger value.type, new_time
expect(@video.log).toHaveBeenCalledWith 'seek_video',
old_time: old_time
new_time: new_time
type: value.type
it 'seek the player', ->
$(@player.progressSlider).trigger 'slide_seek', 60
expect(@player.player.seekTo).toHaveBeenCalledWith 60, true
it 'call updatePlayTime on player', ->
$(@player.progressSlider).trigger 'slide_seek', 60
expect(@player.updatePlayTime).toHaveBeenCalledWith 60
describe 'when the player is playing', ->
beforeEach ->
$(@player.progressSlider).trigger 'slide_seek', 60
@player.player.getPlayerState.andReturn YT.PlayerState.PLAYING
@player.onSeek {}, 60
it 'reset the update interval', ->
expect(window.clearInterval).toHaveBeenCalledWith 100
describe 'when the player is not playing', ->
beforeEach ->
$(@player.progressSlider).trigger 'slide_seek', 60
@player.player.getPlayerState.andReturn YT.PlayerState.PAUSED
@player.onSeek {}, 60
it 'set the current time', ->
expect(@player.currentTime).toEqual 60
describe 'onSpeedChange', ->
beforeEach ->
jasmine.stubVideoPlayerAlpha @, [], false
$('.video').append $('<div class="add-fullscreen" /><div class="hide-subtitles" />')
@player = new VideoPlayerAlpha video: @video
@player.currentTime = 60
spyOn @player, 'updatePlayTime'
spyOn(@video, 'setSpeed').andCallThrough()
spyOn(@video, 'log')
describe 'always', ->
beforeEach ->
@player.onSpeedChange {}, '0.75', false
it 'check if speed_change_video is logged', ->
expect(@video.log).toHaveBeenCalledWith 'speed_change_video',
currentTime: @player.currentTime
old_speed: '1.0'
new_speed: '0.75'
it 'convert the current time to the new speed', ->
expect(@player.currentTime).toEqual '80.000'
it 'set video speed to the new speed', ->
expect(@video.setSpeed).toHaveBeenCalledWith '0.75', false
it 'tell video caption that the speed has changed', ->
expect(@player.caption.currentSpeed).toEqual '0.75'
describe 'when the video is playing', ->
beforeEach ->
@player.player.getPlayerState.andReturn YT.PlayerState.PLAYING
@player.onSpeedChange {}, '0.75'
it 'load the video', ->
expect(@player.player.loadVideoById).toHaveBeenCalledWith 'slowerSpeedYoutubeId', '80.000'
it 'trigger updatePlayTime event', ->
expect(@player.updatePlayTime).toHaveBeenCalledWith '80.000'
describe 'when the video is not playing', ->
beforeEach ->
@player.player.getPlayerState.andReturn YT.PlayerState.PAUSED
@player.onSpeedChange {}, '0.75'
it 'cue the video', ->
expect(@player.player.cueVideoById).toHaveBeenCalledWith 'slowerSpeedYoutubeId', '80.000'
it 'trigger updatePlayTime event', ->
expect(@player.updatePlayTime).toHaveBeenCalledWith '80.000'
describe 'onVolumeChange', ->
beforeEach ->
jasmine.stubVideoPlayerAlpha @, [], false
$('.video').append $('<div class="add-fullscreen" /><div class="hide-subtitles" />')
@player = new VideoPlayerAlpha video: @video
@player.onVolumeChange undefined, 60
it 'set the volume on player', ->
expect(@player.player.setVolume).toHaveBeenCalledWith 60
describe 'update', ->
beforeEach ->
jasmine.stubVideoPlayerAlpha @, [], false
$('.video').append $('<div class="add-fullscreen" /><div class="hide-subtitles" />')
@player = new VideoPlayerAlpha video: @video
spyOn @player, 'updatePlayTime'
describe 'when the current time is unavailable from the player', ->
beforeEach ->
@player.player.getCurrentTime.andReturn undefined
@player.update()
it 'does not trigger updatePlayTime event', ->
expect(@player.updatePlayTime).not.toHaveBeenCalled()
describe 'when the current time is available from the player', ->
beforeEach ->
@player.player.getCurrentTime.andReturn 60
@player.update()
it 'trigger updatePlayTime event', ->
expect(@player.updatePlayTime).toHaveBeenCalledWith(60)
describe 'updatePlayTime', ->
beforeEach ->
jasmine.stubVideoPlayerAlpha @, [], false
$('.video').append $('<div class="add-fullscreen" /><div class="hide-subtitles" />')
@player = new VideoPlayerAlpha video: @video
spyOn(@video, 'getDuration').andReturn 1800
@player.caption.updatePlayTime = jasmine.createSpy('VideoCaptionAlpha.updatePlayTime')
@player.progressSlider.updatePlayTime = jasmine.createSpy('VideoProgressSliderAlpha.updatePlayTime')
@player.updatePlayTime 60
it 'update the video playback time', ->
expect($('.vidtime')).toHaveHtml '1:00 / 30:00'
it 'update the playback time on caption', ->
expect(@player.caption.updatePlayTime).toHaveBeenCalledWith 60
it 'update the playback time on progress slider', ->
expect(@player.progressSlider.updatePlayTime).toHaveBeenCalledWith 60, 1800
describe 'toggleFullScreen', ->
beforeEach ->
jasmine.stubVideoPlayerAlpha @, [], false
$('.video').append $('<div class="add-fullscreen" /><div class="hide-subtitles" />')
@player = new VideoPlayerAlpha video: @video
@player.caption.resize = jasmine.createSpy('VideoCaptionAlpha.resize')
describe 'when the video player is not full screen', ->
beforeEach ->
spyOn @video, 'log'
@player.el.removeClass 'fullscreen'
@player.toggleFullScreen(jQuery.Event("click"))
it 'log the fullscreen event', ->
expect(@video.log).toHaveBeenCalledWith 'fullscreen',
currentTime: @player.currentTime
it 'replace the full screen button tooltip', ->
expect($('.add-fullscreen')).toHaveAttr 'title', 'Exit fill browser'
it 'add the fullscreen class', ->
expect(@player.el).toHaveClass 'fullscreen'
it 'tell VideoCaption to resize', ->
expect(@player.caption.resize).toHaveBeenCalled()
describe 'when the video player already full screen', ->
beforeEach ->
spyOn @video, 'log'
@player.el.addClass 'fullscreen'
@player.toggleFullScreen(jQuery.Event("click"))
it 'log the not_fullscreen event', ->
expect(@video.log).toHaveBeenCalledWith 'not_fullscreen',
currentTime: @player.currentTime
it 'replace the full screen button tooltip', ->
expect($('.add-fullscreen')).toHaveAttr 'title', 'Fill browser'
it 'remove exit full screen button', ->
expect(@player.el).not.toContain 'a.exit'
it 'remove the fullscreen class', ->
expect(@player.el).not.toHaveClass 'fullscreen'
it 'tell VideoCaption to resize', ->
expect(@player.caption.resize).toHaveBeenCalled()
describe 'play', ->
beforeEach ->
jasmine.stubVideoPlayerAlpha @, [], false
$('.video').append $('<div class="add-fullscreen" /><div class="hide-subtitles" />')
@player = new VideoPlayerAlpha video: @video
describe 'when the player is not ready', ->
beforeEach ->
@player.player.playVideo = undefined
@player.play()
it 'does nothing', ->
expect(@player.player.playVideo).toBeUndefined()
describe 'when the player is ready', ->
beforeEach ->
@player.player.playVideo.andReturn true
@player.play()
it 'delegate to the Youtube player', ->
expect(@player.player.playVideo).toHaveBeenCalled()
describe 'isPlaying', ->
beforeEach ->
jasmine.stubVideoPlayerAlpha @, [], false
$('.video').append $('<div class="add-fullscreen" /><div class="hide-subtitles" />')
@player = new VideoPlayerAlpha video: @video
describe 'when the video is playing', ->
beforeEach ->
@player.player.getPlayerState.andReturn YT.PlayerState.PLAYING
it 'return true', ->
expect(@player.isPlaying()).toBeTruthy()
describe 'when the video is not playing', ->
beforeEach ->
@player.player.getPlayerState.andReturn YT.PlayerState.PAUSED
it 'return false', ->
expect(@player.isPlaying()).toBeFalsy()
describe 'pause', ->
beforeEach ->
jasmine.stubVideoPlayerAlpha @, [], false
$('.video').append $('<div class="add-fullscreen" /><div class="hide-subtitles" />')
@player = new VideoPlayerAlpha video: @video
@player.pause()
it 'delegate to the Youtube player', ->
expect(@player.player.pauseVideo).toHaveBeenCalled()
describe 'duration', ->
beforeEach ->
jasmine.stubVideoPlayerAlpha @, [], false
$('.video').append $('<div class="add-fullscreen" /><div class="hide-subtitles" />')
@player = new VideoPlayerAlpha video: @video
spyOn @video, 'getDuration'
@player.duration()
it 'delegate to the video', ->
expect(@video.getDuration).toHaveBeenCalled()
describe 'currentSpeed', ->
beforeEach ->
jasmine.stubVideoPlayerAlpha @, [], false
$('.video').append $('<div class="add-fullscreen" /><div class="hide-subtitles" />')
@player = new VideoPlayerAlpha video: @video
@video.speed = '3.0'
it 'delegate to the video', ->
expect(@player.currentSpeed()).toEqual '3.0'
describe 'volume', ->
beforeEach ->
jasmine.stubVideoPlayerAlpha @, [], false
$('.video').append $('<div class="add-fullscreen" /><div class="hide-subtitles" />')
@player = new VideoPlayerAlpha video: @video
@player.player.getVolume.andReturn 42
describe 'without value', ->
it 'return current volume', ->
expect(@player.volume()).toEqual 42
describe 'with value', ->
it 'set player volume', ->
@player.volume(60)
expect(@player.player.setVolume).toHaveBeenCalledWith(60)

View File

@@ -0,0 +1,165 @@
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'

View File

@@ -0,0 +1,91 @@
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'

View File

@@ -0,0 +1,94 @@
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

View File

@@ -0,0 +1,286 @@
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'

View File

@@ -37,7 +37,7 @@ class @VideoCaptionAlpha extends SubviewAlpha
@loaded = true
if onTouchBasedDevice()
$('.subtitles li').html "Caption will be displayed when you start playing the video."
$('.subtitles').html "<li>Caption will be displayed when you start playing the video.</li>"
else
@renderCaption()

View File

@@ -6,7 +6,7 @@ class @VideoPlayerAlpha extends SubviewAlpha
# we must pause the player (stop setInterval() method).
if (window.OldVideoPlayerAlpha) and (window.OldVideoPlayerAlpha.onPause)
window.OldVideoPlayerAlpha.onPause()
window.OldVideoPlayerAlpha = this
window.OldVideoPlayerAlpha = @
if @video.videoType is 'youtube'
@PlayerState = YT.PlayerState
@@ -29,7 +29,7 @@ class @VideoPlayerAlpha extends SubviewAlpha
$(@progressSlider).bind('slide_seek', @onSeek)
if @volumeControl
$(@volumeControl).bind('volumeChange', @onVolumeChange)
$(document).keyup @bindExitFullScreen
$(document.documentElement).keyup @bindExitFullScreen
@$('.add-fullscreen').click @toggleFullScreen
@addToolTip() unless onTouchBasedDevice()
@@ -114,7 +114,7 @@ class @VideoPlayerAlpha extends SubviewAlpha
@video.log 'load_video'
if @video.videoType is 'html5'
@player.setPlaybackRate @video.speed
if not onTouchBasedDevice() and $('.video:first').data('autoplay') is 'True'
if not onTouchBasedDevice() and $('.video:first').data('autoplay') isnt 'False'
$('.video-load-complete:first').data('video').player.play()
onStateChange: (event) =>
@@ -308,7 +308,7 @@ class @VideoPlayerAlpha extends SubviewAlpha
@player.pauseVideo() if @player.pauseVideo
duration: ->
duration = @player.getDuration()
duration = @player.getDuration() if @player.getDuration
if isFinite(duration) is false
duration = @video.getDuration()
duration

View File

@@ -12,7 +12,7 @@ class @VideoProgressSliderAlpha extends SubviewAlpha
@buildHandle()
buildHandle: ->
@handle = @$('.slider .ui-slider-handle')
@handle = @$('.ui-slider-handle')
@handle.qtip
content: "#{Time.format(@slider.slider('value'))}"
position:
@@ -43,7 +43,7 @@ class @VideoProgressSliderAlpha extends SubviewAlpha
onStop: (event, ui) =>
@frozen = true
$(@).trigger('seek', ui.value)
$(@).trigger('slide_seek', ui.value)
setTimeout (=> @frozen = false), 200
updateTooltip: (value)->

View File

@@ -1,6 +1,6 @@
---
metadata:
display_name: Video Alpha 1
display_name: Video Alpha
version: 1
data: |
<videoalpha show_captions="true" sub="name_of_file" youtube="0.75:JMD_ifUUfsU,1.0:OEoXaMPEzfM,1.25:AKqURZnYqpk,1.50:DYpADpL7jAY" >

View File

@@ -3,6 +3,8 @@ import datetime
import unittest
from django.utils.timezone import UTC
from xmodule.fields import Date, Timedelta
from xmodule.timeinfo import TimeInfo
import time
class DateTest(unittest.TestCase):
@@ -52,6 +54,18 @@ class DateTest(unittest.TestCase):
self.assertEqual(
datetime.datetime(current.year, 12, 4, 16, 30, tzinfo=UTC()),
DateTest.date.from_json("December 4 16:30"))
self.assertIsNone(DateTest.date.from_json("12 12:00"))
def test_non_std_from_json(self):
"""
Test the non-standard args being passed to from_json
"""
now = datetime.datetime.now(UTC())
delta = now - datetime.datetime.fromtimestamp(0, UTC())
self.assertEqual(DateTest.date.from_json(delta.total_seconds() * 1000),
now)
yesterday = datetime.datetime.now(UTC()) - datetime.timedelta(days=-1)
self.assertEqual(DateTest.date.from_json(yesterday), yesterday)
def test_to_json(self):
'''
@@ -90,3 +104,12 @@ class TimedeltaTest(unittest.TestCase):
'1 days 46799 seconds',
TimedeltaTest.delta.to_json(datetime.timedelta(days=1, hours=12, minutes=59, seconds=59))
)
class TimeInfoTest(unittest.TestCase):
def test_time_info(self):
due_date = datetime.datetime(2000, 4, 14, 10, tzinfo=UTC())
grace_pd_string = '1 day 12 hours 59 minutes 59 seconds'
timeinfo = TimeInfo(due_date, grace_pd_string)
self.assertEqual(timeinfo.close_date,
due_date + Timedelta().from_json(grace_pd_string))

View File

@@ -1,15 +1,14 @@
# -*- coding: utf-8 -*-
# pylint: disable=W0232
"""Test for Xmodule functional logic."""
import json
import unittest
from lxml import etree
from xmodule.poll_module import PollDescriptor
from xmodule.conditional_module import ConditionalDescriptor
from xmodule.word_cloud_module import WordCloudDescriptor
from xmodule.videoalpha_module import VideoAlphaDescriptor
from xmodule.tests import test_system
class PostData:
"""Class which emulate postdata."""
@@ -17,6 +16,7 @@ class PostData:
self.dict_data = dict_data
def getlist(self, key):
"""Get data by key from `self.dict_data`."""
return self.dict_data.get(key)
@@ -27,9 +27,10 @@ class LogicTest(unittest.TestCase):
def setUp(self):
class EmptyClass:
"""Empty object."""
pass
self.system = None
self.system = test_system()
self.descriptor = EmptyClass()
self.xmodule_class = self.descriptor_class.module_class
@@ -40,10 +41,12 @@ class LogicTest(unittest.TestCase):
)
def ajax_request(self, dispatch, get):
"""Call Xmodule.handle_ajax."""
return json.loads(self.xmodule.handle_ajax(dispatch, get))
class PollModuleTest(LogicTest):
"""Logic tests for Poll Xmodule."""
descriptor_class = PollDescriptor
raw_model_data = {
'poll_answers': {'Yes': 1, 'Dont_know': 0, 'No': 0},
@@ -69,6 +72,7 @@ class PollModuleTest(LogicTest):
class ConditionalModuleTest(LogicTest):
"""Logic tests for Conditional Xmodule."""
descriptor_class = ConditionalDescriptor
def test_ajax_request(self):
@@ -83,6 +87,7 @@ class ConditionalModuleTest(LogicTest):
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},
@@ -91,8 +96,6 @@ class WordCloudModuleTest(LogicTest):
}
def test_bad_ajax_request(self):
# TODO: move top global test. Formalize all our Xmodule errors.
response = self.ajax_request('bad_dispatch', {})
self.assertDictEqual(response, {
'status': 'fail',
@@ -118,34 +121,6 @@ class WordCloudModuleTest(LogicTest):
{'text': 'cat', 'size': 12, 'percent': 54.0}]
)
self.assertEqual(100.0, sum(i['percent'] for i in response['top_words']) )
class VideoAlphaModuleTest(LogicTest):
descriptor_class = VideoAlphaDescriptor
raw_model_data = {
'data': '<videoalpha />'
}
def test_get_timeframe_no_parameters(self):
xmltree = etree.fromstring('<videoalpha>test</videoalpha>')
output = self.xmodule._get_timeframe(xmltree)
self.assertEqual(output, ('', ''))
def test_get_timeframe_with_one_parameter(self):
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):
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))
self.assertEqual(
100.0,
sum(i['percent'] for i in response['top_words']))

View File

@@ -1,6 +1,5 @@
from .timeparse import parse_timedelta
import logging
from xmodule.fields import Timedelta
log = logging.getLogger(__name__)
class TimeInfo(object):
@@ -14,6 +13,7 @@ class TimeInfo(object):
self.close_date - the real due date
"""
_delta_standin = Timedelta()
def __init__(self, due_date, grace_period_string):
if due_date is not None:
self.display_due_date = due_date
@@ -23,7 +23,7 @@ class TimeInfo(object):
if grace_period_string is not None and self.display_due_date:
try:
self.grace_period = parse_timedelta(grace_period_string)
self.grace_period = TimeInfo._delta_standin.from_json(grace_period_string)
self.close_date = self.display_due_date + self.grace_period
except:
log.error("Error parsing the grace period {0}".format(grace_period_string))

View File

@@ -1,46 +0,0 @@
"""
Helper functions for handling time in the format we like.
"""
import re
from datetime import timedelta, datetime
TIME_FORMAT = "%Y-%m-%dT%H:%M"
TIMEDELTA_REGEX = re.compile(r'^((?P<days>\d+?) day(?:s?))?(\s)?((?P<hours>\d+?) hour(?:s?))?(\s)?((?P<minutes>\d+?) minute(?:s)?)?(\s)?((?P<seconds>\d+?) second(?:s)?)?$')
def parse_time(time_str):
"""
Takes a time string in TIME_FORMAT
Returns it as a time_struct.
Raises ValueError if the string is not in the right format.
"""
return datetime.strptime(time_str, TIME_FORMAT)
def stringify_time(dt):
"""
Convert a datetime struct to a string
"""
return dt.isoformat()
def parse_timedelta(time_str):
"""
time_str: A string with the following components:
<D> day[s] (optional)
<H> hour[s] (optional)
<M> minute[s] (optional)
<S> second[s] (optional)
Returns a datetime.timedelta parsed from the string
"""
parts = TIMEDELTA_REGEX.match(time_str)
if not parts:
return
parts = parts.groupdict()
time_params = {}
for (name, param) in parts.iteritems():
if param:
time_params[name] = int(param)
return timedelta(**time_params)

View File

@@ -1,3 +1,15 @@
# pylint: disable=W0223
"""VideoAlpha is ungraded Xmodule for support video content.
It's new improved video module, which support additional feature:
- Can play non-YouTube video sources via in-browser HTML5 video player.
- YouTube defaults to HTML5 mode from the start.
- Speed changes in both YouTube and non-YouTube videos happen via
in-browser HTML5 video method (when in HTML5 mode).
- Navigational subtitles can be disabled altogether via an attribute
in XML.
"""
import json
import logging
@@ -21,6 +33,7 @@ log = logging.getLogger(__name__)
class VideoAlphaFields(object):
"""Fields for `VideoAlphaModule` and `VideoAlphaDescriptor`."""
data = String(help="XML data for the problem", scope=Scope.content)
position = Integer(help="Current position in the video", scope=Scope.user_state, default=0)
display_name = String(help="Display name for this module", scope=Scope.settings)
@@ -68,7 +81,7 @@ class VideoAlphaModule(VideoAlphaFields, XModule):
'ogv': self._get_source(xmltree, ['ogv']),
}
self.track = self._get_track(xmltree)
self.start_time, self.end_time = self._get_timeframe(xmltree)
self.start_time, self.end_time = self.get_timeframe(xmltree)
def _get_source(self, xmltree, exts=None):
"""Find the first valid source, which ends with one of `exts`."""
@@ -77,7 +90,7 @@ class VideoAlphaModule(VideoAlphaFields, XModule):
return self._get_first_external(xmltree, 'source', condition)
def _get_track(self, xmltree):
# find the first valid track
"""Find the first valid track."""
return self._get_first_external(xmltree, 'track')
def _get_first_external(self, xmltree, tag, condition=bool):
@@ -93,39 +106,33 @@ class VideoAlphaModule(VideoAlphaFields, XModule):
break
return result
def _get_timeframe(self, xmltree):
def get_timeframe(self, xmltree):
""" Converts 'start_time' and 'end_time' parameters in video tag to seconds.
If there are no parameters, returns empty string. """
def parse_time(s):
def parse_time(str_time):
"""Converts s in '12:34:45' format to seconds. If s is
None, returns empty string"""
if s is None:
if str_time is None:
return ''
else:
x = time.strptime(s, '%H:%M:%S')
obj_time = time.strptime(str_time, '%H:%M:%S')
return datetime.timedelta(
hours=x.tm_hour,
minutes=x.tm_min,
seconds=x.tm_sec
hours=obj_time.tm_hour,
minutes=obj_time.tm_min,
seconds=obj_time.tm_sec
).total_seconds()
return parse_time(xmltree.get('start_time')), parse_time(xmltree.get('end_time'))
def handle_ajax(self, dispatch, get):
"""Handle ajax calls to this video.
TODO (vshnayder): This is not being called right now, so the
position is not being saved.
"""
"""This is not being called right now and we raise 404 error."""
log.debug(u"GET {0}".format(get))
log.debug(u"DISPATCH {0}".format(dispatch))
if dispatch == 'goto_position':
self.position = int(float(get['position']))
log.info(u"NEW POSITION {0}".format(self.position))
return json.dumps({'success': True})
raise Http404()
def get_instance_state(self):
"""Return information about state (position)."""
return json.dumps({'position': self.position})
def get_html(self):
@@ -143,7 +150,8 @@ class VideoAlphaModule(VideoAlphaFields, XModule):
'sources': self.sources,
'track': self.track,
'display_name': self.display_name_with_default,
# TODO (cpennington): This won't work when we move to data that isn't on the filesystem
# This won't work when we move to data that
# isn't on the filesystem
'data_dir': getattr(self, 'data_dir', None),
'caption_asset_path': caption_asset_path,
'show_captions': self.show_captions,
@@ -154,5 +162,6 @@ class VideoAlphaModule(VideoAlphaFields, XModule):
class VideoAlphaDescriptor(VideoAlphaFields, RawDescriptor):
"""Descriptor for `VideoAlphaModule`."""
module_class = VideoAlphaModule
template_dir_name = "videoalpha"