diff --git a/cms/djangoapps/contentstore/features/transcripts.py b/cms/djangoapps/contentstore/features/transcripts.py index 9542e22104..00f898fbb6 100644 --- a/cms/djangoapps/contentstore/features/transcripts.py +++ b/cms/djangoapps/contentstore/features/transcripts.py @@ -201,7 +201,9 @@ def upload_file(_step, file_name): @step('I see "([^"]*)" text in the captions') def check_text_in_the_captions(_step, text): - assert world.browser.is_text_present(text.strip(), 5) + world.wait_for(lambda _: world.css_text('.subtitles')) + actual_text = world.css_text('.subtitles') + assert (text in actual_text) @step('I see value "([^"]*)" in the field "([^"]*)"$') diff --git a/common/lib/xmodule/xmodule/js/spec/video/async_process_spec.js b/common/lib/xmodule/xmodule/js/spec/video/async_process_spec.js new file mode 100644 index 0000000000..c26ec7c8e7 --- /dev/null +++ b/common/lib/xmodule/xmodule/js/spec/video/async_process_spec.js @@ -0,0 +1,97 @@ +(function (require) { +require( +['video/00_async_process.js'], +function (AsyncProcess) { + var getArrayNthLength = function (n, multiplier) { + var result = [], + mul = multiplier || 1; + + for (var i = 0; i < n; i++) { + result[i] = i * mul; + } + + return result; + }, + items = getArrayNthLength(1000); + + describe('AsyncProcess', function () { + it ('Array is processed successfully', function () { + var processedArray, + expectedArray = getArrayNthLength(1000, 2), + process = function (item) { + return 2 * item; + }; + + runs(function () { + AsyncProcess.array(items, process).done(function (result) { + processedArray = result; + }); + }); + + waitsFor(function () { + return processedArray; + }, 'Array processing takes too much time', WAIT_TIMEOUT); + + runs(function () { + expect(processedArray).toEqual(expectedArray); + }); + }); + + it ('If non-array is passed, error callback is called', function () { + var isError, + process = function () {}; + + runs(function () { + AsyncProcess.array('string', process).fail(function () { + isError = true; + }); + }); + + waitsFor(function () { + return isError; + }, 'Error callback wasn\'t called', WAIT_TIMEOUT); + + runs(function () { + expect(isError).toBeTruthy(); + }); + }); + + it ('If an empty array is passed, returns initial array', function () { + var processedArray, + process = function () {}; + + runs(function () { + AsyncProcess.array([], process).done(function (result) { + processedArray = result; + }); + }); + + waitsFor(function () { + return processedArray; + }, 'Array processing takes too much time', WAIT_TIMEOUT); + + runs(function () { + expect(processedArray).toEqual([]); + }); + }); + + it ('If no process function passed, returns initial array', function () { + var processedArray; + + runs(function () { + AsyncProcess.array(items).done(function (result) { + processedArray = result; + }); + }); + + waitsFor(function () { + return processedArray; + }, 'Array processing takes too much time', WAIT_TIMEOUT); + + runs(function () { + expect(processedArray).toEqual(items); + }); + }); + }); +}); +}(RequireJS.require)); diff --git a/common/lib/xmodule/xmodule/js/spec/video/sjson_spec.js b/common/lib/xmodule/xmodule/js/spec/video/sjson_spec.js new file mode 100644 index 0000000000..b827246104 --- /dev/null +++ b/common/lib/xmodule/xmodule/js/spec/video/sjson_spec.js @@ -0,0 +1,35 @@ +(function (require) { +require( +['video/00_sjson.js'], +function (Sjson) { + describe('Sjson', function () { + var data = jasmine.stubbedCaption, + sjson; + + beforeEach(function() { + sjson = new Sjson(data); + }); + + it ('returns captions', function () { + expect(sjson.getCaptions()).toEqual(data.text); + }); + + it ('returns start times', function () { + expect(sjson.getStartTimes()).toEqual(data.start); + }); + + it ('returns correct length', function () { + expect(sjson.getSize()).toEqual(data.text.length); + }); + + it('search returns a correct caption index', function () { + expect(sjson.search(0)).toEqual(-1); + expect(sjson.search(3120)).toEqual(1); + expect(sjson.search(6270)).toEqual(2); + expect(sjson.search(8490)).toEqual(2); + expect(sjson.search(21620)).toEqual(4); + expect(sjson.search(24920)).toEqual(5); + }); + }); +}); +}(RequireJS.require)); diff --git a/common/lib/xmodule/xmodule/js/spec/video/video_caption_spec.js b/common/lib/xmodule/xmodule/js/spec/video/video_caption_spec.js index 2f159bc023..34ae9e9b66 100644 --- a/common/lib/xmodule/xmodule/js/spec/video/video_caption_spec.js +++ b/common/lib/xmodule/xmodule/js/spec/video/video_caption_spec.js @@ -92,7 +92,7 @@ }); expect($.ajaxWithPrefix.mostRecentCall.args[0].data) .toEqual({ - videoId: 'abcdefghijkl' + videoId: 'cogebirgzzM' }); }); }); @@ -237,73 +237,94 @@ describe('when on a non touch-based device', function () { beforeEach(function () { - state = jasmine.initializePlayer(); + runs(function () { + state = jasmine.initializePlayer(); + }); + + waitsFor(function () { + return state.videoCaption.rendered; + }, 'Captions are not rendered', WAIT_TIMEOUT); }); it('render the caption', function () { - var captionsData; + runs(function () { + var captionsData; - captionsData = jasmine.stubbedCaption; - $('.subtitles li[data-index]').each( - function (index, link) { + captionsData = jasmine.stubbedCaption; + $('.subtitles li[data-index]').each( + function (index, link) { - expect($(link)).toHaveData('index', index); - expect($(link)).toHaveData( - 'start', captionsData.start[index] - ); - expect($(link)).toHaveAttr('tabindex', 0); - expect($(link)).toHaveText(captionsData.text[index]); + expect($(link)).toHaveData('index', index); + expect($(link)).toHaveData( + 'start', captionsData.start[index] + ); + expect($(link)).toHaveAttr('tabindex', 0); + expect($(link)).toHaveText(captionsData.text[index]); + }); }); }); it('add a padding element to caption', function () { - expect($('.subtitles li:first').hasClass('spacing')) - .toBe(true); - expect($('.subtitles li:last').hasClass('spacing')) - .toBe(true); + runs(function () { + expect($('.subtitles li:first').hasClass('spacing')) + .toBe(true); + expect($('.subtitles li:last').hasClass('spacing')) + .toBe(true); + }); }); it('bind all the caption link', function () { - var handlerList = ['captionMouseOverOut', 'captionClick', - 'captionMouseDown', 'captionFocus', 'captionBlur', - 'captionKeyDown' - ]; + runs(function () { + var handlerList = ['captionMouseOverOut', 'captionClick', + 'captionMouseDown', 'captionFocus', 'captionBlur', + 'captionKeyDown' + ]; - $.each(handlerList, function(index, handler) { - spyOn(state.videoCaption, handler); - }); - $('.subtitles li[data-index]').each( - function (index, link) { + $.each(handlerList, function(index, handler) { + spyOn(state.videoCaption, handler); + }); + $('.subtitles li[data-index]').each( + function (index, link) { - $(link).trigger('mouseover'); - expect(state.videoCaption.captionMouseOverOut).toHaveBeenCalled(); + $(link).trigger('mouseover'); + expect(state.videoCaption.captionMouseOverOut).toHaveBeenCalled(); - state.videoCaption.captionMouseOverOut.reset(); - $(link).trigger('mouseout'); - expect(state.videoCaption.captionMouseOverOut).toHaveBeenCalled(); + state.videoCaption.captionMouseOverOut.reset(); + $(link).trigger('mouseout'); + expect(state.videoCaption.captionMouseOverOut).toHaveBeenCalled(); - $(this).click(); - expect(state.videoCaption.captionClick).toHaveBeenCalled(); + $(this).click(); + expect(state.videoCaption.captionClick).toHaveBeenCalled(); - $(this).trigger('mousedown'); - expect(state.videoCaption.captionMouseDown).toHaveBeenCalled(); + $(this).trigger('mousedown'); + expect(state.videoCaption.captionMouseDown).toHaveBeenCalled(); - $(this).trigger('focus'); - expect(state.videoCaption.captionFocus).toHaveBeenCalled(); + $(this).trigger('focus'); + expect(state.videoCaption.captionFocus).toHaveBeenCalled(); - $(this).trigger('blur'); - expect(state.videoCaption.captionBlur).toHaveBeenCalled(); + $(this).trigger('blur'); + expect(state.videoCaption.captionBlur).toHaveBeenCalled(); - $(this).trigger('keydown'); - expect(state.videoCaption.captionKeyDown).toHaveBeenCalled(); + $(this).trigger('keydown'); + expect(state.videoCaption.captionKeyDown).toHaveBeenCalled(); + }); }); }); it('set rendered to true', function () { - state = jasmine.initializePlayer(); - expect(state.videoCaption.rendered).toBeTruthy(); + runs(function () { + state = jasmine.initializePlayer(); + }); + + waitsFor(function () { + return state.videoCaption.rendered; + }, 'Captions are not rendered', WAIT_TIMEOUT); + + runs(function () { + expect(state.videoCaption.rendered).toBeTruthy(); + }); }); }); @@ -345,49 +366,58 @@ beforeEach(function () { jasmine.Clock.useMock(); spyOn(window, 'clearTimeout'); - state = jasmine.initializePlayer(); + + runs(function () { + state = jasmine.initializePlayer(); + jasmine.Clock.tick(50); + }); + + waitsFor(function () { + return state.videoCaption.rendered; + }, 'Captions are not rendered', WAIT_TIMEOUT); }); describe('when cursor is outside of the caption box', function () { - beforeEach(function () { - $(window).trigger(jQuery.Event('mousemove')); - jasmine.Clock.tick(state.config.captionsFreezeTime); - }); - it('does not set freezing timeout', function () { - expect(state.videoCaption.frozen).toBeFalsy(); + runs(function () { + expect(state.videoCaption.frozen).toBeFalsy(); + }); }); }); describe('when cursor is in the caption box', function () { beforeEach(function () { spyOn(state.videoCaption, 'onMouseLeave'); - $('.subtitles').trigger(jQuery.Event('mouseenter')); - jasmine.Clock.tick(state.config.captionsFreezeTime); + runs(function () { + $(window).trigger(jQuery.Event('mousemove')); + jasmine.Clock.tick(state.config.captionsFreezeTime); + $('.subtitles').trigger(jQuery.Event('mouseenter')); + jasmine.Clock.tick(state.config.captionsFreezeTime); + }); }); it('set the freezing timeout', function () { - expect(state.videoCaption.frozen).not.toBeFalsy(); - expect(state.videoCaption.onMouseLeave).toHaveBeenCalled(); + runs(function () { + expect(state.videoCaption.frozen).not.toBeFalsy(); + expect(state.videoCaption.onMouseLeave).toHaveBeenCalled(); + }); }); describe('when the cursor is moving', function () { - beforeEach(function () { - $('.subtitles').trigger(jQuery.Event('mousemove')); - }); - it('reset the freezing timeout', function () { - expect(window.clearTimeout).toHaveBeenCalled(); + runs(function () { + $('.subtitles').trigger(jQuery.Event('mousemove')); + expect(window.clearTimeout).toHaveBeenCalled(); + }); }); }); describe('when the mouse is scrolling', function () { - beforeEach(function () { - $('.subtitles').trigger(jQuery.Event('mousewheel')); - }); - it('reset the freezing timeout', function () { - expect(window.clearTimeout).toHaveBeenCalled(); + runs(function () { + $('.subtitles').trigger(jQuery.Event('mousewheel')); + expect(window.clearTimeout).toHaveBeenCalled(); + }); }); }); }); @@ -441,25 +471,6 @@ }); }); - it('reRenderCaption', function () { - state = jasmine.initializePlayer(); - - var Caption = state.videoCaption, - li; - - Caption.captions = ['test']; - Caption.start = [500]; - - spyOn(Caption, 'addPaddings'); - - Caption.reRenderCaption(); - li = $('ol.subtitles li'); - - expect(Caption.addPaddings).toHaveBeenCalled(); - expect(li.length).toBe(1); - expect(li).toHaveData('start', '500'); - }); - describe('fetchCaption', function () { var Caption, msg; @@ -467,7 +478,6 @@ state = jasmine.initializePlayer(); Caption = state.videoCaption; spyOn($, 'ajaxWithPrefix').andCallThrough(); - spyOn(Caption, 'reRenderCaption'); spyOn(Caption, 'renderCaption'); spyOn(Caption, 'bindHandlers'); spyOn(Caption, 'updatePlayTime'); @@ -511,7 +521,6 @@ expect(Caption.bindHandlers).toHaveBeenCalled(); expect(Caption.renderCaption).not.toHaveBeenCalled(); expect(Caption.updatePlayTime).not.toHaveBeenCalled(); - expect(Caption.reRenderCaption).not.toHaveBeenCalled(); expect(Caption.loaded).toBeTruthy(); }); @@ -527,7 +536,6 @@ expect(Caption.bindHandlers).not.toHaveBeenCalled(); expect(Caption.renderCaption).not.toHaveBeenCalled(); expect(Caption.updatePlayTime).not.toHaveBeenCalled(); - expect(Caption.reRenderCaption).not.toHaveBeenCalled(); expect(Caption.loaded).toBeTruthy(); }); @@ -539,9 +547,8 @@ expect($.ajaxWithPrefix).toHaveBeenCalled(); expect(Caption.bindHandlers).not.toHaveBeenCalled(); - expect(Caption.renderCaption).not.toHaveBeenCalled(); + expect(Caption.renderCaption).toHaveBeenCalled(); expect(Caption.updatePlayTime).toHaveBeenCalled(); - expect(Caption.reRenderCaption).toHaveBeenCalled(); expect(Caption.loaded).toBeTruthy(); }); @@ -553,19 +560,18 @@ expect(Caption.bindHandlers).toHaveBeenCalled(); expect(Caption.renderCaption).toHaveBeenCalled(); expect(Caption.updatePlayTime).not.toHaveBeenCalled(); - expect(Caption.reRenderCaption).not.toHaveBeenCalled(); expect(Caption.loaded).toBeTruthy(); }); it('on success: re-rendered correct', function () { Caption.loaded = true; + Caption.rendered = true; Caption.fetchCaption(); expect($.ajaxWithPrefix).toHaveBeenCalled(); expect(Caption.bindHandlers).not.toHaveBeenCalled(); - expect(Caption.renderCaption).not.toHaveBeenCalled(); + expect(Caption.renderCaption).toHaveBeenCalled(); expect(Caption.updatePlayTime).toHaveBeenCalled(); - expect(Caption.reRenderCaption).toHaveBeenCalled(); expect(Caption.loaded).toBeTruthy(); }); @@ -680,53 +686,57 @@ }); }); - describe('search', function () { - it('return a correct caption index', function () { - state = jasmine.initializePlayer(); - expect(state.videoCaption.search(0)).toEqual(-1); - expect(state.videoCaption.search(3120)).toEqual(1); - expect(state.videoCaption.search(6270)).toEqual(2); - expect(state.videoCaption.search(8490)).toEqual(2); - expect(state.videoCaption.search(21620)).toEqual(4); - expect(state.videoCaption.search(24920)).toEqual(5); - }); - }); - describe('play', function () { describe('when the caption was not rendered', function () { beforeEach(function () { window.onTouchBasedDevice.andReturn(['iPad']); - state = jasmine.initializePlayer(); - state.videoCaption.play(); + + runs(function () { + state = jasmine.initializePlayer(); + state.videoCaption.play(); + }); + + waitsFor(function () { + return state.videoCaption.rendered; + }, 'Captions are not rendered', WAIT_TIMEOUT); }); it('render the caption', function () { - var captionsData; + runs(function () { + var captionsData; - captionsData = jasmine.stubbedCaption; - $('.subtitles li[data-index]').each( - function (index, link) { + captionsData = jasmine.stubbedCaption; + $('.subtitles li[data-index]').each( + function (index, link) { - expect($(link)).toHaveData('index', index); - expect($(link)).toHaveData( - 'start', captionsData.start[index] - ); - expect($(link)).toHaveAttr('tabindex', 0); - expect($(link)).toHaveText(captionsData.text[index]); + expect($(link)).toHaveData('index', index); + expect($(link)).toHaveData( + 'start', captionsData.start[index] + ); + expect($(link)).toHaveAttr('tabindex', 0); + expect($(link)).toHaveText(captionsData.text[index]); + }); }); + }); it('add a padding element to caption', function () { - expect($('.subtitles li:first')).toBe('.spacing'); - expect($('.subtitles li:last')).toBe('.spacing'); + runs(function () { + expect($('.subtitles li:first')).toBe('.spacing'); + expect($('.subtitles li:last')).toBe('.spacing'); + }); }); it('set rendered to true', function () { - expect(state.videoCaption.rendered).toBeTruthy(); + runs(function () { + expect(state.videoCaption.rendered).toBeTruthy(); + }); }); it('set playing to true', function () { - expect(state.videoCaption.playing).toBeTruthy(); + runs(function () { + expect(state.videoCaption.playing).toBeTruthy(); + }); }); }); }); @@ -745,219 +755,269 @@ describe('updatePlayTime', function () { beforeEach(function () { - state = jasmine.initializePlayer(); + runs(function () { + state = jasmine.initializePlayer(); + }); + + waitsFor(function () { + return state.videoCaption.rendered; + }, 'Captions are not rendered', WAIT_TIMEOUT); }); describe('when the video speed is 1.0x', function () { - beforeEach(function () { - state.videoSpeedControl.currentSpeed = '1.0'; - state.videoCaption.updatePlayTime(25.000); - }); - it('search the caption based on time', function () { - expect(state.videoCaption.currentIndex).toEqual(5); + runs(function () { + state.videoCaption.updatePlayTime(25.000); + expect(state.videoCaption.currentIndex).toEqual(5); + + // Flash mode + spyOn(state, 'isFlashMode').andReturn(true); + state.speed = '1.0'; + state.videoCaption.updatePlayTime(25.000); + expect(state.videoCaption.currentIndex).toEqual(5); + }); }); }); describe('when the video speed is not 1.0x', function () { - beforeEach(function () { - state.videoSpeedControl.currentSpeed = '0.75'; - state.videoCaption.updatePlayTime(25.000); - }); - it('search the caption based on 1.0x speed', function () { - expect(state.videoCaption.currentIndex).toEqual(5); + runs(function () { + state.videoCaption.updatePlayTime(25.000); + expect(state.videoCaption.currentIndex).toEqual(5); + + // Flash mode + state.speed = '2.0'; + spyOn(state, 'isFlashMode').andReturn(true); + state.videoCaption.updatePlayTime(25.000); + expect(state.videoCaption.currentIndex).toEqual(9); + state.speed = '0.75'; + state.videoCaption.updatePlayTime(25.000); + expect(state.videoCaption.currentIndex).toEqual(3); + }); }); }); describe('when the index is not the same', function () { beforeEach(function () { - state.videoCaption.currentIndex = 1; - $('.subtitles li[data-index=5]').addClass('current'); - state.videoCaption.updatePlayTime(25.000); + runs(function () { + state.videoCaption.currentIndex = 1; + $('.subtitles li[data-index=5]').addClass('current'); + state.videoCaption.updatePlayTime(25.000); + }); }); it('deactivate the previous caption', function () { - expect($('.subtitles li[data-index=1]')) - .not.toHaveClass('current'); + runs(function () { + expect($('.subtitles li[data-index=1]')) + .not.toHaveClass('current'); + }); }); it('activate new caption', function () { - expect($('.subtitles li[data-index=5]')) - .toHaveClass('current'); + runs(function () { + expect($('.subtitles li[data-index=5]')) + .toHaveClass('current'); + }); }); it('save new index', function () { - expect(state.videoCaption.currentIndex).toEqual(5); + runs(function () { + expect(state.videoCaption.currentIndex).toEqual(5); + }); }); - // Disabled 11/25/13 due to flakiness in master - xit('scroll caption to new position', function () { - expect($.fn.scrollTo).toHaveBeenCalled(); + it('scroll caption to new position', function () { + runs(function () { + expect($.fn.scrollTo).toHaveBeenCalled(); + }); }); }); describe('when the index is the same', function () { - beforeEach(function () { - state.videoCaption.currentIndex = 1; - $('.subtitles li[data-index=3]').addClass('current'); - state.videoCaption.updatePlayTime(15.000); - }); - it('does not change current subtitle', function () { - expect($('.subtitles li[data-index=3]')) - .toHaveClass('current'); + runs(function () { + state.videoCaption.currentIndex = 1; + $('.subtitles li[data-index=3]').addClass('current'); + state.videoCaption.updatePlayTime(15.000); + expect($('.subtitles li[data-index=3]')) + .toHaveClass('current'); + }); }); }); }); describe('resize', function () { beforeEach(function () { - state = jasmine.initializePlayer(); - videoControl = state.videoControl; - $('.subtitles li[data-index=1]').addClass('current'); - state.videoCaption.resize(); + runs(function () { + state = jasmine.initializePlayer(); + }); + + waitsFor(function () { + return state.videoCaption.rendered; + }, 'Captions are not rendered', WAIT_TIMEOUT); + + runs(function () { + videoControl = state.videoControl; + $('.subtitles li[data-index=1]').addClass('current'); + state.videoCaption.resize(); + }); }); describe('set the height of caption container', function () { it('when CC button is enabled', function () { - var realHeight = parseInt( - $('.subtitles').css('maxHeight'), 10 - ), - shouldBeHeight = $('.video-wrapper').height(); + runs(function () { + var realHeight = parseInt( + $('.subtitles').css('maxHeight'), 10 + ), + shouldBeHeight = $('.video-wrapper').height(); - // Because of some problems with rounding on different - // environments: Linux * Mac * FF * Chrome - expect(realHeight).toBeCloseTo(shouldBeHeight, 2); + // Because of some problems with rounding on different + // environments: Linux * Mac * FF * Chrome + expect(realHeight).toBeCloseTo(shouldBeHeight, 2); + }); }); it('when CC button is disabled ', function () { - var realHeight, videoWrapperHeight, progressSliderHeight, - controlHeight, shouldBeHeight; + runs(function () { + var realHeight, videoWrapperHeight, progressSliderHeight, + controlHeight, shouldBeHeight; - state.captionsHidden = true; - state.videoCaption.setSubtitlesHeight(); + state.captionsHidden = true; + state.videoCaption.setSubtitlesHeight(); - realHeight = parseInt( - $('.subtitles').css('maxHeight'), 10 - ); - videoWrapperHeight = $('.video-wrapper').height(); - progressSliderHeight = videoControl.sliderEl.height(); - controlHeight = videoControl.el.height(); - shouldBeHeight = videoWrapperHeight - - 0.5 * progressSliderHeight - - controlHeight; + realHeight = parseInt( + $('.subtitles').css('maxHeight'), 10 + ); + videoWrapperHeight = $('.video-wrapper').height(); + progressSliderHeight = videoControl.sliderEl.height(); + controlHeight = videoControl.el.height(); + shouldBeHeight = videoWrapperHeight - + 0.5 * progressSliderHeight - + controlHeight; - expect(realHeight).toBe(shouldBeHeight); + expect(realHeight).toBe(shouldBeHeight); + }); }); }); it('set the height of caption spacing', function () { - var firstSpacing, lastSpacing; + runs(function () { + var firstSpacing, lastSpacing; - firstSpacing = Math.abs(parseInt( - $('.subtitles .spacing:first').css('height'), 10 - )); - lastSpacing = Math.abs(parseInt( - $('.subtitles .spacing:last').css('height'), 10 - )); + firstSpacing = Math.abs(parseInt( + $('.subtitles .spacing:first').css('height'), 10 + )); + lastSpacing = Math.abs(parseInt( + $('.subtitles .spacing:last').css('height'), 10 + )); - expect(firstSpacing - state.videoCaption.topSpacingHeight()) - .toBeLessThan(1); - expect(lastSpacing - state.videoCaption.bottomSpacingHeight()) - .toBeLessThan(1); + expect(firstSpacing - state.videoCaption.topSpacingHeight()) + .toBeLessThan(1); + expect(lastSpacing - state.videoCaption.bottomSpacingHeight()) + .toBeLessThan(1); + }); }); it('scroll caption to new position', function () { - expect($.fn.scrollTo).toHaveBeenCalled(); + runs(function () { + expect($.fn.scrollTo).toHaveBeenCalled(); + }); }); }); - // Disabled 11/25/13 due to flakiness in master xdescribe('scrollCaption', function () { beforeEach(function () { - state = jasmine.initializePlayer(); + runs(function () { + state = jasmine.initializePlayer(); + }); + + waitsFor(function () { + return state.videoCaption.rendered; + }, 'Captions are not rendered', WAIT_TIMEOUT); }); describe('when frozen', function () { - beforeEach(function () { - state.videoCaption.frozen = true; - $('.subtitles li[data-index=1]').addClass('current'); - state.videoCaption.scrollCaption(); - }); - it('does not scroll the caption', function () { - expect($.fn.scrollTo).not.toHaveBeenCalled(); + runs(function () { + state.videoCaption.frozen = true; + $('.subtitles li[data-index=1]').addClass('current'); + state.videoCaption.scrollCaption(); + expect($.fn.scrollTo).not.toHaveBeenCalled(); + }); }); }); describe('when not frozen', function () { beforeEach(function () { - state.videoCaption.frozen = false; + runs(function () { + state.videoCaption.frozen = false; + }); }); describe('when there is no current caption', function () { - beforeEach(function () { - state.videoCaption.scrollCaption(); - }); - it('does not scroll the caption', function () { - expect($.fn.scrollTo).not.toHaveBeenCalled(); + runs(function () { + state.videoCaption.scrollCaption(); + expect($.fn.scrollTo).not.toHaveBeenCalled(); + }); }); }); describe('when there is a current caption', function () { - beforeEach(function () { - $('.subtitles li[data-index=1]').addClass('current'); - state.videoCaption.scrollCaption(); - }); - it('scroll to current caption', function () { - expect($.fn.scrollTo).toHaveBeenCalled(); + runs(function () { + $('.subtitles li[data-index=1]').addClass('current'); + state.videoCaption.scrollCaption(); + expect($.fn.scrollTo).toHaveBeenCalled(); + }); }); }); }); }); - // Disabled 10/9/13 due to flakiness in master xdescribe('seekPlayer', function () { beforeEach(function () { - state = jasmine.initializePlayer(); + runs(function () { + state = jasmine.initializePlayer(); + }); + + waitsFor(function () { + var duration = state.videoPlayer.duration(), + isRendered = state.videoCaption.rendered; + + return isRendered && duration; + }, 'Captions are not rendered', WAIT_TIMEOUT); }); describe('when the video speed is 1.0x', function () { - beforeEach(function () { - state.videoSpeedControl.currentSpeed = '1.0'; - $('.subtitles li[data-start="14910"]').trigger('click'); - }); - it('trigger seek event with the correct time', function () { - expect(state.videoPlayer.currentTime).toEqual(14.91); + runs(function () { + state.videoSpeedControl.currentSpeed = '1.0'; + $('.subtitles li[data-start="14910"]').trigger('click'); + expect(state.videoPlayer.currentTime).toEqual(14.91); + }); }); }); describe('when the video speed is not 1.0x', function () { - beforeEach(function () { - state.videoSpeedControl.currentSpeed = '0.75'; - $('.subtitles li[data-start="14910"]').trigger('click'); - }); - it('trigger seek event with the correct time', function () { - expect(state.videoPlayer.currentTime).toEqual(14.91); + runs(function () { + state.videoSpeedControl.currentSpeed = '0.75'; + $('.subtitles li[data-start="14910"]').trigger('click'); + expect(state.videoPlayer.currentTime).toEqual(14.91); + }); }); }); describe('when the player type is Flash at speed 0.75x', function () { - - beforeEach(function () { - state.videoSpeedControl.currentSpeed = '0.75'; - state.currentPlayerMode = 'flash'; - $('.subtitles li[data-start="14910"]').trigger('click'); - }); - it('trigger seek event with the correct time', function () { - expect(state.videoPlayer.currentTime).toEqual(15); + runs(function () { + state.videoSpeedControl.currentSpeed = '0.75'; + state.currentPlayerMode = 'flash'; + $('.subtitles li[data-start="14910"]').trigger('click'); + expect(state.videoPlayer.currentTime).toEqual(15); + }); }); }); }); @@ -998,7 +1058,6 @@ beforeEach(function () { state.el.addClass('closed'); state.videoCaption.toggle(jQuery.Event('click')); - jasmine.Clock.useMock(); }); @@ -1039,41 +1098,59 @@ describe('caption accessibility', function () { beforeEach(function () { - state = jasmine.initializePlayer(); + runs(function () { + state = jasmine.initializePlayer(); + }); + + waitsFor(function () { + return state.videoCaption.rendered; + }, 'Captions are not rendered', WAIT_TIMEOUT); }); describe('when getting focus through TAB key', function () { beforeEach(function () { - state.videoCaption.isMouseFocus = false; - $('.subtitles li[data-index=0]').trigger( - jQuery.Event('focus') - ); + runs(function () { + state.videoCaption.isMouseFocus = false; + $('.subtitles li[data-index=0]').trigger( + jQuery.Event('focus') + ); + }); }); it('shows an outline around the caption', function () { - expect($('.subtitles li[data-index=0]')) - .toHaveClass('focused'); + runs(function () { + expect($('.subtitles li[data-index=0]')) + .toHaveClass('focused'); + }); }); it('has automatic scrolling disabled', function () { - expect(state.videoCaption.autoScrolling).toBe(false); + runs(function () { + expect(state.videoCaption.autoScrolling).toBe(false); + }); }); }); describe('when loosing focus through TAB key', function () { beforeEach(function () { - $('.subtitles li[data-index=0]').trigger( - jQuery.Event('blur') - ); + runs(function () { + $('.subtitles li[data-index=0]').trigger( + jQuery.Event('blur') + ); + }); }); it('does not show an outline around the caption', function () { - expect($('.subtitles li[data-index=0]')) - .not.toHaveClass('focused'); + runs(function () { + expect($('.subtitles li[data-index=0]')) + .not.toHaveClass('focused'); + }); }); it('has automatic scrolling enabled', function () { - expect(state.videoCaption.autoScrolling).toBe(true); + runs(function () { + expect(state.videoCaption.autoScrolling).toBe(true); + }); }); }); @@ -1083,20 +1160,26 @@ function () { beforeEach(function () { - state.videoCaption.isMouseFocus = false; - $('.subtitles li[data-index=0]') - .trigger(jQuery.Event('focus')); - $('.subtitles li[data-index=0]') - .trigger(jQuery.Event('mousedown')); + runs(function () { + state.videoCaption.isMouseFocus = false; + $('.subtitles li[data-index=0]') + .trigger(jQuery.Event('focus')); + $('.subtitles li[data-index=0]') + .trigger(jQuery.Event('mousedown')); + }); }); it('does not show an outline around it', function () { - expect($('.subtitles li[data-index=0]')) - .not.toHaveClass('focused'); + runs(function () { + expect($('.subtitles li[data-index=0]')) + .not.toHaveClass('focused'); + }); }); it('has automatic scrolling enabled', function () { - expect(state.videoCaption.autoScrolling).toBe(true); + runs(function () { + expect(state.videoCaption.autoScrolling).toBe(true); + }); }); }); @@ -1108,29 +1191,37 @@ var subDataLiIdx__0, subDataLiIdx__1; beforeEach(function () { - subDataLiIdx__0 = $('.subtitles li[data-index=0]'); - subDataLiIdx__1 = $('.subtitles li[data-index=1]'); + runs(function () { + subDataLiIdx__0 = $('.subtitles li[data-index=0]'); + subDataLiIdx__1 = $('.subtitles li[data-index=1]'); - state.videoCaption.isMouseFocus = false; + state.videoCaption.isMouseFocus = false; - subDataLiIdx__0.trigger(jQuery.Event('focus')); - subDataLiIdx__0.trigger(jQuery.Event('blur')); + subDataLiIdx__0.trigger(jQuery.Event('focus')); + subDataLiIdx__0.trigger(jQuery.Event('blur')); - state.videoCaption.isMouseFocus = true; + state.videoCaption.isMouseFocus = true; - subDataLiIdx__1.trigger(jQuery.Event('mousedown')); + subDataLiIdx__1.trigger(jQuery.Event('mousedown')); + }); }); it('does not show an outline around the first', function () { - expect(subDataLiIdx__0).not.toHaveClass('focused'); + runs(function () { + expect(subDataLiIdx__0).not.toHaveClass('focused'); + }); }); it('does not show an outline around the second', function () { - expect(subDataLiIdx__1).not.toHaveClass('focused'); + runs(function () { + expect(subDataLiIdx__1).not.toHaveClass('focused'); + }); }); it('has automatic scrolling enabled', function () { - expect(state.videoCaption.autoScrolling).toBe(true); + runs(function () { + expect(state.videoCaption.autoScrolling).toBe(true); + }); }); }); }); diff --git a/common/lib/xmodule/xmodule/js/src/video/00_async_process.js b/common/lib/xmodule/xmodule/js/src/video/00_async_process.js new file mode 100644 index 0000000000..55a2cb1448 --- /dev/null +++ b/common/lib/xmodule/xmodule/js/src/video/00_async_process.js @@ -0,0 +1,59 @@ +(function (define) { +define( +'video/00_async_process.js', +[], +function() { +"use strict"; +/** + * Provides convenient way to process big amount of data without UI blocking. + * + * @param {array} list Array to process. + * @param {function} process Calls this function on each item in the list. + * @return {array} Returns a Promise object to observe when all actions of a + certain type bound to the collection, queued or not, have finished. + */ + var AsyncProcess = { + array: function (list, process) { + if (!_.isArray(list)) { + return $.Deferred().reject().promise(); + } + + if (!_.isFunction(process) || !list.length) { + return $.Deferred().resolve(list).promise(); + } + + var MAX_DELAY = 50, // maximum amount of time that js code should be allowed to run continuously + dfd = $.Deferred(), + result = [], + index = 0, + len = list.length; + + var getCurrentTime = function () { + return (new Date()).getTime(); + }; + + var handler = function () { + var start = getCurrentTime(); + + do { + result[index] = process(list[index], index); + index++; + } while (index < len && getCurrentTime() - start < MAX_DELAY); + + if (index < len) { + setTimeout(handler, 25); + } else { + dfd.resolve(result); + } + }; + + setTimeout(handler, 25); + + return dfd.promise(); + } + }; + + return AsyncProcess; +}); +}(RequireJS.define)); + diff --git a/common/lib/xmodule/xmodule/js/src/video/00_sjson.js b/common/lib/xmodule/xmodule/js/src/video/00_sjson.js new file mode 100644 index 0000000000..b140a2e31c --- /dev/null +++ b/common/lib/xmodule/xmodule/js/src/video/00_sjson.js @@ -0,0 +1,65 @@ +(function (define) { + +define( +'video/00_sjson.js', +[], +function() { +"use strict"; + + var Sjson = function (data) { + var sjson = { + start: data.start.concat(), + text: data.text.concat() + }, + module = {}; + + var getter = function (propertyName) { + return function () { + return sjson[propertyName]; + }; + }; + + var getStartTimes = getter('start'); + + var getCaptions = getter('text'); + + var size = function () { + return sjson.text.length; + }; + + var search = function (time) { + var start = getStartTimes(), + max = size() - 1, + min = 0, + index; + + if (time < start[min]) { + return -1; + } + while (min < max) { + index = Math.ceil((max + min) / 2); + + if (time < start[index]) { + max = index - 1; + } + + if (time >= start[index]) { + min = index; + } + } + + return min; + }; + + return { + getCaptions: getCaptions, + getStartTimes: getStartTimes, + getSize: size, + search: search + }; + }; + + return Sjson; +}); +}(RequireJS.define)); + diff --git a/common/lib/xmodule/xmodule/js/src/video/09_video_caption.js b/common/lib/xmodule/xmodule/js/src/video/09_video_caption.js index 5d8e2a5007..3fd5832f8a 100644 --- a/common/lib/xmodule/xmodule/js/src/video/09_video_caption.js +++ b/common/lib/xmodule/xmodule/js/src/video/09_video_caption.js @@ -1,10 +1,10 @@ -(function (requirejs, require, define) { +(function (define) { // VideoCaption module. define( 'video/09_video_caption.js', -[], -function () { +['video/00_sjson.js', 'video/00_async_process.js'], +function (Sjson, AsyncProcess) { /** * @desc VideoCaption module exports a function. @@ -21,16 +21,11 @@ function () { * @returns {undefined} */ return function (state) { - var dfd = $.Deferred(); - state.videoCaption = {}; - _makeFunctionsPublic(state); - state.videoCaption.renderElements(); - dfd.resolve(); - return dfd.promise(); + return $.Deferred().resolve().promise(); }; // *************************************************************** @@ -65,10 +60,8 @@ function () { renderCaption: renderCaption, renderElements: renderElements, renderLanguageMenu: renderLanguageMenu, - reRenderCaption: reRenderCaption, resize: resize, scrollCaption: scrollCaption, - search: search, seekPlayer: seekPlayer, setSubtitlesHeight: setSubtitlesHeight, toggle: toggle, @@ -133,19 +126,47 @@ function () { // mousemove, etc.). function bindHandlers() { var self = this, - Caption = this.videoCaption; + Caption = this.videoCaption, + events = [ + 'mouseover', 'mouseout', 'mousedown', 'click', 'focus', 'blur', + 'keydown' + ].join(' '); Caption.hideSubtitlesEl.on({ 'click': Caption.toggle }); - Caption.subtitlesEl.on({ - mouseenter: Caption.onMouseEnter, - mouseleave: Caption.onMouseLeave, - mousemove: Caption.onMovement, - mousewheel: Caption.onMovement, - DOMMouseScroll: Caption.onMovement - }); + Caption.subtitlesEl + .on({ + mouseenter: Caption.onMouseEnter, + mouseleave: Caption.onMouseLeave, + mousemove: Caption.onMovement, + mousewheel: Caption.onMovement, + DOMMouseScroll: Caption.onMovement + }) + .on(events, 'li[data-index]', function (event) { + switch (event.type) { + case 'mouseover': + case 'mouseout': + Caption.captionMouseOverOut(event); + break; + case 'mousedown': + Caption.captionMouseDown(event); + break; + case 'click': + Caption.captionClick(event); + break; + case 'focusin': + Caption.captionFocus(event); + break; + case 'focusout': + Caption.captionBlur(event); + break; + case 'keydown': + Caption.captionKeyDown(event); + break; + } + }); if (Caption.showLanguageMenu) { Caption.container.on({ @@ -154,12 +175,6 @@ function () { }); } - this.el.on('speedchange', function () { - if (self.isFlashMode()) { - Caption.fetchCaption(); - } - }); - if ((this.videoType === 'html5') && (this.config.autohideHtml5)) { Caption.subtitlesEl.on('scroll', this.videoControl.showControls); } @@ -241,7 +256,7 @@ function () { if (this.videoType === 'youtube') { data = { - videoId: this.youtubeId() + videoId: this.youtubeId('1.0') }; } @@ -251,13 +266,15 @@ function () { url: self.config.transcriptTranslationUrl + '/' + language, notifyOnError: false, data: data, - success: function (captions) { - Caption.captions = captions.text; - Caption.start = captions.start; + success: function (response) { + Caption.sjson = new Sjson(response); + + var start = Caption.sjson.getStartTimes(), + captions = Caption.sjson.getCaptions(); if (Caption.loaded) { if (Caption.rendered) { - Caption.reRenderCaption(); + Caption.renderCaption(start, captions); Caption.updatePlayTime(self.videoPlayer.currentTime); } } else { @@ -269,7 +286,7 @@ function () { ) ); } else { - Caption.renderCaption(); + Caption.renderCaption(start, captions); } Caption.bindHandlers(); @@ -334,7 +351,6 @@ function () { .height(this.videoCaption.bottomSpacingHeight()); this.videoCaption.scrollCaption(); - this.videoCaption.setSubtitlesHeight(); } @@ -380,90 +396,53 @@ function () { }); } - function buildCaptions (container, captions, start) { - var fragment = document.createDocumentFragment(); + function buildCaptions (container, start, captions) { + var process = function(text, index) { + var liEl = $('