diff --git a/common/lib/xmodule/xmodule/js/fixtures/videoalpha.html b/common/lib/xmodule/xmodule/js/fixtures/videoalpha.html index bccf5df2cc..b55c4d1122 100644 --- a/common/lib/xmodule/xmodule/js/fixtures/videoalpha.html +++ b/common/lib/xmodule/xmodule/js/fixtures/videoalpha.html @@ -3,7 +3,7 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file diff --git a/common/lib/xmodule/xmodule/js/spec/.gitignore b/common/lib/xmodule/xmodule/js/spec/.gitignore index 03534687ca..a8db2aae70 100644 --- a/common/lib/xmodule/xmodule/js/spec/.gitignore +++ b/common/lib/xmodule/xmodule/js/spec/.gitignore @@ -1,2 +1,4 @@ *.js +# Tests for videoalpha are written in pure JavaScript. +!videoalpha/*.js diff --git a/common/lib/xmodule/xmodule/js/spec/helper.coffee b/common/lib/xmodule/xmodule/js/spec/helper.coffee index 5f7fc27be0..e4595e8105 100644 --- a/common/lib/xmodule/xmodule/js/spec/helper.coffee +++ b/common/lib/xmodule/xmodule/js/spec/helper.coffee @@ -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 diff --git a/common/lib/xmodule/xmodule/js/spec/videoalpha/display/html5_video.coffee b/common/lib/xmodule/xmodule/js/spec/videoalpha/display/html5_video.coffee deleted file mode 100644 index 176ceb7827..0000000000 --- a/common/lib/xmodule/xmodule/js/spec/videoalpha/display/html5_video.coffee +++ /dev/null @@ -1,311 +0,0 @@ -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 diff --git a/common/lib/xmodule/xmodule/js/spec/videoalpha/display/html5_video.js b/common/lib/xmodule/xmodule/js/spec/videoalpha/display/html5_video.js new file mode 100644 index 0000000000..7c0ec571aa --- /dev/null +++ b/common/lib/xmodule/xmodule/js/spec/videoalpha/display/html5_video.js @@ -0,0 +1,316 @@ +(function () { + describe('VideoAlpha HTML5Video', function () { + var state, player, playbackRates = [0.75, 1.0, 1.25, 1.5]; + + beforeEach(function () { + loadFixtures('videoalpha_html5.html'); + state = new VideoAlpha('#example'); + player = state.videoPlayer.player; + + player.config.events.onReady = jasmine.createSpy('onReady'); + }); + + describe('events:', function () { + beforeEach(function () { + spyOn(player, 'callStateChangeCallback').andCallThrough(); + }); + + describe('click', function () { + describe('when player is paused', function () { + beforeEach(function () { + spyOn(player.video, 'play').andCallThrough(); + player.playerState = STATUS.PAUSED; + $(player.videoEl).trigger('click'); + }); + + it('native play event was called', function () { + expect(player.video.play).toHaveBeenCalled(); + }); + + it('player state was changed', function () { + waitsFor(function () { + return player.getPlayerState() !== STATUS.PAUSED; + }, 'Player state should be changed', WAIT_TIMEOUT); + + runs(function () { + expect(player.getPlayerState()).toBe(STATUS.PLAYING); + }); + }); + + it('callback was called', function () { + waitsFor(function () { + return state.videoPlayer.player.getPlayerState() !== STATUS.PAUSED; + }, 'Player state should be changed', WAIT_TIMEOUT); + + runs(function () { + expect(player.callStateChangeCallback).toHaveBeenCalled(); + }); + }); + }); + }); + + describe('when player is played', function () { + beforeEach(function () { + spyOn(player.video, 'pause').andCallThrough(); + player.playerState = STATUS.PLAYING; + $(player.videoEl).trigger('click'); + }); + + it('native event was called', function () { + expect(player.video.pause).toHaveBeenCalled(); + }); + + it('player state was changed', function () { + waitsFor(function () { + return player.getPlayerState() !== STATUS.PLAYING; + }, 'Player state should be changed', WAIT_TIMEOUT); + + runs(function () { + expect(player.getPlayerState()).toBe(STATUS.PAUSED); + }); + }); + + it('callback was called', function () { + waitsFor(function () { + return player.getPlayerState() !== STATUS.PLAYING; + }, 'Player state should be changed', WAIT_TIMEOUT); + + runs(function () { + expect(player.callStateChangeCallback).toHaveBeenCalled(); + }); + }); + }); + + describe('play', function () { + beforeEach(function () { + spyOn(player.video, 'play').andCallThrough(); + player.playerState = STATUS.PAUSED; + player.playVideo(); + }); + + it('native event was called', function () { + expect(player.video.play).toHaveBeenCalled(); + }); + + it('player state was changed', function () { + waitsFor(function () { + return player.getPlayerState() !== STATUS.PAUSED; + }, 'Player state should be changed', WAIT_TIMEOUT); + + runs(function () { + expect(player.getPlayerState()).toBe(STATUS.PLAYING); + }); + }); + + it('callback was called', function () { + waitsFor(function () { + return player.getPlayerState() !== STATUS.PAUSED; + }, 'Player state should be changed', WAIT_TIMEOUT); + + runs(function () { + expect(player.callStateChangeCallback).toHaveBeenCalled(); + }); + }); + }); + + describe('pause', function () { + beforeEach(function () { + spyOn(player.video, 'pause').andCallThrough(); + player.playVideo(); + player.pauseVideo(); + }); + + it('native event was called', function () { + expect(player.video.pause).toHaveBeenCalled(); + }); + + it('player state was changed', function () { + waitsFor(function () { + return player.getPlayerState() !== STATUS.UNSTARTED; + }, 'Player state should be changed', WAIT_TIMEOUT); + + runs(function () { + expect(player.getPlayerState()).toBe(STATUS.PAUSED); + }); + }); + + it('callback was called', function () { + waitsFor(function () { + return player.getPlayerState() !== STATUS.UNSTARTED; + }, 'Player state should be changed', WAIT_TIMEOUT); + runs(function () { + expect(player.callStateChangeCallback).toHaveBeenCalled(); + }); + }); + }); + + describe('canplay', function () { + beforeEach(function () { + waitsFor(function () { + return player.getPlayerState() !== STATUS.UNSTARTED; + }, 'Video cannot be played', WAIT_TIMEOUT); + }); + + it('player state was changed', function () { + runs(function () { + expect(player.getPlayerState()).toBe(STATUS.PAUSED); + }); + }); + + it('end property was defined', function () { + runs(function () { + expect(player.end).not.toBeNull(); + }); + }); + + it('start position was defined', function () { + runs(function () { + expect(player.video.currentTime).toBe(player.start); + }); + }); + + it('onReady callback was called', function () { + runs(function () { + expect(player.config.events.onReady).toHaveBeenCalled(); + }); + }); + }); + + describe('ended', function () { + beforeEach(function () { + waitsFor(function () { + return player.getPlayerState() !== STATUS.UNSTARTED; + }, 'Video cannot be played', WAIT_TIMEOUT); + }); + + it('player state was changed', function () { + runs(function () { + jasmine.fireEvent(player.video, 'ended'); + expect(player.getPlayerState()).toBe(STATUS.ENDED); + }); + }); + + it('callback was called', function () { + jasmine.fireEvent(player.video, 'ended'); + expect(player.callStateChangeCallback).toHaveBeenCalled(); + }); + }); + }); // End-of: describe('events:', function () { + + describe('methods', function () { + var volume, seek, duration, playbackRate; + + beforeEach(function () { + waitsFor(function () { + volume = player.video.volume; + seek = player.video.currentTime; + return player.playerState === STATUS.PAUSED; + }, 'Video cannot be played', WAIT_TIMEOUT); + }); + + it('pauseVideo', function () { + runs(function () { + spyOn(player.video, 'pause').andCallThrough(); + player.pauseVideo(); + expect(player.video.pause).toHaveBeenCalled(); + }); + }); + + describe('seekTo', function () { + it('set new correct value', function () { + runs(function () { + player.seekTo(2); + expect(player.getCurrentTime()).toBe(2); + }); + }); + + it('set new inccorrect values', function () { + runs(function () { + player.seekTo(-50); + expect(player.getCurrentTime()).toBe(seek); + player.seekTo('5'); + expect(player.getCurrentTime()).toBe(seek); + player.seekTo(500000); + expect(player.getCurrentTime()).toBe(seek); + }); + }); + }); + + describe('setVolume', function () { + it('set new correct value', function () { + runs(function () { + player.setVolume(50); + expect(player.getVolume()).toBe(50 * 0.01); + }); + }); + + it('set new incorrect values', function () { + runs(function () { + player.setVolume(-50); + expect(player.getVolume()).toBe(volume); + player.setVolume('5'); + expect(player.getVolume()).toBe(volume); + player.setVolume(500000); + expect(player.getVolume()).toBe(volume); + }); + }); + }); + + it('getCurrentTime', function () { + runs(function () { + player.video.currentTime = 3; + expect(player.getCurrentTime()).toBe(player.video.currentTime); + }); + }); + + it('playVideo', function () { + runs(function () { + spyOn(player.video, 'play').andCallThrough(); + player.playVideo(); + expect(player.video.play).toHaveBeenCalled(); + }); + }); + + it('getPlayerState', function () { + runs(function () { + player.playerState = STATUS.PLAYING; + expect(player.getPlayerState()).toBe(STATUS.PLAYING); + player.playerState = STATUS.ENDED; + expect(player.getPlayerState()).toBe(STATUS.ENDED); + }); + }); + + it('getVolume', function () { + runs(function () { + volume = player.video.volume = 0.5; + expect(player.getVolume()).toBe(volume); + }); + }); + + it('getDuration', function () { + runs(function () { + duration = player.video.duration; + expect(player.getDuration()).toBe(duration); + }); + }); + + describe('setPlaybackRate', function () { + it('set a correct value', function () { + playbackRate = 1.5; + player.setPlaybackRate(playbackRate); + expect(player.video.playbackRate).toBe(playbackRate); + }); + + it('set NaN value', function () { + playbackRate = NaN; + player.setPlaybackRate(playbackRate); + expect(player.video.playbackRate).toBe(1.0); + }); + }); + + it('getAvailablePlaybackRates', function () { + expect(player.getAvailablePlaybackRates()).toEqual(playbackRates); + }); + }); // End-of: describe('methods', function () { + }); +}).call(this); diff --git a/common/lib/xmodule/xmodule/js/spec/videoalpha/display/video_caption_spec.coffee b/common/lib/xmodule/xmodule/js/spec/videoalpha/display/video_caption_spec.coffee deleted file mode 100644 index 4bd237b81d..0000000000 --- a/common/lib/xmodule/xmodule/js/spec/videoalpha/display/video_caption_spec.coffee +++ /dev/null @@ -1,373 +0,0 @@ -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() diff --git a/common/lib/xmodule/xmodule/js/spec/videoalpha/display/video_caption_spec.js b/common/lib/xmodule/xmodule/js/spec/videoalpha/display/video_caption_spec.js new file mode 100644 index 0000000000..2263868483 --- /dev/null +++ b/common/lib/xmodule/xmodule/js/spec/videoalpha/display/video_caption_spec.js @@ -0,0 +1,417 @@ +// Generated by CoffeeScript 1.6.3 +(function() { + xdescribe('VideoCaptionAlpha', function() { + beforeEach(function() { + spyOn(VideoCaptionAlpha.prototype, 'fetchCaption').andCallThrough(); + spyOn($, 'ajaxWithPrefix').andCallThrough(); + return window.onTouchBasedDevice = jasmine.createSpy('onTouchBasedDevice').andReturn(false); + }); + afterEach(function() { + YT.Player = void 0; + $.fn.scrollTo.reset(); + return $('.subtitles').remove(); + }); + describe('constructor', function() { + describe('always', function() { + beforeEach(function() { + this.player = jasmine.stubVideoPlayerAlpha(this); + return this.caption = this.player.caption; + }); + it('set the youtube id', function() { + return expect(this.caption.youtubeId).toEqual('normalSpeedYoutubeId'); + }); + it('create the caption element', function() { + return expect($('.video')).toContain('ol.subtitles'); + }); + it('add caption control to video player', function() { + return expect($('.video')).toContain('a.hide-subtitles'); + }); + it('fetch the caption', function() { + expect(this.caption.loaded).toBeTruthy(); + expect(this.caption.fetchCaption).toHaveBeenCalled(); + return expect($.ajaxWithPrefix).toHaveBeenCalledWith({ + url: this.caption.captionURL(), + notifyOnError: false, + success: jasmine.any(Function) + }); + }); + it('bind window resize event', function() { + return expect($(window)).toHandleWith('resize', this.caption.resize); + }); + it('bind the hide caption button', function() { + return expect($('.hide-subtitles')).toHandleWith('click', this.caption.toggle); + }); + return it('bind the mouse movement', function() { + expect($('.subtitles')).toHandleWith('mouseover', this.caption.onMouseEnter); + expect($('.subtitles')).toHandleWith('mouseout', this.caption.onMouseLeave); + expect($('.subtitles')).toHandleWith('mousemove', this.caption.onMovement); + expect($('.subtitles')).toHandleWith('mousewheel', this.caption.onMovement); + return expect($('.subtitles')).toHandleWith('DOMMouseScroll', this.caption.onMovement); + }); + }); + describe('when on a non touch-based device', function() { + beforeEach(function() { + this.player = jasmine.stubVideoPlayerAlpha(this); + return this.caption = this.player.caption; + }); + it('render the caption', function() { + var captionsData, + _this = this; + captionsData = jasmine.stubbedCaption; + return $('.subtitles li[data-index]').each(function(index, link) { + expect($(link)).toHaveData('index', index); + expect($(link)).toHaveData('start', captionsData.start[index]); + return expect($(link)).toHaveText(captionsData.text[index]); + }); + }); + it('add a padding element to caption', function() { + expect($('.subtitles li:first')).toBe('.spacing'); + return expect($('.subtitles li:last')).toBe('.spacing'); + }); + it('bind all the caption link', function() { + var _this = this; + return $('.subtitles li[data-index]').each(function(index, link) { + return expect($(link)).toHandleWith('click', _this.caption.seekPlayer); + }); + }); + return it('set rendered to true', function() { + return expect(this.caption.rendered).toBeTruthy(); + }); + }); + return describe('when on a touch-based device', function() { + beforeEach(function() { + window.onTouchBasedDevice.andReturn(true); + this.player = jasmine.stubVideoPlayerAlpha(this); + return this.caption = this.player.caption; + }); + it('show explaination message', function() { + return expect($('.subtitles li')).toHaveHtml("Caption will be displayed when you start playing the video."); + }); + return it('does not set rendered to true', function() { + return expect(this.caption.rendered).toBeFalsy(); + }); + }); + }); + describe('mouse movement', function() { + beforeEach(function() { + this.player = jasmine.stubVideoPlayerAlpha(this); + this.caption = this.player.caption; + window.setTimeout.andReturn(100); + return spyOn(window, 'clearTimeout'); + }); + describe('when cursor is outside of the caption box', function() { + beforeEach(function() { + return $(window).trigger(jQuery.Event('mousemove')); + }); + return it('does not set freezing timeout', function() { + return expect(this.caption.frozen).toBeFalsy(); + }); + }); + describe('when cursor is in the caption box', function() { + beforeEach(function() { + return $('.subtitles').trigger(jQuery.Event('mouseenter')); + }); + it('set the freezing timeout', function() { + return expect(this.caption.frozen).toEqual(100); + }); + describe('when the cursor is moving', function() { + beforeEach(function() { + return $('.subtitles').trigger(jQuery.Event('mousemove')); + }); + return it('reset the freezing timeout', function() { + return expect(window.clearTimeout).toHaveBeenCalledWith(100); + }); + }); + return describe('when the mouse is scrolling', function() { + beforeEach(function() { + return $('.subtitles').trigger(jQuery.Event('mousewheel')); + }); + return it('reset the freezing timeout', function() { + return expect(window.clearTimeout).toHaveBeenCalledWith(100); + }); + }); + }); + return describe('when cursor is moving out of the caption box', function() { + beforeEach(function() { + this.caption.frozen = 100; + return $.fn.scrollTo.reset(); + }); + describe('always', function() { + beforeEach(function() { + return $('.subtitles').trigger(jQuery.Event('mouseout')); + }); + it('reset the freezing timeout', function() { + return expect(window.clearTimeout).toHaveBeenCalledWith(100); + }); + return it('unfreeze the caption', function() { + return expect(this.caption.frozen).toBeNull(); + }); + }); + describe('when the player is playing', function() { + beforeEach(function() { + this.caption.playing = true; + $('.subtitles li[data-index]:first').addClass('current'); + return $('.subtitles').trigger(jQuery.Event('mouseout')); + }); + return it('scroll the caption', function() { + return expect($.fn.scrollTo).toHaveBeenCalled(); + }); + }); + return describe('when the player is not playing', function() { + beforeEach(function() { + this.caption.playing = false; + return $('.subtitles').trigger(jQuery.Event('mouseout')); + }); + return it('does not scroll the caption', function() { + return expect($.fn.scrollTo).not.toHaveBeenCalled(); + }); + }); + }); + }); + describe('search', function() { + beforeEach(function() { + this.player = jasmine.stubVideoPlayerAlpha(this); + return this.caption = this.player.caption; + }); + return it('return a correct caption index', function() { + expect(this.caption.search(0)).toEqual(0); + expect(this.caption.search(9999)).toEqual(0); + expect(this.caption.search(10000)).toEqual(1); + expect(this.caption.search(15000)).toEqual(1); + expect(this.caption.search(30000)).toEqual(3); + return expect(this.caption.search(30001)).toEqual(3); + }); + }); + describe('play', function() { + return describe('when the caption was not rendered', function() { + beforeEach(function() { + window.onTouchBasedDevice.andReturn(true); + this.player = jasmine.stubVideoPlayerAlpha(this); + this.caption = this.player.caption; + return this.caption.play(); + }); + it('render the caption', function() { + var captionsData, + _this = this; + captionsData = jasmine.stubbedCaption; + return $('.subtitles li[data-index]').each(function(index, link) { + expect($(link)).toHaveData('index', index); + expect($(link)).toHaveData('start', captionsData.start[index]); + return expect($(link)).toHaveText(captionsData.text[index]); + }); + }); + it('add a padding element to caption', function() { + expect($('.subtitles li:first')).toBe('.spacing'); + return expect($('.subtitles li:last')).toBe('.spacing'); + }); + it('bind all the caption link', function() { + var _this = this; + return $('.subtitles li[data-index]').each(function(index, link) { + return expect($(link)).toHandleWith('click', _this.caption.seekPlayer); + }); + }); + it('set rendered to true', function() { + return expect(this.caption.rendered).toBeTruthy(); + }); + return it('set playing to true', function() { + return expect(this.caption.playing).toBeTruthy(); + }); + }); + }); + describe('pause', function() { + beforeEach(function() { + this.player = jasmine.stubVideoPlayerAlpha(this); + this.caption = this.player.caption; + this.caption.playing = true; + return this.caption.pause(); + }); + return it('set playing to false', function() { + return expect(this.caption.playing).toBeFalsy(); + }); + }); + describe('updatePlayTime', function() { + beforeEach(function() { + this.player = jasmine.stubVideoPlayerAlpha(this); + return this.caption = this.player.caption; + }); + describe('when the video speed is 1.0x', function() { + beforeEach(function() { + this.caption.currentSpeed = '1.0'; + return this.caption.updatePlayTime(25.000); + }); + return it('search the caption based on time', function() { + return expect(this.caption.currentIndex).toEqual(2); + }); + }); + describe('when the video speed is not 1.0x', function() { + beforeEach(function() { + this.caption.currentSpeed = '0.75'; + return this.caption.updatePlayTime(25.000); + }); + return it('search the caption based on 1.0x speed', function() { + return expect(this.caption.currentIndex).toEqual(1); + }); + }); + describe('when the index is not the same', function() { + beforeEach(function() { + this.caption.currentIndex = 1; + $('.subtitles li[data-index=1]').addClass('current'); + return this.caption.updatePlayTime(25.000); + }); + it('deactivate the previous caption', function() { + return expect($('.subtitles li[data-index=1]')).not.toHaveClass('current'); + }); + it('activate new caption', function() { + return expect($('.subtitles li[data-index=2]')).toHaveClass('current'); + }); + it('save new index', function() { + return expect(this.caption.currentIndex).toEqual(2); + }); + return it('scroll caption to new position', function() { + return expect($.fn.scrollTo).toHaveBeenCalled(); + }); + }); + return describe('when the index is the same', function() { + beforeEach(function() { + this.caption.currentIndex = 1; + $('.subtitles li[data-index=1]').addClass('current'); + return this.caption.updatePlayTime(15.000); + }); + return it('does not change current subtitle', function() { + return expect($('.subtitles li[data-index=1]')).toHaveClass('current'); + }); + }); + }); + describe('resize', function() { + beforeEach(function() { + this.player = jasmine.stubVideoPlayerAlpha(this); + this.caption = this.player.caption; + $('.subtitles li[data-index=1]').addClass('current'); + return this.caption.resize(); + }); + it('set the height of caption container', function() { + return expect(parseInt($('.subtitles').css('maxHeight'))).toBeCloseTo($('.video-wrapper').height(), 2); + }); + it('set the height of caption spacing', function() { + var firstSpacing, lastSpacing; + firstSpacing = Math.abs(parseInt($('.subtitles .spacing:first').css('height'))); + lastSpacing = Math.abs(parseInt($('.subtitles .spacing:last').css('height'))); + expect(firstSpacing - this.caption.topSpacingHeight()).toBeLessThan(1); + return expect(lastSpacing - this.caption.bottomSpacingHeight()).toBeLessThan(1); + }); + return it('scroll caption to new position', function() { + return expect($.fn.scrollTo).toHaveBeenCalled(); + }); + }); + describe('scrollCaption', function() { + beforeEach(function() { + this.player = jasmine.stubVideoPlayerAlpha(this); + return this.caption = this.player.caption; + }); + describe('when frozen', function() { + beforeEach(function() { + this.caption.frozen = true; + $('.subtitles li[data-index=1]').addClass('current'); + return this.caption.scrollCaption(); + }); + return it('does not scroll the caption', function() { + return expect($.fn.scrollTo).not.toHaveBeenCalled(); + }); + }); + return describe('when not frozen', function() { + beforeEach(function() { + return this.caption.frozen = false; + }); + describe('when there is no current caption', function() { + beforeEach(function() { + return this.caption.scrollCaption(); + }); + return it('does not scroll the caption', function() { + return expect($.fn.scrollTo).not.toHaveBeenCalled(); + }); + }); + return describe('when there is a current caption', function() { + beforeEach(function() { + $('.subtitles li[data-index=1]').addClass('current'); + return this.caption.scrollCaption(); + }); + return it('scroll to current caption', function() { + var offset; + offset = -0.5 * ($('.video-wrapper').height() - $('.subtitles .current:first').height()); + return expect($.fn.scrollTo).toHaveBeenCalledWith($('.subtitles .current:first', this.caption.el), { + offset: offset + }); + }); + }); + }); + }); + describe('seekPlayer', function() { + beforeEach(function() { + var _this = this; + this.player = jasmine.stubVideoPlayerAlpha(this); + this.caption = this.player.caption; + return $(this.caption).bind('seek', function(event, time) { + return _this.time = time; + }); + }); + describe('when the video speed is 1.0x', function() { + beforeEach(function() { + this.caption.currentSpeed = '1.0'; + return $('.subtitles li[data-start="30000"]').trigger('click'); + }); + return it('trigger seek event with the correct time', function() { + return expect(this.player.currentTime).toEqual(30.000); + }); + }); + return describe('when the video speed is not 1.0x', function() { + beforeEach(function() { + this.caption.currentSpeed = '0.75'; + return $('.subtitles li[data-start="30000"]').trigger('click'); + }); + return it('trigger seek event with the correct time', function() { + return expect(this.player.currentTime).toEqual(40.000); + }); + }); + }); + return describe('toggle', function() { + beforeEach(function() { + this.player = jasmine.stubVideoPlayerAlpha(this); + spyOn(this.video, 'log'); + this.caption = this.player.caption; + return $('.subtitles li[data-index=1]').addClass('current'); + }); + describe('when the caption is visible', function() { + beforeEach(function() { + this.caption.el.removeClass('closed'); + return this.caption.toggle(jQuery.Event('click')); + }); + it('log the hide_transcript event', function() { + return expect(this.video.log).toHaveBeenCalledWith('hide_transcript', { + currentTime: this.player.currentTime + }); + }); + return it('hide the caption', function() { + return expect(this.caption.el).toHaveClass('closed'); + }); + }); + return describe('when the caption is hidden', function() { + beforeEach(function() { + this.caption.el.addClass('closed'); + return this.caption.toggle(jQuery.Event('click')); + }); + it('log the show_transcript event', function() { + return expect(this.video.log).toHaveBeenCalledWith('show_transcript', { + currentTime: this.player.currentTime + }); + }); + it('show the caption', function() { + return expect(this.caption.el).not.toHaveClass('closed'); + }); + return it('scroll the caption', function() { + return expect($.fn.scrollTo).toHaveBeenCalled(); + }); + }); + }); + }); + +}).call(this); diff --git a/common/lib/xmodule/xmodule/js/spec/videoalpha/display/video_control_spec.coffee b/common/lib/xmodule/xmodule/js/spec/videoalpha/display/video_control_spec.coffee deleted file mode 100644 index a4dc8739d8..0000000000 --- a/common/lib/xmodule/xmodule/js/spec/videoalpha/display/video_control_spec.coffee +++ /dev/null @@ -1,103 +0,0 @@ -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 diff --git a/common/lib/xmodule/xmodule/js/spec/videoalpha/display/video_control_spec.js b/common/lib/xmodule/xmodule/js/spec/videoalpha/display/video_control_spec.js new file mode 100644 index 0000000000..9def050941 --- /dev/null +++ b/common/lib/xmodule/xmodule/js/spec/videoalpha/display/video_control_spec.js @@ -0,0 +1,128 @@ +// 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); diff --git a/common/lib/xmodule/xmodule/js/spec/videoalpha/display/video_player_spec.coffee b/common/lib/xmodule/xmodule/js/spec/videoalpha/display/video_player_spec.coffee deleted file mode 100644 index e9a5ca30b4..0000000000 --- a/common/lib/xmodule/xmodule/js/spec/videoalpha/display/video_player_spec.coffee +++ /dev/null @@ -1,561 +0,0 @@ -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 $('
') - @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 $('
') - 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 $('
') - @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 $('
') - $('.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 $('
') - 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 $('
') - @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 $('
') - - 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 $('
') - @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 $('
') - @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 $('
') - @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 $('
') - @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 $('
') - @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 $('
') - @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 $('
') - @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 $('
') - @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 $('
') - @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 $('
') - @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 $('
') - @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 $('
') - @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) diff --git a/common/lib/xmodule/xmodule/js/spec/videoalpha/display/video_player_spec.js b/common/lib/xmodule/xmodule/js/spec/videoalpha/display/video_player_spec.js new file mode 100644 index 0000000000..d1c3938181 --- /dev/null +++ b/common/lib/xmodule/xmodule/js/spec/videoalpha/display/video_player_spec.js @@ -0,0 +1,677 @@ +// Generated by CoffeeScript 1.6.3 +(function() { + xdescribe('VideoPlayerAlpha', function() { + var playerVars; + playerVars = { + controls: 0, + wmode: 'transparent', + rel: 0, + showinfo: 0, + enablejsapi: 1, + modestbranding: 1, + html5: 1 + }; + beforeEach(function() { + var part, _i, _len, _ref, _results; + window.onTouchBasedDevice = jasmine.createSpy('onTouchBasedDevice').andReturn(false); + _ref = ['VideoCaptionAlpha', 'VideoSpeedControlAlpha', 'VideoVolumeControlAlpha', 'VideoProgressSliderAlpha', 'VideoControlAlpha']; + _results = []; + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + part = _ref[_i]; + _results.push(spyOn(window[part].prototype, 'initialize').andCallThrough()); + } + return _results; + }); + afterEach(function() { + return YT.Player = void 0; + }); + describe('constructor', function() { + beforeEach(function() { + return $.fn.qtip.andCallFake(function() { + return $(this).data('qtip', true); + }); + }); + describe('always', function() { + beforeEach(function() { + jasmine.stubVideoPlayerAlpha(this, [], false); + $('.video').append($('
')); + return this.player = new VideoPlayerAlpha({ + video: this.video + }); + }); + it('instanticate current time to zero', function() { + return expect(this.player.currentTime).toEqual(0); + }); + it('set the element', function() { + return expect(this.player.el).toHaveId('video_id'); + }); + it('create video control', function() { + expect(window.VideoControlAlpha.prototype.initialize).toHaveBeenCalled(); + expect(this.player.control).toBeDefined(); + return expect(this.player.control.el).toBe($('.video-controls', this.player.el)); + }); + it('create video caption', function() { + expect(window.VideoCaptionAlpha.prototype.initialize).toHaveBeenCalled(); + expect(this.player.caption).toBeDefined(); + expect(this.player.caption.el).toBe(this.player.el); + expect(this.player.caption.youtubeId).toEqual('normalSpeedYoutubeId'); + expect(this.player.caption.currentSpeed).toEqual('1.0'); + return expect(this.player.caption.captionAssetPath).toEqual('/static/subs/'); + }); + it('create video speed control', function() { + expect(window.VideoSpeedControlAlpha.prototype.initialize).toHaveBeenCalled(); + expect(this.player.speedControl).toBeDefined(); + expect(this.player.speedControl.el).toBe($('.secondary-controls', this.player.el)); + expect(this.player.speedControl.speeds).toEqual(['0.75', '1.0']); + return expect(this.player.speedControl.currentSpeed).toEqual('1.0'); + }); + it('create video progress slider', function() { + expect(window.VideoSpeedControlAlpha.prototype.initialize).toHaveBeenCalled(); + expect(this.player.progressSlider).toBeDefined(); + return expect(this.player.progressSlider.el).toBe($('.slider', this.player.el)); + }); + it('bind to video control play event', function() { + return expect($(this.player.control)).toHandleWith('play', this.player.play); + }); + it('bind to video control pause event', function() { + return expect($(this.player.control)).toHandleWith('pause', this.player.pause); + }); + it('bind to video caption seek event', function() { + return expect($(this.player.caption)).toHandleWith('caption_seek', this.player.onSeek); + }); + it('bind to video speed control speedChange event', function() { + return expect($(this.player.speedControl)).toHandleWith('speedChange', this.player.onSpeedChange); + }); + it('bind to video progress slider seek event', function() { + return expect($(this.player.progressSlider)).toHandleWith('slide_seek', this.player.onSeek); + }); + it('bind to video volume control volumeChange event', function() { + return expect($(this.player.volumeControl)).toHandleWith('volumeChange', this.player.onVolumeChange); + }); + it('bind to key press', function() { + return expect($(document.documentElement)).toHandleWith('keyup', this.player.bindExitFullScreen); + }); + return it('bind to fullscreen switching button', function() { + return expect($('.add-fullscreen')).toHandleWith('click', this.player.toggleFullScreen); + }); + }); + it('create Youtube player', function() { + jasmine.stubVideoPlayerAlpha(this, [], false); + $('.video').append($('
')); + spyOn(YT, 'Player'); + this.player = new VideoPlayerAlpha({ + video: this.video + }); + return expect(YT.Player).toHaveBeenCalledWith('id', { + playerVars: playerVars, + videoId: 'normalSpeedYoutubeId', + events: { + onReady: this.player.onReady, + onStateChange: this.player.onStateChange, + onPlaybackQualityChange: this.player.onPlaybackQualityChange + } + }); + }); + it('create HTML5 player', function() { + jasmine.stubVideoPlayerAlpha(this, [], false, true); + spyOn(HTML5Video, 'Player'); + $('.video').append($('
')); + this.player = new VideoPlayerAlpha({ + video: this.video + }); + return expect(HTML5Video.Player).toHaveBeenCalledWith(this.video.el, { + playerVars: playerVars, + videoSources: this.video.html5Sources, + events: { + onReady: this.player.onReady, + onStateChange: this.player.onStateChange + } + }); + }); + describe('when not on a touch based device', function() { + beforeEach(function() { + jasmine.stubVideoPlayerAlpha(this, [], false); + $('.video').append($('
')); + $('.add-fullscreen, .hide-subtitles').removeData('qtip'); + return this.player = new VideoPlayerAlpha({ + video: this.video + }); + }); + it('add the tooltip to fullscreen and subtitle button', function() { + expect($('.add-fullscreen')).toHaveData('qtip'); + return expect($('.hide-subtitles')).toHaveData('qtip'); + }); + return it('create video volume control', function() { + expect(window.VideoVolumeControlAlpha.prototype.initialize).toHaveBeenCalled(); + expect(this.player.volumeControl).toBeDefined(); + return expect(this.player.volumeControl.el).toBe($('.secondary-controls', this.player.el)); + }); + }); + return describe('when on a touch based device', function() { + beforeEach(function() { + jasmine.stubVideoPlayerAlpha(this, [], false); + $('.video').append($('
')); + window.onTouchBasedDevice.andReturn(true); + $('.add-fullscreen, .hide-subtitles').removeData('qtip'); + return this.player = new VideoPlayerAlpha({ + video: this.video + }); + }); + it('does not add the tooltip to fullscreen and subtitle button', function() { + expect($('.add-fullscreen')).not.toHaveData('qtip'); + return expect($('.hide-subtitles')).not.toHaveData('qtip'); + }); + return it('does not create video volume control', function() { + expect(window.VideoVolumeControlAlpha.prototype.initialize).not.toHaveBeenCalled(); + return expect(this.player.volumeControl).not.toBeDefined(); + }); + }); + }); + describe('onReady', function() { + beforeEach(function() { + jasmine.stubVideoPlayerAlpha(this, [], false); + spyOn(this.video, 'log'); + $('.video').append($('
')); + this.video.embed(); + this.player = this.video.player; + spyOnEvent(this.player, 'ready'); + spyOnEvent(this.player, 'updatePlayTime'); + return this.player.onReady(); + }); + it('log the load_video event', function() { + return expect(this.video.log).toHaveBeenCalledWith('load_video'); + }); + describe('when not on a touch based device', function() { + beforeEach(function() { + spyOn(this.player, 'play'); + return this.player.onReady(); + }); + return it('autoplay the first video', function() { + return expect(this.player.play).toHaveBeenCalled(); + }); + }); + return describe('when on a touch based device', function() { + beforeEach(function() { + window.onTouchBasedDevice.andReturn(true); + spyOn(this.player, 'play'); + return this.player.onReady(); + }); + return it('does not autoplay the first video', function() { + return expect(this.player.play).not.toHaveBeenCalled(); + }); + }); + }); + describe('onStateChange', function() { + beforeEach(function() { + jasmine.stubVideoPlayerAlpha(this, [], false); + return $('.video').append($('
')); + }); + describe('when the video is unstarted', function() { + beforeEach(function() { + this.player = new VideoPlayerAlpha({ + video: this.video + }); + spyOn(this.player.control, 'pause'); + this.player.caption.pause = jasmine.createSpy('VideoCaptionAlpha.pause'); + return this.player.onStateChange({ + data: YT.PlayerState.UNSTARTED + }); + }); + it('pause the video control', function() { + return expect(this.player.control.pause).toHaveBeenCalled(); + }); + return it('pause the video caption', function() { + return expect(this.player.caption.pause).toHaveBeenCalled(); + }); + }); + describe('when the video is playing', function() { + beforeEach(function() { + this.anotherPlayer = jasmine.createSpyObj('AnotherPlayer', ['onPause']); + window.OldVideoPlayerAlpha = this.anotherPlayer; + this.player = new VideoPlayerAlpha({ + video: this.video + }); + spyOn(this.video, 'log'); + spyOn(window, 'setInterval').andReturn(100); + spyOn(this.player.control, 'play'); + this.player.caption.play = jasmine.createSpy('VideoCaptionAlpha.play'); + this.player.progressSlider.play = jasmine.createSpy('VideoProgressSliderAlpha.play'); + this.player.player.getVideoEmbedCode.andReturn('embedCode'); + return this.player.onStateChange({ + data: YT.PlayerState.PLAYING + }); + }); + it('log the play_video event', function() { + return expect(this.video.log).toHaveBeenCalledWith('play_video', { + currentTime: 0 + }); + }); + it('pause other video player', function() { + return expect(this.anotherPlayer.onPause).toHaveBeenCalled(); + }); + it('set current video player as active player', function() { + return expect(window.OldVideoPlayerAlpha).toEqual(this.player); + }); + it('set update interval', function() { + expect(window.setInterval).toHaveBeenCalledWith(this.player.update, 200); + return expect(this.player.player.interval).toEqual(100); + }); + it('play the video control', function() { + return expect(this.player.control.play).toHaveBeenCalled(); + }); + it('play the video caption', function() { + return expect(this.player.caption.play).toHaveBeenCalled(); + }); + return it('play the video progress slider', function() { + return expect(this.player.progressSlider.play).toHaveBeenCalled(); + }); + }); + describe('when the video is paused', function() { + beforeEach(function() { + this.player = new VideoPlayerAlpha({ + video: this.video + }); + spyOn(this.video, 'log'); + spyOn(window, 'clearInterval'); + spyOn(this.player.control, 'pause'); + this.player.caption.pause = jasmine.createSpy('VideoCaptionAlpha.pause'); + this.player.player.interval = 100; + this.player.player.getVideoEmbedCode.andReturn('embedCode'); + return this.player.onStateChange({ + data: YT.PlayerState.PAUSED + }); + }); + it('log the pause_video event', function() { + return expect(this.video.log).toHaveBeenCalledWith('pause_video', { + currentTime: 0 + }); + }); + it('clear update interval', function() { + expect(window.clearInterval).toHaveBeenCalledWith(100); + return expect(this.player.player.interval).toBeNull(); + }); + it('pause the video control', function() { + return expect(this.player.control.pause).toHaveBeenCalled(); + }); + return it('pause the video caption', function() { + return expect(this.player.caption.pause).toHaveBeenCalled(); + }); + }); + return describe('when the video is ended', function() { + beforeEach(function() { + this.player = new VideoPlayerAlpha({ + video: this.video + }); + spyOn(this.player.control, 'pause'); + this.player.caption.pause = jasmine.createSpy('VideoCaptionAlpha.pause'); + return this.player.onStateChange({ + data: YT.PlayerState.ENDED + }); + }); + it('pause the video control', function() { + return expect(this.player.control.pause).toHaveBeenCalled(); + }); + return it('pause the video caption', function() { + return expect(this.player.caption.pause).toHaveBeenCalled(); + }); + }); + }); + describe('onSeek', function() { + var conf; + 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(function() { + jasmine.stubVideoPlayerAlpha(this, [], false); + $('.video').append($('
')); + this.player = new VideoPlayerAlpha({ + video: this.video + }); + spyOn(window, 'clearInterval'); + this.player.player.interval = 100; + spyOn(this.player, 'updatePlayTime'); + return spyOn(this.video, 'log'); + }); + $.each(conf, function(key, value) { + return it(value.desc, function() { + var new_time, old_time, type; + type = value.type; + old_time = 0; + new_time = 60; + $(this.player[value.obj]).trigger(value.type, new_time); + return expect(this.video.log).toHaveBeenCalledWith('seek_video', { + old_time: old_time, + new_time: new_time, + type: value.type + }); + }); + }); + it('seek the player', function() { + $(this.player.progressSlider).trigger('slide_seek', 60); + return expect(this.player.player.seekTo).toHaveBeenCalledWith(60, true); + }); + it('call updatePlayTime on player', function() { + $(this.player.progressSlider).trigger('slide_seek', 60); + return expect(this.player.updatePlayTime).toHaveBeenCalledWith(60); + }); + describe('when the player is playing', function() { + beforeEach(function() { + $(this.player.progressSlider).trigger('slide_seek', 60); + this.player.player.getPlayerState.andReturn(YT.PlayerState.PLAYING); + return this.player.onSeek({}, 60); + }); + return it('reset the update interval', function() { + return expect(window.clearInterval).toHaveBeenCalledWith(100); + }); + }); + return describe('when the player is not playing', function() { + beforeEach(function() { + $(this.player.progressSlider).trigger('slide_seek', 60); + this.player.player.getPlayerState.andReturn(YT.PlayerState.PAUSED); + return this.player.onSeek({}, 60); + }); + return it('set the current time', function() { + return expect(this.player.currentTime).toEqual(60); + }); + }); + }); + describe('onSpeedChange', function() { + beforeEach(function() { + jasmine.stubVideoPlayerAlpha(this, [], false); + $('.video').append($('
')); + this.player = new VideoPlayerAlpha({ + video: this.video + }); + this.player.currentTime = 60; + spyOn(this.player, 'updatePlayTime'); + spyOn(this.video, 'setSpeed').andCallThrough(); + return spyOn(this.video, 'log'); + }); + describe('always', function() { + beforeEach(function() { + return this.player.onSpeedChange({}, '0.75', false); + }); + it('check if speed_change_video is logged', function() { + return expect(this.video.log).toHaveBeenCalledWith('speed_change_video', { + currentTime: this.player.currentTime, + old_speed: '1.0', + new_speed: '0.75' + }); + }); + it('convert the current time to the new speed', function() { + return expect(this.player.currentTime).toEqual('80.000'); + }); + it('set video speed to the new speed', function() { + return expect(this.video.setSpeed).toHaveBeenCalledWith('0.75', false); + }); + return it('tell video caption that the speed has changed', function() { + return expect(this.player.caption.currentSpeed).toEqual('0.75'); + }); + }); + describe('when the video is playing', function() { + beforeEach(function() { + this.player.player.getPlayerState.andReturn(YT.PlayerState.PLAYING); + return this.player.onSpeedChange({}, '0.75'); + }); + it('load the video', function() { + return expect(this.player.player.loadVideoById).toHaveBeenCalledWith('slowerSpeedYoutubeId', '80.000'); + }); + return it('trigger updatePlayTime event', function() { + return expect(this.player.updatePlayTime).toHaveBeenCalledWith('80.000'); + }); + }); + return describe('when the video is not playing', function() { + beforeEach(function() { + this.player.player.getPlayerState.andReturn(YT.PlayerState.PAUSED); + return this.player.onSpeedChange({}, '0.75'); + }); + it('cue the video', function() { + return expect(this.player.player.cueVideoById).toHaveBeenCalledWith('slowerSpeedYoutubeId', '80.000'); + }); + return it('trigger updatePlayTime event', function() { + return expect(this.player.updatePlayTime).toHaveBeenCalledWith('80.000'); + }); + }); + }); + describe('onVolumeChange', function() { + beforeEach(function() { + jasmine.stubVideoPlayerAlpha(this, [], false); + $('.video').append($('
')); + this.player = new VideoPlayerAlpha({ + video: this.video + }); + return this.player.onVolumeChange(void 0, 60); + }); + return it('set the volume on player', function() { + return expect(this.player.player.setVolume).toHaveBeenCalledWith(60); + }); + }); + describe('update', function() { + beforeEach(function() { + jasmine.stubVideoPlayerAlpha(this, [], false); + $('.video').append($('
')); + this.player = new VideoPlayerAlpha({ + video: this.video + }); + return spyOn(this.player, 'updatePlayTime'); + }); + describe('when the current time is unavailable from the player', function() { + beforeEach(function() { + this.player.player.getCurrentTime.andReturn(void 0); + return this.player.update(); + }); + return it('does not trigger updatePlayTime event', function() { + return expect(this.player.updatePlayTime).not.toHaveBeenCalled(); + }); + }); + return describe('when the current time is available from the player', function() { + beforeEach(function() { + this.player.player.getCurrentTime.andReturn(60); + return this.player.update(); + }); + return it('trigger updatePlayTime event', function() { + return expect(this.player.updatePlayTime).toHaveBeenCalledWith(60); + }); + }); + }); + describe('updatePlayTime', function() { + beforeEach(function() { + jasmine.stubVideoPlayerAlpha(this, [], false); + $('.video').append($('
')); + this.player = new VideoPlayerAlpha({ + video: this.video + }); + spyOn(this.video, 'getDuration').andReturn(1800); + this.player.caption.updatePlayTime = jasmine.createSpy('VideoCaptionAlpha.updatePlayTime'); + this.player.progressSlider.updatePlayTime = jasmine.createSpy('VideoProgressSliderAlpha.updatePlayTime'); + return this.player.updatePlayTime(60); + }); + it('update the video playback time', function() { + return expect($('.vidtime')).toHaveHtml('1:00 / 30:00'); + }); + it('update the playback time on caption', function() { + return expect(this.player.caption.updatePlayTime).toHaveBeenCalledWith(60); + }); + return it('update the playback time on progress slider', function() { + return expect(this.player.progressSlider.updatePlayTime).toHaveBeenCalledWith(60, 1800); + }); + }); + describe('toggleFullScreen', function() { + beforeEach(function() { + jasmine.stubVideoPlayerAlpha(this, [], false); + $('.video').append($('
')); + this.player = new VideoPlayerAlpha({ + video: this.video + }); + return this.player.caption.resize = jasmine.createSpy('VideoCaptionAlpha.resize'); + }); + describe('when the video player is not full screen', function() { + beforeEach(function() { + spyOn(this.video, 'log'); + this.player.el.removeClass('fullscreen'); + return this.player.toggleFullScreen(jQuery.Event("click")); + }); + it('log the fullscreen event', function() { + return expect(this.video.log).toHaveBeenCalledWith('fullscreen', { + currentTime: this.player.currentTime + }); + }); + it('replace the full screen button tooltip', function() { + return expect($('.add-fullscreen')).toHaveAttr('title', 'Exit fill browser'); + }); + it('add the fullscreen class', function() { + return expect(this.player.el).toHaveClass('fullscreen'); + }); + return it('tell VideoCaption to resize', function() { + return expect(this.player.caption.resize).toHaveBeenCalled(); + }); + }); + return describe('when the video player already full screen', function() { + beforeEach(function() { + spyOn(this.video, 'log'); + this.player.el.addClass('fullscreen'); + return this.player.toggleFullScreen(jQuery.Event("click")); + }); + it('log the not_fullscreen event', function() { + return expect(this.video.log).toHaveBeenCalledWith('not_fullscreen', { + currentTime: this.player.currentTime + }); + }); + it('replace the full screen button tooltip', function() { + return expect($('.add-fullscreen')).toHaveAttr('title', 'Fill browser'); + }); + it('remove exit full screen button', function() { + return expect(this.player.el).not.toContain('a.exit'); + }); + it('remove the fullscreen class', function() { + return expect(this.player.el).not.toHaveClass('fullscreen'); + }); + return it('tell VideoCaption to resize', function() { + return expect(this.player.caption.resize).toHaveBeenCalled(); + }); + }); + }); + describe('play', function() { + beforeEach(function() { + jasmine.stubVideoPlayerAlpha(this, [], false); + $('.video').append($('
')); + return this.player = new VideoPlayerAlpha({ + video: this.video + }); + }); + describe('when the player is not ready', function() { + beforeEach(function() { + this.player.player.playVideo = void 0; + return this.player.play(); + }); + return it('does nothing', function() { + return expect(this.player.player.playVideo).toBeUndefined(); + }); + }); + return describe('when the player is ready', function() { + beforeEach(function() { + this.player.player.playVideo.andReturn(true); + return this.player.play(); + }); + return it('delegate to the Youtube player', function() { + return expect(this.player.player.playVideo).toHaveBeenCalled(); + }); + }); + }); + describe('isPlaying', function() { + beforeEach(function() { + jasmine.stubVideoPlayerAlpha(this, [], false); + $('.video').append($('
')); + return this.player = new VideoPlayerAlpha({ + video: this.video + }); + }); + describe('when the video is playing', function() { + beforeEach(function() { + return this.player.player.getPlayerState.andReturn(YT.PlayerState.PLAYING); + }); + return it('return true', function() { + return expect(this.player.isPlaying()).toBeTruthy(); + }); + }); + return describe('when the video is not playing', function() { + beforeEach(function() { + return this.player.player.getPlayerState.andReturn(YT.PlayerState.PAUSED); + }); + return it('return false', function() { + return expect(this.player.isPlaying()).toBeFalsy(); + }); + }); + }); + describe('pause', function() { + beforeEach(function() { + jasmine.stubVideoPlayerAlpha(this, [], false); + $('.video').append($('
')); + this.player = new VideoPlayerAlpha({ + video: this.video + }); + return this.player.pause(); + }); + return it('delegate to the Youtube player', function() { + return expect(this.player.player.pauseVideo).toHaveBeenCalled(); + }); + }); + describe('duration', function() { + beforeEach(function() { + jasmine.stubVideoPlayerAlpha(this, [], false); + $('.video').append($('
')); + this.player = new VideoPlayerAlpha({ + video: this.video + }); + spyOn(this.video, 'getDuration'); + return this.player.duration(); + }); + return it('delegate to the video', function() { + return expect(this.video.getDuration).toHaveBeenCalled(); + }); + }); + describe('currentSpeed', function() { + beforeEach(function() { + jasmine.stubVideoPlayerAlpha(this, [], false); + $('.video').append($('
')); + this.player = new VideoPlayerAlpha({ + video: this.video + }); + return this.video.speed = '3.0'; + }); + return it('delegate to the video', function() { + return expect(this.player.currentSpeed()).toEqual('3.0'); + }); + }); + return describe('volume', function() { + beforeEach(function() { + jasmine.stubVideoPlayerAlpha(this, [], false); + $('.video').append($('
')); + this.player = new VideoPlayerAlpha({ + video: this.video + }); + return this.player.player.getVolume.andReturn(42); + }); + describe('without value', function() { + return it('return current volume', function() { + return expect(this.player.volume()).toEqual(42); + }); + }); + return describe('with value', function() { + return it('set player volume', function() { + this.player.volume(60); + return expect(this.player.player.setVolume).toHaveBeenCalledWith(60); + }); + }); + }); + }); + +}).call(this); diff --git a/common/lib/xmodule/xmodule/js/spec/videoalpha/display/video_progress_slider_spec.coffee b/common/lib/xmodule/xmodule/js/spec/videoalpha/display/video_progress_slider_spec.coffee deleted file mode 100644 index dd787aefbb..0000000000 --- a/common/lib/xmodule/xmodule/js/spec/videoalpha/display/video_progress_slider_spec.coffee +++ /dev/null @@ -1,165 +0,0 @@ -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' diff --git a/common/lib/xmodule/xmodule/js/spec/videoalpha/display/video_progress_slider_spec.js b/common/lib/xmodule/xmodule/js/spec/videoalpha/display/video_progress_slider_spec.js new file mode 100644 index 0000000000..6b5b36635e --- /dev/null +++ b/common/lib/xmodule/xmodule/js/spec/videoalpha/display/video_progress_slider_spec.js @@ -0,0 +1,199 @@ +// 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); diff --git a/common/lib/xmodule/xmodule/js/spec/videoalpha/display/video_speed_control_spec.coffee b/common/lib/xmodule/xmodule/js/spec/videoalpha/display/video_speed_control_spec.coffee deleted file mode 100644 index ca4bfe815a..0000000000 --- a/common/lib/xmodule/xmodule/js/spec/videoalpha/display/video_speed_control_spec.coffee +++ /dev/null @@ -1,91 +0,0 @@ -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' diff --git a/common/lib/xmodule/xmodule/js/spec/videoalpha/display/video_speed_control_spec.js b/common/lib/xmodule/xmodule/js/spec/videoalpha/display/video_speed_control_spec.js new file mode 100644 index 0000000000..e31c31fcc8 --- /dev/null +++ b/common/lib/xmodule/xmodule/js/spec/videoalpha/display/video_speed_control_spec.js @@ -0,0 +1,131 @@ +// 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); diff --git a/common/lib/xmodule/xmodule/js/spec/videoalpha/display/video_volume_control_spec.coffee b/common/lib/xmodule/xmodule/js/spec/videoalpha/display/video_volume_control_spec.coffee deleted file mode 100644 index 4bb9f1cbf8..0000000000 --- a/common/lib/xmodule/xmodule/js/spec/videoalpha/display/video_volume_control_spec.coffee +++ /dev/null @@ -1,94 +0,0 @@ -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 """ -
- -
-
-
-
- """ - - 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 diff --git a/common/lib/xmodule/xmodule/js/spec/videoalpha/display/video_volume_control_spec.js b/common/lib/xmodule/xmodule/js/spec/videoalpha/display/video_volume_control_spec.js new file mode 100644 index 0000000000..dcc4485f2a --- /dev/null +++ b/common/lib/xmodule/xmodule/js/spec/videoalpha/display/video_volume_control_spec.js @@ -0,0 +1,113 @@ +(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("
\n \n
\n
\n
\n
"); + }); + 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); diff --git a/common/lib/xmodule/xmodule/js/spec/videoalpha/display_spec.coffee b/common/lib/xmodule/xmodule/js/spec/videoalpha/display_spec.coffee deleted file mode 100644 index 3715c3d813..0000000000 --- a/common/lib/xmodule/xmodule/js/spec/videoalpha/display_spec.coffee +++ /dev/null @@ -1,286 +0,0 @@ -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' diff --git a/common/lib/xmodule/xmodule/js/spec/videoalpha/display_spec.js b/common/lib/xmodule/xmodule/js/spec/videoalpha/display_spec.js new file mode 100644 index 0000000000..e0a8854f04 --- /dev/null +++ b/common/lib/xmodule/xmodule/js/spec/videoalpha/display_spec.js @@ -0,0 +1,392 @@ +(function () { + describe('VideoAlpha', function () { + var metadata; + metadata = { + slowerSpeedYoutubeId: { + id: this.slowerSpeedYoutubeId, + duration: 300 + }, + normalSpeedYoutubeId: { + id: this.normalSpeedYoutubeId, + duration: 200 + } + }; + + beforeEach(function () { + jasmine.stubRequests(); + window.onTouchBasedDevice = jasmine.createSpy('onTouchBasedDevice').andReturn(false); + this.videosDefinition = '0.75:slowerSpeedYoutubeId,1.0:normalSpeedYoutubeId'; + this.slowerSpeedYoutubeId = 'slowerSpeedYoutubeId'; + this.normalSpeedYoutubeId = 'normalSpeedYoutubeId'; + }); + + afterEach(function () { + window.OldVideoPlayerAlpha = void 0; + window.onYouTubePlayerAPIReady = void 0; + window.onHTML5PlayerAPIReady = void 0; + }); + + describe('constructor', function () { + describe('YT', function () { + beforeEach(function () { + loadFixtures('videoalpha.html'); + // TO DO??? this.stubbedVideoPlayer = jasmine.createSpy('StubbedVideoPlayer'); + $.cookie.andReturn('0.75'); + }); + + describe('by default', function () { + beforeEach(function () { + this.state = new window.VideoAlpha('#example'); + }); + + it('check videoType', function () { + expect(this.state.videoType).toEqual('youtube'); + }); + + it('reset the current video player', function () { + expect(window.OldVideoPlayerAlpha).toBeUndefined(); + }); + + it('set the elements', function () { + console.log('We are in this function.'); + + expect(this.state.el).toBe('#video_id'); + }); + + it('parse the videos', function () { + expect(this.state.videos).toEqual({ + '0.75': this.slowerSpeedYoutubeId, + '1.0': this.normalSpeedYoutubeId + }); + }); + + it('parse available video speeds', function () { + expect(this.state.speeds).toEqual(['0.75', '1.0']); + }); + + it('set current video speed via cookie', function () { + expect(this.state.speed).toEqual('0.75'); + }); + + //it('store a reference for this video player in the element', function () { + //expect($('.video').data('video')).toEqual(this.state); + //}); + }); + + /*describe('when the Youtube API is already available', function () { + beforeEach(function () { + this.originalYT = window.YT; + window.YT = { + Player: true + }; + this.state = new window.VideoAlpha('#example'); + }); + + afterEach(function () { + window.YT = this.originalYT; + }); + + it('create the Video Player', function () { + expect(window.VideoPlayerAlpha).toHaveBeenCalledWith({ + video: this.video + }); + expect(this.video.player).toEqual(this.stubbedVideoPlayer); + }); + }); + + describe('when the Youtube API is not ready', function () { + beforeEach(function () { + this.originalYT = window.YT; + window.YT = {}; + this.video = new VideoAlpha('#example'); + }); + + afterEach(function () { + window.YT = this.originalYT; + }); + + it('set the callback on the window object', function () { + expect(window.onYouTubePlayerAPIReady).toEqual(jasmine.any(Function)); + }); + }); + + describe('when the Youtube API becoming ready', function () { + beforeEach(function () { + this.originalYT = window.YT; + window.YT = {}; + spyOn(window, 'VideoPlayerAlpha').andReturn(this.stubVideoPlayerAlpha); + this.video = new VideoAlpha('#example'); + window.onYouTubePlayerAPIReady(); + }); + + afterEach(function () { + window.YT = this.originalYT; + }); + + it('create the Video Player for all video elements', function () { + expect(window.VideoPlayerAlpha).toHaveBeenCalledWith({ + video: this.video + }); + expect(this.video.player).toEqual(this.stubVideoPlayerAlpha); + }); + });*/ + }); + + describe('HTML5', function () { + var state; + + beforeEach(function () { + loadFixtures('videoalpha_html5.html'); + this.stubVideoPlayerAlpha = jasmine.createSpy('VideoPlayerAlpha'); + $.cookie.andReturn('0.75'); + }); + + describe('by default', function () { + beforeEach(function () { + state = new window.VideoAlpha('#example'); + }); + + afterEach(function () { + state = void 0; + }); + + it('check videoType', function () { + expect(state.videoType).toEqual('html5'); + }); + + it('reset the current video player', function () { + expect(window.OldVideoPlayerAlpha).toBeUndefined(); + }); + + it('set the elements', function () { + expect(state.el).toBe('#video_id'); + }); + + it('parse the videos if subtitles exist', function () { + var sub; + sub = 'test_name_of_the_subtitles'; + expect(state.videos).toEqual({ + '0.75': sub, + '1.0': sub, + '1.25': sub, + '1.5': sub + }); + }); + + it('parse the videos if subtitles do not exist', function () { + var sub; + $('#example').find('.videoalpha').data('sub', ''); + state = new window.VideoAlpha('#example'); + sub = ''; + expect(state.videos).toEqual({ + '0.75': sub, + '1.0': sub, + '1.25': sub, + '1.5': sub + }); + }); + + it('parse Html5 sources', function () { + var html5Sources; + html5Sources = { + mp4: 'test.mp4', + webm: 'test.webm', + ogg: 'test.ogv' + }; + expect(state.html5Sources).toEqual(html5Sources); + }); + + it('parse available video speeds', function () { + var speeds; + speeds = jasmine.stubbedHtml5Speeds; + expect(state.speeds).toEqual(speeds); + }); + + it('set current video speed via cookie', function () { + expect(state.speed).toEqual('0.75'); + }); + }); + + // Note that the loading of stand alone HTML5 player API is handled by + // Require JS. When state.videoPlayer is created, the stand alone HTML5 + // player object is already loaded, so no further testing in that case + // is required. + describe('HTML5 API is available', function () { + beforeEach(function () { + //TO DO??? spyOn(window, 'VideoAlpha').andReturn(jasmine.stubbedState); + state = new VideoAlpha('#example'); + }); + + afterEach(function () { + state = null; + }); + + it('create the Video Player', function () { + expect(state.videoPlayer.player).not.toBeUndefined(); + }); + }); + + /* NOT NECESSARY??? describe('when the HTML5 API is not ready', function () { + beforeEach(function () { + this.originalHTML5Video = window.HTML5Video; + window.HTML5Video = {}; + state = new VideoAlpha('#example'); + }); + + afterEach(function () { + window.HTML5Video = this.originalHTML5Video; + }); + + it('set the callback on the window object', function () { + expect(window.onHTML5PlayerAPIReady).toEqual(jasmine.any(Function)); + }); + }); + + describe('when the HTML5 API becoming ready', function () { + beforeEach(function () { + this.originalHTML5Video = window.HTML5Video; + window.HTML5Video = {}; + spyOn(window, 'VideoPlayerAlpha').andReturn(this.stubVideoPlayerAlpha); + state = new VideoAlpha('#example'); + window.onHTML5PlayerAPIReady(); + }); + + afterEach(function () { + window.HTML5Video = this.originalHTML5Video; + }); + + it('create the Video Player for all video elements', function () { + expect(window.VideoPlayerAlpha).toHaveBeenCalledWith({ + video: this.video + }); + expect(this.video.player).toEqual(this.stubVideoPlayerAlpha); + }); + });*/ + }); + }); + + describe('youtubeId', function () { + beforeEach(function () { + loadFixtures('videoalpha.html'); + $.cookie.andReturn('1.0'); + state = new VideoAlpha('#example'); + }); + + describe('with speed', function () { + it('return the video id for given speed', function () { + expect(state.youtubeId('0.75')).toEqual(this.slowerSpeedYoutubeId); + expect(state.youtubeId('1.0')).toEqual(this.normalSpeedYoutubeId); + }); + }); + + describe('without speed', function () { + it('return the video id for current speed', function () { + expect(state.youtubeId()).toEqual(this.normalSpeedYoutubeId); + }); + }); + }); + + describe('setSpeed', function () { + describe('YT', function () { + beforeEach(function () { + loadFixtures('videoalpha.html'); + state = new VideoAlpha('#example'); + }); + + describe('when new speed is available', function () { + beforeEach(function () { + state.setSpeed('0.75'); + }); + + it('set new speed', function () { + expect(state.speed).toEqual('0.75'); + }); + + it('save setting for new speed', function () { + expect($.cookie).toHaveBeenCalledWith('video_speed', '0.75', { + expires: 3650, + path: '/' + }); + }); + }); + + describe('when new speed is not available', function () { + beforeEach(function () { + state.setSpeed('1.75'); + }); + + it('set speed to 1.0x', function () { + expect(state.speed).toEqual('1.0'); + }); + }); + }); + + describe('HTML5', function () { + beforeEach(function () { + loadFixtures('videoalpha_html5.html'); + state = new VideoAlpha('#example'); + }); + + describe('when new speed is available', function () { + beforeEach(function () { + state.setSpeed('0.75'); + }); + + it('set new speed', function () { + expect(state.speed).toEqual('0.75'); + }); + + it('save setting for new speed', function () { + expect($.cookie).toHaveBeenCalledWith('video_speed', '0.75', { + expires: 3650, + path: '/' + }); + }); + }); + + describe('when new speed is not available', function () { + beforeEach(function () { + state.setSpeed('1.75'); + }); + + it('set speed to 1.0x', function () { + expect(state.speed).toEqual('1.0'); + }); + }); + }); + }); + + describe('getDuration', function () { + beforeEach(function () { + loadFixtures('videoalpha.html'); + state = new VideoAlpha('#example'); + }); + + it('return duration for current video', function () { + expect(state.getDuration()).toEqual(200); + }); + }); + + describe('log', function () { + beforeEach(function () { + //TO DO??? loadFixtures('videoalpha.html'); + loadFixtures('videoalpha_html5.html'); + state = new VideoAlpha('#example'); + spyOn(Logger, 'log'); + state.videoPlayer.log('someEvent', { + currentTime: 25, + speed: '1.0' + }); + }); + + it('call the logger with valid extra parameters', function () { + expect(Logger.log).toHaveBeenCalledWith('someEvent', { + id: 'id', + code: 'html5', + currentTime: 25, + speed: '1.0' + }); + }); + }); + }); +}).call(this); diff --git a/common/lib/xmodule/xmodule/js/src/videoalpha/display/.gitignore b/common/lib/xmodule/xmodule/js/src/videoalpha/display/.gitignore deleted file mode 100644 index c7a88ce092..0000000000 --- a/common/lib/xmodule/xmodule/js/src/videoalpha/display/.gitignore +++ /dev/null @@ -1 +0,0 @@ -!html5_video.js diff --git a/common/lib/xmodule/xmodule/js/src/videoalpha/helper_utils.js b/common/lib/xmodule/xmodule/js/src/videoalpha/helper_utils.js new file mode 100644 index 0000000000..451fc5e45d --- /dev/null +++ b/common/lib/xmodule/xmodule/js/src/videoalpha/helper_utils.js @@ -0,0 +1,74 @@ +// 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; + } +} diff --git a/common/lib/xmodule/xmodule/js/src/videoalpha/display/html5_video.js b/common/lib/xmodule/xmodule/js/src/videoalpha/html5_video.js similarity index 86% rename from common/lib/xmodule/xmodule/js/src/videoalpha/display/html5_video.js rename to common/lib/xmodule/xmodule/js/src/videoalpha/html5_video.js index 5372108aba..bf10f6586a 100644 --- a/common/lib/xmodule/xmodule/js/src/videoalpha/display/html5_video.js +++ b/common/lib/xmodule/xmodule/js/src/videoalpha/html5_video.js @@ -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. + // 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; } - - - - - if (typeof el === 'string') { - this.el = $(el); - // REFACTOR: Simplify chck. - 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. diff --git a/common/lib/xmodule/xmodule/js/src/videoalpha/display/initialize.js b/common/lib/xmodule/xmodule/js/src/videoalpha/initialize.js similarity index 85% rename from common/lib/xmodule/xmodule/js/src/videoalpha/display/initialize.js rename to common/lib/xmodule/xmodule/js/src/videoalpha/initialize.js index 049746b47e..e681db0696 100644 --- a/common/lib/xmodule/xmodule/js/src/videoalpha/display/initialize.js +++ b/common/lib/xmodule/xmodule/js/src/videoalpha/initialize.js @@ -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: '/' }); } } diff --git a/common/lib/xmodule/xmodule/js/src/videoalpha/main.js b/common/lib/xmodule/xmodule/js/src/videoalpha/main.js index ac29e5f16d..513e977ead 100644 --- a/common/lib/xmodule/xmodule/js/src/videoalpha/main.js +++ b/common/lib/xmodule/xmodule/js/src/videoalpha/main.js @@ -1,7 +1,5 @@ (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; }; }); diff --git a/common/lib/xmodule/xmodule/js/src/videoalpha/display/video_caption.js b/common/lib/xmodule/xmodule/js/src/videoalpha/video_caption.js similarity index 78% rename from common/lib/xmodule/xmodule/js/src/videoalpha/display/video_caption.js rename to common/lib/xmodule/xmodule/js/src/videoalpha/video_caption.js index 095dc784a0..9a1d33df22 100644 --- a/common/lib/xmodule/xmodule/js/src/videoalpha/display/video_caption.js +++ b/common/lib/xmodule/xmodule/js/src/videoalpha/video_caption.js @@ -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 = $('
    '); $.each(this.videoCaption.captions, function(index, text) { - // REFACTOR: Use .data() - container.append($('
  1. ').html(text).attr({ - 'data-index': index, - 'data-start': _this.videoCaption.start[index] - })); + container.append( + $('
  2. ').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( - $('
  3. ').height(this.videoCaption.topSpacingHeight()) - ) - .append( - $('
  4. ').height(this.videoCaption.bottomSpacingHeight()) - ); + .html(container.html()) + .find('li[data-index]').on('click', this.videoCaption.seekPlayer) + .prepend( + $('
  5. ').height(this.videoCaption.topSpacingHeight()) + ) + .append( + $('
  6. ').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(); diff --git a/common/lib/xmodule/xmodule/js/src/videoalpha/display/video_control.js b/common/lib/xmodule/xmodule/js/src/videoalpha/video_control.js similarity index 78% rename from common/lib/xmodule/xmodule/js/src/videoalpha/display/video_control.js rename to common/lib/xmodule/xmodule/js/src/videoalpha/video_control.js index cc2a85a803..008f7f8e70 100644 --- a/common/lib/xmodule/xmodule/js/src/videoalpha/display/video_control.js +++ b/common/lib/xmodule/xmodule/js/src/videoalpha/video_control.js @@ -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); } } diff --git a/common/lib/xmodule/xmodule/js/src/videoalpha/display/video_player.js b/common/lib/xmodule/xmodule/js/src/videoalpha/video_player.js similarity index 77% rename from common/lib/xmodule/xmodule/js/src/videoalpha/display/video_player.js rename to common/lib/xmodule/xmodule/js/src/videoalpha/video_player.js index f14769e0b4..43ba8de5c5 100644 --- a/common/lib/xmodule/xmodule/js/src/videoalpha/display/video_player.js +++ b/common/lib/xmodule/xmodule/js/src/videoalpha/video_player.js @@ -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 { diff --git a/common/lib/xmodule/xmodule/js/src/videoalpha/display/video_progress_slider.js b/common/lib/xmodule/xmodule/js/src/videoalpha/video_progress_slider.js similarity index 80% rename from common/lib/xmodule/xmodule/js/src/videoalpha/display/video_progress_slider.js rename to common/lib/xmodule/xmodule/js/src/videoalpha/video_progress_slider.js index 612beb9abf..d0cd2a8470 100644 --- a/common/lib/xmodule/xmodule/js/src/videoalpha/display/video_progress_slider.js +++ b/common/lib/xmodule/xmodule/js/src/videoalpha/video_progress_slider.js @@ -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); } } diff --git a/common/lib/xmodule/xmodule/js/src/videoalpha/display/video_quality_control.js b/common/lib/xmodule/xmodule/js/src/videoalpha/video_quality_control.js similarity index 82% rename from common/lib/xmodule/xmodule/js/src/videoalpha/display/video_quality_control.js rename to common/lib/xmodule/xmodule/js/src/videoalpha/video_quality_control.js index 1d559e45cd..67840e4902 100644 --- a/common/lib/xmodule/xmodule/js/src/videoalpha/display/video_quality_control.js +++ b/common/lib/xmodule/xmodule/js/src/videoalpha/video_quality_control.js @@ -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'; diff --git a/common/lib/xmodule/xmodule/js/src/videoalpha/display/video_speed_control.js b/common/lib/xmodule/xmodule/js/src/videoalpha/video_speed_control.js similarity index 77% rename from common/lib/xmodule/xmodule/js/src/videoalpha/display/video_speed_control.js rename to common/lib/xmodule/xmodule/js/src/videoalpha/video_speed_control.js index 2c8d8ca9b7..f78badf4ec 100644 --- a/common/lib/xmodule/xmodule/js/src/videoalpha/display/video_speed_control.js +++ b/common/lib/xmodule/xmodule/js/src/videoalpha/video_speed_control.js @@ -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 = $('').attr({ - 'href': '#' - }).html('' + speed + 'x'); + var link = $('' + speed + 'x'); - // REFACTOR: Use jQuery .data() - state.videoSpeedControl.videoSpeedsEl.prepend($('
  7. ').attr('data-speed', speed).html(link)); + state.videoSpeedControl.videoSpeedsEl.prepend($('
  8. ' + link + '
  9. ')); }); 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 = $('').attr({ - 'href': '#' - }).html('' + speed + 'x'); + link = $('' + speed + 'x'); - listItem = $('
  10. ').attr('data-speed', speed).html(link); + listItem = $('
  11. ' + link + '
  12. '); if (speed === params.currentSpeed) { listItem.addClass('active'); diff --git a/common/lib/xmodule/xmodule/js/src/videoalpha/display/video_volume_control.js b/common/lib/xmodule/xmodule/js/src/videoalpha/video_volume_control.js similarity index 82% rename from common/lib/xmodule/xmodule/js/src/videoalpha/display/video_volume_control.js rename to common/lib/xmodule/xmodule/js/src/videoalpha/video_volume_control.js index 3d979a0d1a..c3892cbaae 100644 --- a/common/lib/xmodule/xmodule/js/src/videoalpha/display/video_volume_control.js +++ b/common/lib/xmodule/xmodule/js/src/videoalpha/video_volume_control.js @@ -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); diff --git a/common/lib/xmodule/xmodule/tests/__init__.py b/common/lib/xmodule/xmodule/tests/__init__.py index d310110844..6565e49fbb 100644 --- a/common/lib/xmodule/xmodule/tests/__init__.py +++ b/common/lib/xmodule/xmodule/tests/__init__.py @@ -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)) diff --git a/common/lib/xmodule/xmodule/tests/test_conditional_logic.py b/common/lib/xmodule/xmodule/tests/test_conditional_logic.py new file mode 100644 index 0000000000..85428fc125 --- /dev/null +++ b/common/lib/xmodule/xmodule/tests/test_conditional_logic.py @@ -0,0 +1,20 @@ +# -*- 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, []) diff --git a/common/lib/xmodule/xmodule/tests/test_logic.py b/common/lib/xmodule/xmodule/tests/test_logic.py deleted file mode 100644 index 5fe7aa2832..0000000000 --- a/common/lib/xmodule/xmodule/tests/test_logic.py +++ /dev/null @@ -1,127 +0,0 @@ -# -*- 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'])) diff --git a/common/lib/xmodule/xmodule/tests/test_poll.py b/common/lib/xmodule/xmodule/tests/test_poll.py new file mode 100644 index 0000000000..6e421c7cb7 --- /dev/null +++ b/common/lib/xmodule/xmodule/tests/test_poll.py @@ -0,0 +1,31 @@ +# -*- 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') diff --git a/common/lib/xmodule/xmodule/tests/test_video_xml.py b/common/lib/xmodule/xmodule/tests/test_video_xml.py index 081870792c..1ccc633ee2 100644 --- a/common/lib/xmodule/xmodule/tests/test_video_xml.py +++ b/common/lib/xmodule/xmodule/tests/test_video_xml.py @@ -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): diff --git a/common/lib/xmodule/xmodule/tests/test_videoalpha.py b/common/lib/xmodule/xmodule/tests/test_videoalpha.py new file mode 100644 index 0000000000..909b273104 --- /dev/null +++ b/common/lib/xmodule/xmodule/tests/test_videoalpha.py @@ -0,0 +1,50 @@ +# -*- 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': '' + } + + def test_get_timeframe_no_parameters(self): + "Make sure that timeframe() works correctly w/o parameters" + xmltree = etree.fromstring('test') + 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( + 'test' + ) + 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( + '''test''' + ) + output = self.xmodule.get_timeframe(xmltree) + self.assertEqual(output, (247, 47079)) diff --git a/common/lib/xmodule/xmodule/tests/test_word_cloud.py b/common/lib/xmodule/xmodule/tests/test_word_cloud.py new file mode 100644 index 0000000000..87992e4d5f --- /dev/null +++ b/common/lib/xmodule/xmodule/tests/test_word_cloud.py @@ -0,0 +1,46 @@ +# -*- 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']) ) + diff --git a/common/lib/xmodule/xmodule/videoalpha_module.py b/common/lib/xmodule/xmodule/videoalpha_module.py index a0a8f395ee..fe7ef5ba43 100644 --- a/common/lib/xmodule/xmodule/videoalpha_module.py +++ b/common/lib/xmodule/xmodule/videoalpha_module.py @@ -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')) diff --git a/lms/djangoapps/courseware/features/video.feature b/lms/djangoapps/courseware/features/video.feature index 2b8d0f013a..7ba60c4f92 100644 --- a/lms/djangoapps/courseware/features/video.feature +++ b/lms/djangoapps/courseware/features/video.feature @@ -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 diff --git a/lms/djangoapps/courseware/features/video.py b/lms/djangoapps/courseware/features/video.py index f95ffd9917..2e6665f6e8 100644 --- a/lms/djangoapps/courseware/features/video.py +++ b/lms/djangoapps/courseware/features/video.py @@ -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' diff --git a/lms/djangoapps/courseware/tests/test_videoalpha_xml.py b/lms/djangoapps/courseware/tests/test_videoalpha_xml.py index 6df957a0e5..eaa5d1f502 100644 --- a/lms/djangoapps/courseware/tests/test_videoalpha_xml.py +++ b/lms/djangoapps/courseware/tests/test_videoalpha_xml.py @@ -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 = """ diff --git a/lms/envs/common.py b/lms/envs/common.py index 4f796d8534..6c64cf1d90 100644 --- a/lms/envs/common.py +++ b/lms/envs/common.py @@ -35,7 +35,7 @@ from .discussionsettings import * PLATFORM_NAME = "edX" COURSEWARE_ENABLED = True -ENABLE_JASMINE = True +ENABLE_JASMINE = False PERFSTATS = False