Commit 93248a30 by Prem Sichanugrist

Add test coverage for JavaScript

parent c7b5ec92
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
"/static/js/jquery-1.6.2.min.js", "/static/js/jquery-1.6.2.min.js",
"/static/js/jquery-ui-1.8.16.custom.min.js", "/static/js/jquery-ui-1.8.16.custom.min.js",
"/static/js/jquery.leanModal.js", "/static/js/jquery.leanModal.js",
"/static/js/jquery.cookie.js" "/static/js/flot/jquery.flot.js"
], ],
"static_files": [ "static_files": [
"js/application.js" "js/application.js"
......
...@@ -24,6 +24,7 @@ describe 'Calculator', -> ...@@ -24,6 +24,7 @@ describe 'Calculator', ->
expect($('form#calculator')).toHandleWith 'submit', @calculator.calculate expect($('form#calculator')).toHandleWith 'submit', @calculator.calculate
it 'prevent default behavior on form submit', -> it 'prevent default behavior on form submit', ->
jasmine.stubRequests()
$('form#calculator').submit (e) -> $('form#calculator').submit (e) ->
expect(e.isDefaultPrevented()).toBeTruthy() expect(e.isDefaultPrevented()).toBeTruthy()
e.preventDefault() e.preventDefault()
...@@ -55,7 +56,7 @@ describe 'Calculator', -> ...@@ -55,7 +56,7 @@ describe 'Calculator', ->
describe 'calculate', -> describe 'calculate', ->
beforeEach -> beforeEach ->
$('#calculator_input').val '1+2' $('#calculator_input').val '1+2'
spyOn($, 'getJSON').andCallFake (url, data, callback) -> spyOn($, 'getWithPrefix').andCallFake (url, data, callback) ->
callback({ result: 3 }) callback({ result: 3 })
@calculator.calculate() @calculator.calculate()
......
...@@ -26,6 +26,7 @@ ...@@ -26,6 +26,7 @@
return expect($('form#calculator')).toHandleWith('submit', this.calculator.calculate); return expect($('form#calculator')).toHandleWith('submit', this.calculator.calculate);
}); });
return it('prevent default behavior on form submit', function() { return it('prevent default behavior on form submit', function() {
jasmine.stubRequests();
$('form#calculator').submit(function(e) { $('form#calculator').submit(function(e) {
expect(e.isDefaultPrevented()).toBeTruthy(); expect(e.isDefaultPrevented()).toBeTruthy();
return e.preventDefault(); return e.preventDefault();
...@@ -58,7 +59,7 @@ ...@@ -58,7 +59,7 @@
return describe('calculate', function() { return describe('calculate', function() {
beforeEach(function() { beforeEach(function() {
$('#calculator_input').val('1+2'); $('#calculator_input').val('1+2');
spyOn($, 'getJSON').andCallFake(function(url, data, callback) { spyOn($, 'getWithPrefix').andCallFake(function(url, data, callback) {
return callback({ return callback({
result: 3 result: 3
}); });
......
...@@ -35,16 +35,28 @@ describe 'Courseware', -> ...@@ -35,16 +35,28 @@ describe 'Courseware', ->
describe 'render', -> describe 'render', ->
beforeEach -> beforeEach ->
jasmine.stubRequests()
@courseware = new Courseware @courseware = new Courseware
spyOn(window, 'Histogram')
spyOn(window, 'Problem')
spyOn(window, 'Video')
setFixtures """ setFixtures """
<div class="course-content"> <div class="course-content">
<div id="video_1" class="video" data-streams="1.0:abc1234"></div> <div id="video_1" class="video" data-streams="1.0:abc1234"></div>
<div id="video_2" class="video" data-streams="1.0:def5678"></div> <div id="video_2" class="video" data-streams="1.0:def5678"></div>
<div id="problem_3" class="problems-wrapper" data-url="/example/url/">
<div id="histogram_3" class="histogram" data-histogram="[[0, 1]]" style="height: 20px; display: block;">
</div>
</div> </div>
""" """
it 'detect the video element and convert them', ->
spyOn(window, 'Video')
@courseware.render() @courseware.render()
it 'detect the video elements and convert them', ->
expect(window.Video).toHaveBeenCalledWith('1', '1.0:abc1234') expect(window.Video).toHaveBeenCalledWith('1', '1.0:abc1234')
expect(window.Video).toHaveBeenCalledWith('2', '1.0:def5678') expect(window.Video).toHaveBeenCalledWith('2', '1.0:def5678')
it 'detect the problem element and convert it', ->
expect(window.Problem).toHaveBeenCalledWith('3', '/example/url/')
it 'detect the histrogram element and convert it', ->
expect(window.Histogram).toHaveBeenCalledWith('3', [[0, 1]])
...@@ -35,15 +35,24 @@ ...@@ -35,15 +35,24 @@
}); });
return describe('render', function() { return describe('render', function() {
beforeEach(function() { beforeEach(function() {
jasmine.stubRequests();
this.courseware = new Courseware; this.courseware = new Courseware;
return setFixtures("<div class=\"course-content\">\n <div id=\"video_1\" class=\"video\" data-streams=\"1.0:abc1234\"></div>\n <div id=\"video_2\" class=\"video\" data-streams=\"1.0:def5678\"></div>\n</div>"); spyOn(window, 'Histogram');
}); spyOn(window, 'Problem');
return it('detect the video element and convert them', function() {
spyOn(window, 'Video'); spyOn(window, 'Video');
this.courseware.render(); setFixtures("<div class=\"course-content\">\n <div id=\"video_1\" class=\"video\" data-streams=\"1.0:abc1234\"></div>\n <div id=\"video_2\" class=\"video\" data-streams=\"1.0:def5678\"></div>\n <div id=\"problem_3\" class=\"problems-wrapper\" data-url=\"/example/url/\">\n <div id=\"histogram_3\" class=\"histogram\" data-histogram=\"[[0, 1]]\" style=\"height: 20px; display: block;\">\n </div>\n</div>");
return this.courseware.render();
});
it('detect the video elements and convert them', function() {
expect(window.Video).toHaveBeenCalledWith('1', '1.0:abc1234'); expect(window.Video).toHaveBeenCalledWith('1', '1.0:abc1234');
return expect(window.Video).toHaveBeenCalledWith('2', '1.0:def5678'); return expect(window.Video).toHaveBeenCalledWith('2', '1.0:def5678');
}); });
it('detect the problem element and convert it', function() {
return expect(window.Problem).toHaveBeenCalledWith('3', '/example/url/');
});
return it('detect the histrogram element and convert it', function() {
return expect(window.Histogram).toHaveBeenCalledWith('3', [[0, 1]]);
});
}); });
}); });
......
...@@ -5,7 +5,7 @@ describe 'FeedbackForm', -> ...@@ -5,7 +5,7 @@ describe 'FeedbackForm', ->
describe 'constructor', -> describe 'constructor', ->
beforeEach -> beforeEach ->
new FeedbackForm new FeedbackForm
spyOn($, 'post').andCallFake (url, data, callback, format) -> spyOn($, 'postWithPrefix').andCallFake (url, data, callback, format) ->
callback() callback()
it 'binds to the #feedback_button', -> it 'binds to the #feedback_button', ->
......
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
return describe('constructor', function() { return describe('constructor', function() {
beforeEach(function() { beforeEach(function() {
new FeedbackForm; new FeedbackForm;
return spyOn($, 'post').andCallFake(function(url, data, callback, format) { return spyOn($, 'postWithPrefix').andCallFake(function(url, data, callback, format) {
return callback(); return callback();
}); });
}); });
......
jasmine.getFixtures().fixturesPath = "/_jasmine/fixtures/" jasmine.getFixtures().fixturesPath = "/_jasmine/fixtures/"
jasmine.stubbedMetadata =
abc123:
id: 'abc123'
duration: 100
def456:
id: 'def456'
duration: 200
bogus:
duration: 300
jasmine.stubbedCaption =
start: [0, 10000, 20000, 30000, 40000, 50000, 60000, 70000, 80000, 90000,
100000, 110000, 120000]
text: ['Caption at 0', 'Caption at 10000', 'Caption at 20000',
'Caption at 30000', 'Caption at 40000', 'Caption at 50000', 'Caption at 60000',
'Caption at 70000', 'Caption at 80000', 'Caption at 90000', 'Caption at 100000',
'Caption at 110000', 'Caption at 120000']
jasmine.stubRequests = ->
spyOn($, 'ajax').andCallFake (settings) ->
if match = settings.url.match /youtube\.com\/.+\/videos\/(.+)\?v=2&alt=jsonc/
settings.success data: jasmine.stubbedMetadata[match[1]]
else if match = settings.url.match /static\/subs\/(.+)\.srt\.sjson/
settings.success jasmine.stubbedCaption
else if settings.url == '/calculate' ||
settings.url == '/6002x/modx/sequential/1/goto_position' ||
settings.url.match(/event$/) ||
settings.url.match(/6002x\/modx\/problem\/.+\/problem_(check|reset|show|save)$/)
# do nothing
else
throw "External request attempted for #{settings.url}, which is not defined."
jasmine.stubYoutubePlayer = ->
YT.Player = -> jasmine.createSpyObj 'YT.Player', ['cueVideoById', 'getVideoEmbedCode',
'getCurrentTime', 'getPlayerState', 'loadVideoById', 'playVideo', 'pauseVideo', 'seekTo']
jasmine.stubVideoPlayer = (context, enableParts) ->
enableParts = [enableParts] unless $.isArray(enableParts)
suite = context.suite
currentPartName = suite.description while suite = suite.parentSuite
enableParts.push currentPartName
for part in ['VideoCaption', 'VideoSpeedControl', 'VideoProgressSlider']
unless $.inArray(part, enableParts) >= 0
spyOn window, part
loadFixtures 'video.html'
jasmine.stubRequests()
YT.Player = undefined
context.video = new Video 'example', '.75:abc123,1.0:def456'
jasmine.stubYoutubePlayer()
return new VideoPlayer context.video
spyOn(window, 'onunload')
# Stub Youtube API
window.YT =
PlayerState:
UNSTARTED: -1
ENDED: 0
PLAYING: 1
PAUSED: 2
BUFFERING: 3
CUED: 5
# Stub jQuery.cookie
$.cookie = jasmine.createSpy('jQuery.cookie').andReturn '1.0'
# Stub jQuery.qtip
$.fn.qtip = jasmine.createSpy 'jQuery.qtip'
# Stub jQuery.scrollTo
$.fn.scrollTo = jasmine.createSpy 'jQuery.scrollTo'
...@@ -2,4 +2,90 @@ ...@@ -2,4 +2,90 @@
jasmine.getFixtures().fixturesPath = "/_jasmine/fixtures/"; jasmine.getFixtures().fixturesPath = "/_jasmine/fixtures/";
jasmine.stubbedMetadata = {
abc123: {
id: 'abc123',
duration: 100
},
def456: {
id: 'def456',
duration: 200
},
bogus: {
duration: 300
}
};
jasmine.stubbedCaption = {
start: [0, 10000, 20000, 30000, 40000, 50000, 60000, 70000, 80000, 90000, 100000, 110000, 120000],
text: ['Caption at 0', 'Caption at 10000', 'Caption at 20000', 'Caption at 30000', 'Caption at 40000', 'Caption at 50000', 'Caption at 60000', 'Caption at 70000', 'Caption at 80000', 'Caption at 90000', 'Caption at 100000', 'Caption at 110000', 'Caption at 120000']
};
jasmine.stubRequests = function() {
return spyOn($, 'ajax').andCallFake(function(settings) {
var match;
if (match = settings.url.match(/youtube\.com\/.+\/videos\/(.+)\?v=2&alt=jsonc/)) {
return settings.success({
data: jasmine.stubbedMetadata[match[1]]
});
} else if (match = settings.url.match(/static\/subs\/(.+)\.srt\.sjson/)) {
return settings.success(jasmine.stubbedCaption);
} else if (settings.url === '/calculate' || settings.url === '/6002x/modx/sequential/1/goto_position' || settings.url.match(/event$/) || settings.url.match(/6002x\/modx\/problem\/.+\/problem_(check|reset|show|save)$/)) {
} else {
throw "External request attempted for " + settings.url + ", which is not defined.";
}
});
};
jasmine.stubYoutubePlayer = function() {
return YT.Player = function() {
return jasmine.createSpyObj('YT.Player', ['cueVideoById', 'getVideoEmbedCode', 'getCurrentTime', 'getPlayerState', 'loadVideoById', 'playVideo', 'pauseVideo', 'seekTo']);
};
};
jasmine.stubVideoPlayer = function(context, enableParts) {
var currentPartName, part, suite, _i, _len, _ref;
if (!$.isArray(enableParts)) {
enableParts = [enableParts];
}
suite = context.suite;
while (suite = suite.parentSuite) {
currentPartName = suite.description;
}
enableParts.push(currentPartName);
_ref = ['VideoCaption', 'VideoSpeedControl', 'VideoProgressSlider'];
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
part = _ref[_i];
if (!($.inArray(part, enableParts) >= 0)) {
spyOn(window, part);
}
}
loadFixtures('video.html');
jasmine.stubRequests();
YT.Player = void 0;
context.video = new Video('example', '.75:abc123,1.0:def456');
jasmine.stubYoutubePlayer();
return new VideoPlayer(context.video);
};
spyOn(window, 'onunload');
window.YT = {
PlayerState: {
UNSTARTED: -1,
ENDED: 0,
PLAYING: 1,
PAUSED: 2,
BUFFERING: 3,
CUED: 5
}
};
$.cookie = jasmine.createSpy('jQuery.cookie').andReturn('1.0');
$.fn.qtip = jasmine.createSpy('jQuery.qtip');
$.fn.scrollTo = jasmine.createSpy('jQuery.scrollTo');
}).call(this); }).call(this);
class @Courseware class @Courseware
@prefix: ''
constructor: -> constructor: ->
Courseware.prefix = $("meta[name='path_prefix']").attr('content') Courseware.prefix = $("meta[name='path_prefix']").attr('content')
new Navigation new Navigation
...@@ -25,4 +27,3 @@ class @Courseware ...@@ -25,4 +27,3 @@ class @Courseware
$('.course-content .histogram').each -> $('.course-content .histogram').each ->
id = $(this).attr('id').replace(/histogram_/, '') id = $(this).attr('id').replace(/histogram_/, '')
new Histogram id, $(this).data('histogram') new Histogram id, $(this).data('histogram')
[
{
"content": "\"Video 1\"",
"type": "video",
"title": "Video 1"
}, {
"content": "\"Video 2\"",
"type": "video",
"title": "Video 2"
}, {
"content": "\"Sample Problem\"",
"type": "problem",
"title": "Sample Problem"
}
]
<section id="problem_1" class="problems-wrapper" data-url="/problem/url/"></section>
<h2 class="problem-header">Problem Header</h2>
<section class="problem">
<p>Problem Content</p>
<section class="action">
<input type="hidden" name="problem_id" value="1">
<input class="check" type="button" value="Check">
<input class="reset" type="button" value="Reset">
<input class="save" type="button" value="Save">
<input class="show" type="button" value="Show Answer">
<a href="/courseware/6.002_Spring_2012/${ explain }" class="new-page">Explanation</a>
<section class="submission_feedback"></section>
</section>
</section>
<div id="sequence_1" class="sequence">
<nav class="sequence-nav">
<ol id="sequence-list">
</ol>
<ul class="sequence-nav-buttons">
<li class="prev"><a href="#">Previous</a></li>
<li class="next"><a href="#">Next</a></li>
</ul>
</nav>
<div id="seq_content"></div>
<nav class="sequence-bottom">
<ul class="sequence-nav-buttons">
<li class="prev"><a href="#">Previous</a></li>
<li class="next"><a href="#">Next</a></li>
</ul>
</nav>
</div>
<div id="tab_1" class="tab">
<ul class="navigation"></ul>
</div>
<div class="course-content">
<div id="video_example" class="video">
<div class="tc-wrapper">
<article class="video-wrapper">
<section class="video-player">
<div id="example"></div>
</section>
<section class="video-controls"></section>
</article>
</div>
</div>
</div>
describe 'Histogram', ->
beforeEach ->
spyOn $, 'plot'
describe 'constructor', ->
it 'instantiate the data arrays', ->
histogram = new Histogram 1, []
expect(histogram.xTicks).toEqual []
expect(histogram.yTicks).toEqual []
expect(histogram.data).toEqual []
describe 'calculate', ->
beforeEach ->
@histogram = new Histogram(1, [[1, 1], [2, 2], [3, 3]])
it 'store the correct value for data', ->
expect(@histogram.data).toEqual [[1, Math.log(2)], [2, Math.log(3)], [3, Math.log(4)]]
it 'store the correct value for x ticks', ->
expect(@histogram.xTicks).toEqual [[1, '1'], [2, '2'], [3, '3']]
it 'store the correct value for y ticks', ->
expect(@histogram.yTicks).toEqual
describe 'render', ->
it 'call flot with correct option', ->
new Histogram(1, [[1, 1], [2, 2], [3, 3]])
expect($.plot).toHaveBeenCalledWith $("#histogram_1"), [
data: [[1, Math.log(2)], [2, Math.log(3)], [3, Math.log(4)]]
bars:
show: true
align: 'center'
lineWidth: 0
fill: 1.0
color: "#b72121"
],
xaxis:
min: -1
max: 4
ticks: [[1, '1'], [2, '2'], [3, '3']]
tickLength: 0
yaxis:
min: 0.0
max: Math.log(4) * 1.1
ticks: [[Math.log(2), '1'], [Math.log(3), '2'], [Math.log(4), '3']]
labelWidth: 50
(function() {
describe('Histogram', function() {
beforeEach(function() {
return spyOn($, 'plot');
});
describe('constructor', function() {
return it('instantiate the data arrays', function() {
var histogram;
histogram = new Histogram(1, []);
expect(histogram.xTicks).toEqual([]);
expect(histogram.yTicks).toEqual([]);
return expect(histogram.data).toEqual([]);
});
});
describe('calculate', function() {
beforeEach(function() {
return this.histogram = new Histogram(1, [[1, 1], [2, 2], [3, 3]]);
});
it('store the correct value for data', function() {
return expect(this.histogram.data).toEqual([[1, Math.log(2)], [2, Math.log(3)], [3, Math.log(4)]]);
});
it('store the correct value for x ticks', function() {
return expect(this.histogram.xTicks).toEqual([[1, '1'], [2, '2'], [3, '3']]);
});
return it('store the correct value for y ticks', function() {
return expect(this.histogram.yTicks).toEqual;
});
});
return describe('render', function() {
return it('call flot with correct option', function() {
new Histogram(1, [[1, 1], [2, 2], [3, 3]]);
return expect($.plot).toHaveBeenCalledWith($("#histogram_1"), [
{
data: [[1, Math.log(2)], [2, Math.log(3)], [3, Math.log(4)]],
bars: {
show: true,
align: 'center',
lineWidth: 0,
fill: 1.0
},
color: "#b72121"
}
], {
xaxis: {
min: -1,
max: 4,
ticks: [[1, '1'], [2, '2'], [3, '3']],
tickLength: 0
},
yaxis: {
min: 0.0,
max: Math.log(4) * 1.1,
ticks: [[Math.log(2), '1'], [Math.log(3), '2'], [Math.log(4), '3']],
labelWidth: 50
}
});
});
});
});
}).call(this);
describe 'Logger', ->
it 'expose window.log_event', ->
jasmine.stubRequests()
expect(window.log_event).toBe Logger.log
describe 'log', ->
it 'send a request to log event', ->
spyOn $, 'getWithPrefix'
Logger.log 'example', 'data'
expect($.getWithPrefix).toHaveBeenCalledWith '/event',
event_type: 'example'
event: '"data"'
page: window.location.href
describe 'bind', ->
beforeEach ->
Logger.bind()
Courseware.prefix = '/6002x'
afterEach ->
window.onunload = null
it 'bind the onunload event', ->
expect(window.onunload).toEqual jasmine.any(Function)
it 'send a request to log event', ->
spyOn($, 'ajax')
$(window).trigger('onunload')
expect($.ajax).toHaveBeenCalledWith
url: "#{Courseware.prefix}/event",
data:
event_type: 'page_close'
event: ''
page: window.location.href
async: false
(function() {
describe('Logger', function() {
it('expose window.log_event', function() {
jasmine.stubRequests();
return expect(window.log_event).toBe(Logger.log);
});
describe('log', function() {
return it('send a request to log event', function() {
spyOn($, 'getWithPrefix');
Logger.log('example', 'data');
return expect($.getWithPrefix).toHaveBeenCalledWith('/event', {
event_type: 'example',
event: '"data"',
page: window.location.href
});
});
});
return describe('bind', function() {
beforeEach(function() {
Logger.bind();
return Courseware.prefix = '/6002x';
});
afterEach(function() {
return window.onunload = null;
});
it('bind the onunload event', function() {
return expect(window.onunload).toEqual(jasmine.any(Function));
});
return it('send a request to log event', function() {
spyOn($, 'ajax');
$(window).trigger('onunload');
return expect($.ajax).toHaveBeenCalledWith({
url: "" + Courseware.prefix + "/event",
data: {
event_type: 'page_close',
event: '',
page: window.location.href
},
async: false
});
});
});
});
}).call(this);
describe 'Problem', ->
beforeEach ->
# Stub MathJax
window.MathJax = { Hub: { Queue: -> } }
window.update_schematics = ->
loadFixtures 'problem.html'
spyOn Logger, 'log'
spyOn($.fn, 'load').andCallFake (url, callback) ->
$(@).html readFixtures('problem_content.html')
callback()
describe 'constructor', ->
beforeEach ->
@problem = new Problem 1, '/problem/url/'
it 'set the element', ->
expect(@problem.element).toBe '#problem_1'
it 'set the content url', ->
expect(@problem.content_url).toEqual '/problem/url/problem_get?id=1'
it 'render the content', ->
expect($.fn.load).toHaveBeenCalledWith @problem.content_url, @problem.bind
describe 'bind', ->
beforeEach ->
spyOn MathJax.Hub, 'Queue'
spyOn window, 'update_schematics'
@problem = new Problem 1, '/problem/url/'
it 'set mathjax typeset', ->
expect(MathJax.Hub.Queue).toHaveBeenCalled()
it 'update schematics', ->
expect(window.update_schematics).toHaveBeenCalled()
it 'bind answer refresh on button click', ->
expect($('section.action input:button')).toHandleWith 'click', @problem.refreshAnswers
it 'bind the check button', ->
expect($('section.action input.check')).toHandleWith 'click', @problem.check
it 'bind the reset button', ->
expect($('section.action input.reset')).toHandleWith 'click', @problem.reset
it 'bind the show button', ->
expect($('section.action input.show')).toHandleWith 'click', @problem.show
it 'bind the save button', ->
expect($('section.action input.save')).toHandleWith 'click', @problem.save
describe 'render', ->
beforeEach ->
@problem = new Problem 1, '/problem/url/'
@bind = @problem.bind
spyOn @problem, 'bind'
describe 'with content given', ->
beforeEach ->
@problem.render 'Hello World'
it 'render the content', ->
expect(@problem.element.html()).toEqual 'Hello World'
it 're-bind the content', ->
expect(@problem.bind).toHaveBeenCalled()
describe 'with no content given', ->
it 'load the content via ajax', ->
expect($.fn.load).toHaveBeenCalledWith @problem.content_url, @bind
describe 'check', ->
beforeEach ->
jasmine.stubRequests()
@problem = new Problem 1, '/problem/url/'
@problem.answers = 'foo=1&bar=2'
it 'log the problem_check event', ->
@problem.check()
expect(Logger.log).toHaveBeenCalledWith 'problem_check', 'foo=1&bar=2'
it 'submit the answer for check', ->
spyOn $, 'postWithPrefix'
@problem.check()
expect($.postWithPrefix).toHaveBeenCalledWith '/modx/problem/1/problem_check', 'foo=1&bar=2', jasmine.any(Function)
describe 'when the response is correct', ->
it 'call render with returned content', ->
spyOn($, 'postWithPrefix').andCallFake (url, answers, callback) -> callback(success: 'correct', contents: 'Correct!')
@problem.check()
expect(@problem.element.html()).toEqual 'Correct!'
describe 'when the response is incorrect', ->
it 'call render with returned content', ->
spyOn($, 'postWithPrefix').andCallFake (url, answers, callback) -> callback(success: 'incorrect', contents: 'Correct!')
@problem.check()
expect(@problem.element.html()).toEqual 'Correct!'
describe 'when the response is undetermined', ->
it 'alert the response', ->
spyOn window, 'alert'
spyOn($, 'postWithPrefix').andCallFake (url, answers, callback) -> callback(success: 'Number Only!')
@problem.check()
expect(window.alert).toHaveBeenCalledWith 'Number Only!'
describe 'reset', ->
beforeEach ->
jasmine.stubRequests()
@problem = new Problem 1, '/problem/url/'
it 'log the problem_reset event', ->
@problem.answers = 'foo=1&bar=2'
@problem.reset()
expect(Logger.log).toHaveBeenCalledWith 'problem_reset', 'foo=1&bar=2'
it 'POST to the problem reset page', ->
spyOn $, 'postWithPrefix'
@problem.reset()
expect($.postWithPrefix).toHaveBeenCalledWith '/modx/problem/1/problem_reset', { id: 1 }, jasmine.any(Function)
it 'render the returned content', ->
spyOn($, 'postWithPrefix').andCallFake (url, answers, callback) -> callback("Reset!")
@problem.reset()
expect(@problem.element.html()).toEqual 'Reset!'
describe 'show', ->
beforeEach ->
jasmine.stubRequests()
@problem = new Problem 1, '/problem/url/'
@problem.element.prepend '<div id="answer_1_1" /><div id="answer_1_2" />'
describe 'when the answer has not yet shown', ->
beforeEach ->
@problem.element.removeClass 'showed'
it 'log the problem_show event', ->
@problem.show()
expect(Logger.log).toHaveBeenCalledWith 'problem_show', problem: 1
it 'fetch the answers', ->
spyOn $, 'postWithPrefix'
@problem.show()
expect($.postWithPrefix).toHaveBeenCalledWith '/modx/problem/1/problem_show', jasmine.any(Function)
it 'show the answers', ->
spyOn($, 'postWithPrefix').andCallFake (url, callback) -> callback('1_1': 'One', '1_2': 'Two')
@problem.show()
expect($('#answer_1_1')).toHaveHtml 'One'
expect($('#answer_1_2')).toHaveHtml 'Two'
it 'toggle the show answer button', ->
spyOn($, 'postWithPrefix').andCallFake (url, callback) -> callback({})
@problem.show()
expect($('.show')).toHaveValue 'Hide Answer'
it 'add the showed class to element', ->
spyOn($, 'postWithPrefix').andCallFake (url, callback) -> callback({})
@problem.show()
expect(@problem.element).toHaveClass 'showed'
describe 'multiple choice question', ->
beforeEach ->
@problem.element.prepend '''
<label for="input_1_1_1"><input type="checkbox" name="input_1_1" id="input_1_1_1" value="1"> One</label>
<label for="input_1_1_2"><input type="checkbox" name="input_1_1" id="input_1_1_2" value="2"> Two</label>
<label for="input_1_1_3"><input type="checkbox" name="input_1_1" id="input_1_1_3" value="3"> Three</label>
<label for="input_1_2_1"><input type="radio" name="input_1_2" id="input_1_2_1" value="1"> Other</label>
'''
it 'set the correct_answer attribute on the choice', ->
spyOn($, 'postWithPrefix').andCallFake (url, callback) -> callback('1_1': [2, 3])
@problem.show()
expect($('label[for="input_1_1_1"]')).not.toHaveAttr 'correct_answer', 'true'
expect($('label[for="input_1_1_2"]')).toHaveAttr 'correct_answer', 'true'
expect($('label[for="input_1_1_3"]')).toHaveAttr 'correct_answer', 'true'
expect($('label[for="input_1_2_1"]')).not.toHaveAttr 'correct_answer', 'true'
describe 'when the answers are alreay shown', ->
beforeEach ->
@problem.element.addClass 'showed'
@problem.element.prepend '''
<label for="input_1_1_1" correct_answer="true">
<input type="checkbox" name="input_1_1" id="input_1_1_1" value="1" />
One
</label>
'''
$('#answer_1_1').html('One')
$('#answer_1_2').html('Two')
it 'hide the answers', ->
@problem.show()
expect($('#answer_1_1')).toHaveHtml ''
expect($('#answer_1_2')).toHaveHtml ''
expect($('label[for="input_1_1_1"]')).not.toHaveAttr 'correct_answer'
it 'toggle the show answer button', ->
@problem.show()
expect($('.show')).toHaveValue 'Show Answer'
it 'remove the showed class from element', ->
@problem.show()
expect(@problem.element).not.toHaveClass 'showed'
describe 'save', ->
beforeEach ->
jasmine.stubRequests()
@problem = new Problem 1, '/problem/url/'
@problem.answers = 'foo=1&bar=2'
it 'log the problem_save event', ->
@problem.save()
expect(Logger.log).toHaveBeenCalledWith 'problem_save', 'foo=1&bar=2'
it 'POST to save problem', ->
spyOn $, 'postWithPrefix'
@problem.save()
expect($.postWithPrefix).toHaveBeenCalledWith '/modx/problem/1/problem_save', 'foo=1&bar=2', jasmine.any(Function)
it 'alert to the user', ->
spyOn window, 'alert'
spyOn($, 'postWithPrefix').andCallFake (url, answers, callback) -> callback(success: 'OK')
@problem.save()
expect(window.alert).toHaveBeenCalledWith 'Saved'
describe 'refreshAnswers', ->
beforeEach ->
@problem = new Problem 1, '/problem/url/'
@problem.element.html '''
<textarea class="CodeMirror" />
<input id="input_1_1" name="input_1_1" class="schematic" value="one" />
<input id="input_1_2" name="input_1_2" value="two" />
<input id="input_bogus_3" name="input_bogus_3" value="three" />
'''
@stubSchematic = { update_value: jasmine.createSpy('schematic') }
@stubCodeMirror = { save: jasmine.createSpy('CodeMirror') }
$('input.schematic').get(0).schematic = @stubSchematic
$('textarea.CodeMirror').get(0).CodeMirror = @stubCodeMirror
it 'update each schematic', ->
@problem.refreshAnswers()
expect(@stubSchematic.update_value).toHaveBeenCalled()
it 'update each code block', ->
@problem.refreshAnswers()
expect(@stubCodeMirror.save).toHaveBeenCalled()
it 'serialize all answers', ->
@problem.refreshAnswers()
expect(@problem.answers).toEqual "input_1_1=one&input_1_2=two"
(function() {
describe('Problem', function() {
beforeEach(function() {
window.MathJax = {
Hub: {
Queue: function() {}
}
};
window.update_schematics = function() {};
loadFixtures('problem.html');
spyOn(Logger, 'log');
return spyOn($.fn, 'load').andCallFake(function(url, callback) {
$(this).html(readFixtures('problem_content.html'));
return callback();
});
});
describe('constructor', function() {
beforeEach(function() {
return this.problem = new Problem(1, '/problem/url/');
});
it('set the element', function() {
return expect(this.problem.element).toBe('#problem_1');
});
it('set the content url', function() {
return expect(this.problem.content_url).toEqual('/problem/url/problem_get?id=1');
});
return it('render the content', function() {
return expect($.fn.load).toHaveBeenCalledWith(this.problem.content_url, this.problem.bind);
});
});
describe('bind', function() {
beforeEach(function() {
spyOn(MathJax.Hub, 'Queue');
spyOn(window, 'update_schematics');
return this.problem = new Problem(1, '/problem/url/');
});
it('set mathjax typeset', function() {
return expect(MathJax.Hub.Queue).toHaveBeenCalled();
});
it('update schematics', function() {
return expect(window.update_schematics).toHaveBeenCalled();
});
it('bind answer refresh on button click', function() {
return expect($('section.action input:button')).toHandleWith('click', this.problem.refreshAnswers);
});
it('bind the check button', function() {
return expect($('section.action input.check')).toHandleWith('click', this.problem.check);
});
it('bind the reset button', function() {
return expect($('section.action input.reset')).toHandleWith('click', this.problem.reset);
});
it('bind the show button', function() {
return expect($('section.action input.show')).toHandleWith('click', this.problem.show);
});
return it('bind the save button', function() {
return expect($('section.action input.save')).toHandleWith('click', this.problem.save);
});
});
describe('render', function() {
beforeEach(function() {
this.problem = new Problem(1, '/problem/url/');
this.bind = this.problem.bind;
return spyOn(this.problem, 'bind');
});
describe('with content given', function() {
beforeEach(function() {
return this.problem.render('Hello World');
});
it('render the content', function() {
return expect(this.problem.element.html()).toEqual('Hello World');
});
return it('re-bind the content', function() {
return expect(this.problem.bind).toHaveBeenCalled();
});
});
return describe('with no content given', function() {
return it('load the content via ajax', function() {
return expect($.fn.load).toHaveBeenCalledWith(this.problem.content_url, this.bind);
});
});
});
describe('check', function() {
beforeEach(function() {
jasmine.stubRequests();
this.problem = new Problem(1, '/problem/url/');
return this.problem.answers = 'foo=1&bar=2';
});
it('log the problem_check event', function() {
this.problem.check();
return expect(Logger.log).toHaveBeenCalledWith('problem_check', 'foo=1&bar=2');
});
it('submit the answer for check', function() {
spyOn($, 'postWithPrefix');
this.problem.check();
return expect($.postWithPrefix).toHaveBeenCalledWith('/modx/problem/1/problem_check', 'foo=1&bar=2', jasmine.any(Function));
});
describe('when the response is correct', function() {
return it('call render with returned content', function() {
spyOn($, 'postWithPrefix').andCallFake(function(url, answers, callback) {
return callback({
success: 'correct',
contents: 'Correct!'
});
});
this.problem.check();
return expect(this.problem.element.html()).toEqual('Correct!');
});
});
describe('when the response is incorrect', function() {
return it('call render with returned content', function() {
spyOn($, 'postWithPrefix').andCallFake(function(url, answers, callback) {
return callback({
success: 'incorrect',
contents: 'Correct!'
});
});
this.problem.check();
return expect(this.problem.element.html()).toEqual('Correct!');
});
});
return describe('when the response is undetermined', function() {
return it('alert the response', function() {
spyOn(window, 'alert');
spyOn($, 'postWithPrefix').andCallFake(function(url, answers, callback) {
return callback({
success: 'Number Only!'
});
});
this.problem.check();
return expect(window.alert).toHaveBeenCalledWith('Number Only!');
});
});
});
describe('reset', function() {
beforeEach(function() {
jasmine.stubRequests();
return this.problem = new Problem(1, '/problem/url/');
});
it('log the problem_reset event', function() {
this.problem.answers = 'foo=1&bar=2';
this.problem.reset();
return expect(Logger.log).toHaveBeenCalledWith('problem_reset', 'foo=1&bar=2');
});
it('POST to the problem reset page', function() {
spyOn($, 'postWithPrefix');
this.problem.reset();
return expect($.postWithPrefix).toHaveBeenCalledWith('/modx/problem/1/problem_reset', {
id: 1
}, jasmine.any(Function));
});
return it('render the returned content', function() {
spyOn($, 'postWithPrefix').andCallFake(function(url, answers, callback) {
return callback("Reset!");
});
this.problem.reset();
return expect(this.problem.element.html()).toEqual('Reset!');
});
});
describe('show', function() {
beforeEach(function() {
jasmine.stubRequests();
this.problem = new Problem(1, '/problem/url/');
return this.problem.element.prepend('<div id="answer_1_1" /><div id="answer_1_2" />');
});
describe('when the answer has not yet shown', function() {
beforeEach(function() {
return this.problem.element.removeClass('showed');
});
it('log the problem_show event', function() {
this.problem.show();
return expect(Logger.log).toHaveBeenCalledWith('problem_show', {
problem: 1
});
});
it('fetch the answers', function() {
spyOn($, 'postWithPrefix');
this.problem.show();
return expect($.postWithPrefix).toHaveBeenCalledWith('/modx/problem/1/problem_show', jasmine.any(Function));
});
it('show the answers', function() {
spyOn($, 'postWithPrefix').andCallFake(function(url, callback) {
return callback({
'1_1': 'One',
'1_2': 'Two'
});
});
this.problem.show();
expect($('#answer_1_1')).toHaveHtml('One');
return expect($('#answer_1_2')).toHaveHtml('Two');
});
it('toggle the show answer button', function() {
spyOn($, 'postWithPrefix').andCallFake(function(url, callback) {
return callback({});
});
this.problem.show();
return expect($('.show')).toHaveValue('Hide Answer');
});
it('add the showed class to element', function() {
spyOn($, 'postWithPrefix').andCallFake(function(url, callback) {
return callback({});
});
this.problem.show();
return expect(this.problem.element).toHaveClass('showed');
});
return describe('multiple choice question', function() {
beforeEach(function() {
return this.problem.element.prepend('<label for="input_1_1_1"><input type="checkbox" name="input_1_1" id="input_1_1_1" value="1"> One</label>\n<label for="input_1_1_2"><input type="checkbox" name="input_1_1" id="input_1_1_2" value="2"> Two</label>\n<label for="input_1_1_3"><input type="checkbox" name="input_1_1" id="input_1_1_3" value="3"> Three</label>\n<label for="input_1_2_1"><input type="radio" name="input_1_2" id="input_1_2_1" value="1"> Other</label>');
});
return it('set the correct_answer attribute on the choice', function() {
spyOn($, 'postWithPrefix').andCallFake(function(url, callback) {
return callback({
'1_1': [2, 3]
});
});
this.problem.show();
expect($('label[for="input_1_1_1"]')).not.toHaveAttr('correct_answer', 'true');
expect($('label[for="input_1_1_2"]')).toHaveAttr('correct_answer', 'true');
expect($('label[for="input_1_1_3"]')).toHaveAttr('correct_answer', 'true');
return expect($('label[for="input_1_2_1"]')).not.toHaveAttr('correct_answer', 'true');
});
});
});
return describe('when the answers are alreay shown', function() {
beforeEach(function() {
this.problem.element.addClass('showed');
this.problem.element.prepend('<label for="input_1_1_1" correct_answer="true">\n <input type="checkbox" name="input_1_1" id="input_1_1_1" value="1" />\n One\n</label>');
$('#answer_1_1').html('One');
return $('#answer_1_2').html('Two');
});
it('hide the answers', function() {
this.problem.show();
expect($('#answer_1_1')).toHaveHtml('');
expect($('#answer_1_2')).toHaveHtml('');
return expect($('label[for="input_1_1_1"]')).not.toHaveAttr('correct_answer');
});
it('toggle the show answer button', function() {
this.problem.show();
return expect($('.show')).toHaveValue('Show Answer');
});
return it('remove the showed class from element', function() {
this.problem.show();
return expect(this.problem.element).not.toHaveClass('showed');
});
});
});
describe('save', function() {
beforeEach(function() {
jasmine.stubRequests();
this.problem = new Problem(1, '/problem/url/');
return this.problem.answers = 'foo=1&bar=2';
});
it('log the problem_save event', function() {
this.problem.save();
return expect(Logger.log).toHaveBeenCalledWith('problem_save', 'foo=1&bar=2');
});
it('POST to save problem', function() {
spyOn($, 'postWithPrefix');
this.problem.save();
return expect($.postWithPrefix).toHaveBeenCalledWith('/modx/problem/1/problem_save', 'foo=1&bar=2', jasmine.any(Function));
});
return it('alert to the user', function() {
spyOn(window, 'alert');
spyOn($, 'postWithPrefix').andCallFake(function(url, answers, callback) {
return callback({
success: 'OK'
});
});
this.problem.save();
return expect(window.alert).toHaveBeenCalledWith('Saved');
});
});
return describe('refreshAnswers', function() {
beforeEach(function() {
this.problem = new Problem(1, '/problem/url/');
this.problem.element.html('<textarea class="CodeMirror" />\n<input id="input_1_1" name="input_1_1" class="schematic" value="one" />\n<input id="input_1_2" name="input_1_2" value="two" />\n<input id="input_bogus_3" name="input_bogus_3" value="three" />');
this.stubSchematic = {
update_value: jasmine.createSpy('schematic')
};
this.stubCodeMirror = {
save: jasmine.createSpy('CodeMirror')
};
$('input.schematic').get(0).schematic = this.stubSchematic;
return $('textarea.CodeMirror').get(0).CodeMirror = this.stubCodeMirror;
});
it('update each schematic', function() {
this.problem.refreshAnswers();
return expect(this.stubSchematic.update_value).toHaveBeenCalled();
});
it('update each code block', function() {
this.problem.refreshAnswers();
return expect(this.stubCodeMirror.save).toHaveBeenCalled();
});
return it('serialize all answers', function() {
this.problem.refreshAnswers();
return expect(this.problem.answers).toEqual("input_1_1=one&input_1_2=two");
});
});
});
}).call(this);
describe 'Sequence', ->
beforeEach ->
# Stub MathJax
window.MathJax = { Hub: { Queue: -> } }
spyOn Logger, 'log'
loadFixtures 'sequence.html'
@items = $.parseJSON readFixtures('items.json')
describe 'constructor', ->
beforeEach ->
@sequence = new Sequence '1', @items, 1
it 'set the element', ->
expect(@sequence.element).toEqual $('#sequence_1')
it 'build the navigation', ->
classes = $('#sequence-list li>a').map(-> $(this).attr('class')).get()
elements = $('#sequence-list li>a').map(-> $(this).attr('data-element')).get()
titles = $('#sequence-list li>a>p').map(-> $(this).html()).get()
expect(classes).toEqual ['seq_video_active', 'seq_video_inactive', 'seq_problem_inactive']
expect(elements).toEqual ['1', '2', '3']
expect(titles).toEqual ['Video 1', 'Video 2', 'Sample Problem']
it 'bind the page events', ->
expect(@sequence.element).toHandleWith 'contentChanged', @sequence.toggleArrows
expect($('#sequence-list a')).toHandleWith 'click', @sequence.goto
it 'render the active sequence content', ->
expect($('#seq_content').html()).toEqual 'Video 1'
describe 'toggleArrows', ->
beforeEach ->
@sequence = new Sequence '1', @items, 1
describe 'when the first tab is active', ->
beforeEach ->
@sequence.position = 1
@sequence.toggleArrows()
it 'disable the previous button', ->
expect($('.sequence-nav-buttons .prev a')).toHaveClass 'disabled'
it 'enable the next button', ->
expect($('.sequence-nav-buttons .next a')).not.toHaveClass 'disabled'
expect($('.sequence-nav-buttons .next a')).toHandleWith 'click', @sequence.next
describe 'when the middle tab is active', ->
beforeEach ->
@sequence.position = 2
@sequence.toggleArrows()
it 'enable the previous button', ->
expect($('.sequence-nav-buttons .prev a')).not.toHaveClass 'disabled'
expect($('.sequence-nav-buttons .prev a')).toHandleWith 'click', @sequence.previous
it 'enable the next button', ->
expect($('.sequence-nav-buttons .next a')).not.toHaveClass 'disabled'
expect($('.sequence-nav-buttons .next a')).toHandleWith 'click', @sequence.next
describe 'when the last tab is active', ->
beforeEach ->
@sequence.position = 3
@sequence.toggleArrows()
it 'enable the previous button', ->
expect($('.sequence-nav-buttons .prev a')).not.toHaveClass 'disabled'
expect($('.sequence-nav-buttons .prev a')).toHandleWith 'click', @sequence.previous
it 'disable the next button', ->
expect($('.sequence-nav-buttons .next a')).toHaveClass 'disabled'
describe 'render', ->
beforeEach ->
spyOn $, 'postWithPrefix'
@sequence = new Sequence '1', @items
spyOnEvent @sequence.element, 'contentChanged'
describe 'with a different position than the current one', ->
beforeEach ->
@sequence.render 1
describe 'with no previous position', ->
it 'does not save the new position', ->
expect($.postWithPrefix).not.toHaveBeenCalled()
describe 'with previous position', ->
beforeEach ->
@sequence.position = 2
@sequence.render 1
it 'mark the previous tab as visited', ->
expect($('[data-element="2"]')).toHaveClass 'seq_video_visited'
it 'save the new position', ->
expect($.postWithPrefix).toHaveBeenCalledWith '/modx/sequential/1/goto_position', position: 1
it 'mark new tab as active', ->
expect($('[data-element="1"]')).toHaveClass 'seq_video_active'
it 'render the new content', ->
expect($('#seq_content').html()).toEqual 'Video 1'
it 'update the position', ->
expect(@sequence.position).toEqual 1
it 'trigger contentChanged event', ->
expect('contentChanged').toHaveBeenTriggeredOn @sequence.element
describe 'with the same position as the current one', ->
it 'should not trigger contentChanged event', ->
@sequence.position = 2
@sequence.render 2
expect('contentChanged').not.toHaveBeenTriggeredOn @sequence.element
describe 'goto', ->
beforeEach ->
jasmine.stubRequests()
@sequence = new Sequence '1', @items, 2
$('[data-element="3"]').click()
it 'log the sequence goto event', ->
expect(Logger.log).toHaveBeenCalledWith 'seq_goto', old: 2, new: 3, id: '1'
it 'call render on the right sequence', ->
expect($('#seq_content').html()).toEqual 'Sample Problem'
describe 'next', ->
beforeEach ->
jasmine.stubRequests()
@sequence = new Sequence '1', @items, 2
$('.sequence-nav-buttons .next a').click()
it 'log the next sequence event', ->
expect(Logger.log).toHaveBeenCalledWith 'seq_next', old: 2, new: 3, id: '1'
it 'call render on the next sequence', ->
expect($('#seq_content').html()).toEqual 'Sample Problem'
describe 'previous', ->
beforeEach ->
jasmine.stubRequests()
@sequence = new Sequence '1', @items, 2
$('.sequence-nav-buttons .prev a').click()
it 'log the previous sequence event', ->
expect(Logger.log).toHaveBeenCalledWith 'seq_prev', old: 2, new: 1, id: '1'
it 'call render on the previous sequence', ->
expect($('#seq_content').html()).toEqual 'Video 1'
describe 'link_for', ->
it 'return a link for specific position', ->
sequence = new Sequence '1', @items, 2
expect(sequence.link_for(2)).toBe '[data-element="2"]'
(function() {
describe('Sequence', function() {
beforeEach(function() {
window.MathJax = {
Hub: {
Queue: function() {}
}
};
spyOn(Logger, 'log');
loadFixtures('sequence.html');
return this.items = $.parseJSON(readFixtures('items.json'));
});
describe('constructor', function() {
beforeEach(function() {
return this.sequence = new Sequence('1', this.items, 1);
});
it('set the element', function() {
return expect(this.sequence.element).toEqual($('#sequence_1'));
});
it('build the navigation', function() {
var classes, elements, titles;
classes = $('#sequence-list li>a').map(function() {
return $(this).attr('class');
}).get();
elements = $('#sequence-list li>a').map(function() {
return $(this).attr('data-element');
}).get();
titles = $('#sequence-list li>a>p').map(function() {
return $(this).html();
}).get();
expect(classes).toEqual(['seq_video_active', 'seq_video_inactive', 'seq_problem_inactive']);
expect(elements).toEqual(['1', '2', '3']);
return expect(titles).toEqual(['Video 1', 'Video 2', 'Sample Problem']);
});
it('bind the page events', function() {
expect(this.sequence.element).toHandleWith('contentChanged', this.sequence.toggleArrows);
return expect($('#sequence-list a')).toHandleWith('click', this.sequence.goto);
});
return it('render the active sequence content', function() {
return expect($('#seq_content').html()).toEqual('Video 1');
});
});
describe('toggleArrows', function() {
beforeEach(function() {
return this.sequence = new Sequence('1', this.items, 1);
});
describe('when the first tab is active', function() {
beforeEach(function() {
this.sequence.position = 1;
return this.sequence.toggleArrows();
});
it('disable the previous button', function() {
return expect($('.sequence-nav-buttons .prev a')).toHaveClass('disabled');
});
return it('enable the next button', function() {
expect($('.sequence-nav-buttons .next a')).not.toHaveClass('disabled');
return expect($('.sequence-nav-buttons .next a')).toHandleWith('click', this.sequence.next);
});
});
describe('when the middle tab is active', function() {
beforeEach(function() {
this.sequence.position = 2;
return this.sequence.toggleArrows();
});
it('enable the previous button', function() {
expect($('.sequence-nav-buttons .prev a')).not.toHaveClass('disabled');
return expect($('.sequence-nav-buttons .prev a')).toHandleWith('click', this.sequence.previous);
});
return it('enable the next button', function() {
expect($('.sequence-nav-buttons .next a')).not.toHaveClass('disabled');
return expect($('.sequence-nav-buttons .next a')).toHandleWith('click', this.sequence.next);
});
});
return describe('when the last tab is active', function() {
beforeEach(function() {
this.sequence.position = 3;
return this.sequence.toggleArrows();
});
it('enable the previous button', function() {
expect($('.sequence-nav-buttons .prev a')).not.toHaveClass('disabled');
return expect($('.sequence-nav-buttons .prev a')).toHandleWith('click', this.sequence.previous);
});
return it('disable the next button', function() {
return expect($('.sequence-nav-buttons .next a')).toHaveClass('disabled');
});
});
});
describe('render', function() {
beforeEach(function() {
spyOn($, 'postWithPrefix');
this.sequence = new Sequence('1', this.items);
return spyOnEvent(this.sequence.element, 'contentChanged');
});
describe('with a different position than the current one', function() {
beforeEach(function() {
return this.sequence.render(1);
});
describe('with no previous position', function() {
return it('does not save the new position', function() {
return expect($.postWithPrefix).not.toHaveBeenCalled();
});
});
describe('with previous position', function() {
beforeEach(function() {
this.sequence.position = 2;
return this.sequence.render(1);
});
it('mark the previous tab as visited', function() {
return expect($('[data-element="2"]')).toHaveClass('seq_video_visited');
});
return it('save the new position', function() {
return expect($.postWithPrefix).toHaveBeenCalledWith('/modx/sequential/1/goto_position', {
position: 1
});
});
});
it('mark new tab as active', function() {
return expect($('[data-element="1"]')).toHaveClass('seq_video_active');
});
it('render the new content', function() {
return expect($('#seq_content').html()).toEqual('Video 1');
});
it('update the position', function() {
return expect(this.sequence.position).toEqual(1);
});
return it('trigger contentChanged event', function() {
return expect('contentChanged').toHaveBeenTriggeredOn(this.sequence.element);
});
});
return describe('with the same position as the current one', function() {
return it('should not trigger contentChanged event', function() {
this.sequence.position = 2;
this.sequence.render(2);
return expect('contentChanged').not.toHaveBeenTriggeredOn(this.sequence.element);
});
});
});
describe('goto', function() {
beforeEach(function() {
jasmine.stubRequests();
this.sequence = new Sequence('1', this.items, 2);
return $('[data-element="3"]').click();
});
it('log the sequence goto event', function() {
return expect(Logger.log).toHaveBeenCalledWith('seq_goto', {
old: 2,
"new": 3,
id: '1'
});
});
return it('call render on the right sequence', function() {
return expect($('#seq_content').html()).toEqual('Sample Problem');
});
});
describe('next', function() {
beforeEach(function() {
jasmine.stubRequests();
this.sequence = new Sequence('1', this.items, 2);
return $('.sequence-nav-buttons .next a').click();
});
it('log the next sequence event', function() {
return expect(Logger.log).toHaveBeenCalledWith('seq_next', {
old: 2,
"new": 3,
id: '1'
});
});
return it('call render on the next sequence', function() {
return expect($('#seq_content').html()).toEqual('Sample Problem');
});
});
describe('previous', function() {
beforeEach(function() {
jasmine.stubRequests();
this.sequence = new Sequence('1', this.items, 2);
return $('.sequence-nav-buttons .prev a').click();
});
it('log the previous sequence event', function() {
return expect(Logger.log).toHaveBeenCalledWith('seq_prev', {
old: 2,
"new": 1,
id: '1'
});
});
return it('call render on the previous sequence', function() {
return expect($('#seq_content').html()).toEqual('Video 1');
});
});
return describe('link_for', function() {
return it('return a link for specific position', function() {
var sequence;
sequence = new Sequence('1', this.items, 2);
return expect(sequence.link_for(2)).toBe('[data-element="2"]');
});
});
});
}).call(this);
describe 'Tab', ->
beforeEach ->
loadFixtures 'tab.html'
@items = $.parseJSON readFixtures('items.json')
describe 'constructor', ->
beforeEach ->
spyOn($.fn, 'tabs')
@tab = new Tab 1, @items
it 'set the element', ->
expect(@tab.element).toEqual $('#tab_1')
it 'build the tabs', ->
links = $('.navigation li>a').map(-> $(this).attr('href')).get()
expect(links).toEqual ['#tab-1-0', '#tab-1-1', '#tab-1-2']
it 'build the container', ->
containers = $('section').map(-> $(this).attr('id')).get()
expect(containers).toEqual ['tab-1-0', 'tab-1-1', 'tab-1-2']
it 'bind the tabs', ->
expect($.fn.tabs).toHaveBeenCalledWith show: @tab.onShow
describe 'onShow', ->
beforeEach ->
@tab = new Tab 1, @items
$('[href="#tab-1-0"]').click()
it 'replace content in the container', ->
$('[href="#tab-1-1"]').click()
expect($('#tab-1-0').html()).toEqual ''
expect($('#tab-1-1').html()).toEqual 'Video 2'
expect($('#tab-1-2').html()).toEqual ''
it 'trigger contentChanged event on the element', ->
spyOnEvent @tab.element, 'contentChanged'
$('[href="#tab-1-1"]').click()
expect('contentChanged').toHaveBeenTriggeredOn @tab.element
(function() {
describe('Tab', function() {
beforeEach(function() {
loadFixtures('tab.html');
return this.items = $.parseJSON(readFixtures('items.json'));
});
describe('constructor', function() {
beforeEach(function() {
spyOn($.fn, 'tabs');
return this.tab = new Tab(1, this.items);
});
it('set the element', function() {
return expect(this.tab.element).toEqual($('#tab_1'));
});
it('build the tabs', function() {
var links;
links = $('.navigation li>a').map(function() {
return $(this).attr('href');
}).get();
return expect(links).toEqual(['#tab-1-0', '#tab-1-1', '#tab-1-2']);
});
it('build the container', function() {
var containers;
containers = $('section').map(function() {
return $(this).attr('id');
}).get();
return expect(containers).toEqual(['tab-1-0', 'tab-1-1', 'tab-1-2']);
});
return it('bind the tabs', function() {
return expect($.fn.tabs).toHaveBeenCalledWith({
show: this.tab.onShow
});
});
});
return describe('onShow', function() {
beforeEach(function() {
this.tab = new Tab(1, this.items);
return $('[href="#tab-1-0"]').click();
});
it('replace content in the container', function() {
$('[href="#tab-1-1"]').click();
expect($('#tab-1-0').html()).toEqual('');
expect($('#tab-1-1').html()).toEqual('Video 2');
return expect($('#tab-1-2').html()).toEqual('');
});
return it('trigger contentChanged event on the element', function() {
spyOnEvent(this.tab.element, 'contentChanged');
$('[href="#tab-1-1"]').click();
return expect('contentChanged').toHaveBeenTriggeredOn(this.tab.element);
});
});
});
}).call(this);
describe 'VideoCaption', ->
beforeEach ->
@player = jasmine.stubVideoPlayer @
afterEach ->
YT.Player = undefined
$.fn.scrollTo.reset()
describe 'constructor', ->
beforeEach ->
spyOn($, 'getWithPrefix').andCallThrough()
@caption = new VideoCaption @player, 'def456'
it 'set the player', ->
expect(@caption.player).toEqual @player
it 'set the youtube id', ->
expect(@caption.youtubeId).toEqual 'def456'
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($.getWithPrefix).toHaveBeenCalledWith @caption.captionURL(), jasmine.any(Function)
it 'render the caption', ->
expect($('.subtitles').html()).toMatch new RegExp('''
<li data-index="0" data-start="0">Caption at 0</li>
<li data-index="1" data-start="10000">Caption at 10000</li>
<li data-index="2" data-start="20000">Caption at 20000</li>
<li data-index="3" data-start="30000">Caption at 30000</li>
<li data-index="4" data-start="40000">Caption at 40000</li>
<li data-index="5" data-start="50000">Caption at 50000</li>
<li data-index="6" data-start="60000">Caption at 60000</li>
<li data-index="7" data-start="70000">Caption at 70000</li>
<li data-index="8" data-start="80000">Caption at 80000</li>
<li data-index="9" data-start="90000">Caption at 90000</li>
<li data-index="10" data-start="100000">Caption at 100000</li>
<li data-index="11" data-start="110000">Caption at 110000</li>
<li data-index="12" data-start="120000">Caption at 120000</li>
'''.replace(/\n/g, ''))
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 'bind window resize event', ->
expect($(window)).toHandleWith 'resize', @caption.onWindowResize
it 'bind player resize event', ->
expect($(@player)).toHandleWith 'resize', @caption.onWindowResize
it 'bind player updatePlayTime event', ->
expect($(@player)).toHandleWith 'updatePlayTime', @caption.onUpdatePlayTime
it 'bind the hide caption button', ->
expect($('.hide-subtitles')).toHandleWith 'click', @caption.toggle
it 'bind the mouse movement', ->
expect($('.subtitles')).toHandleWith 'mouseenter', @caption.onMouseEnter
expect($('.subtitles')).toHandleWith 'mouseleave', @caption.onMouseLeave
expect($('.subtitles')).toHandleWith 'mousemove', @caption.onMovement
expect($('.subtitles')).toHandleWith 'mousewheel', @caption.onMovement
expect($('.subtitles')).toHandleWith 'DOMMouseScroll', @caption.onMovement
describe 'mouse movement', ->
beforeEach ->
spyOn(window, 'setTimeout').andReturn 100
spyOn window, 'clearTimeout'
@caption = new VideoCaption @player, 'def456'
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 ->
spyOn(@player, 'isPlaying').andReturn 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 ->
spyOn(@player, 'isPlaying').andReturn false
$('.subtitles').trigger jQuery.Event 'mouseout'
it 'does not scroll the caption', ->
expect($.fn.scrollTo).not.toHaveBeenCalled()
describe 'search', ->
beforeEach ->
@caption = new VideoCaption @player, 'def456'
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(120000)).toEqual 12
expect(@caption.search(120001)).toEqual 12
describe 'onUpdatePlayTime', ->
beforeEach ->
@caption = new VideoCaption @player, 'def456'
describe 'when the video speed is 1.0x', ->
beforeEach ->
@video.setSpeed '1.0'
@caption.onUpdatePlayTime {}, 25.000
it 'search the caption based on time', ->
expect(@caption.currentIndex).toEqual 2
describe 'when the video speed is not 1.0x', ->
beforeEach ->
@video.setSpeed '0.75'
@caption.onUpdatePlayTime {}, 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.onUpdatePlayTime {}, 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.onUpdatePlayTime {}, 15.000
it 'does not change current subtitle', ->
expect($('.subtitles li[data-index=1]')).toHaveClass 'current'
describe 'onWindowResize', ->
beforeEach ->
@caption = new VideoCaption @player, 'def456'
$('.subtitles li[data-index=1]').addClass 'current'
@caption.onWindowResize()
it 'set the height of caption container', ->
expect(parseInt($('.subtitles').css('maxHeight'))).toEqual $('.video-wrapper').height()
it 'set the height of caption spacing', ->
expect(parseInt($('.subtitles .spacing:first').css('height'))).toEqual(
$('.video-wrapper').height() / 2 - $('.subtitles li:not(.spacing):first').height() / 2)
expect(parseInt($('.subtitles .spacing:last').css('height'))).toEqual(
$('.video-wrapper').height() / 2 - $('.subtitles li:not(.spacing):last').height() / 2)
it 'scroll caption to new position', ->
expect($.fn.scrollTo).toHaveBeenCalled()
describe 'scrollCaption', ->
beforeEach ->
@caption = new VideoCaption @player, 'def456'
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', ->
expect($.fn.scrollTo).toHaveBeenCalledWith $('.subtitles .current:first', @player.element),
offset: - ($('.video-wrapper').height() / 2 - $('.subtitles .current:first').height() / 2)
describe 'seekPlayer', ->
beforeEach ->
@caption = new VideoCaption @player, 'def456'
@time = null
$(@player).bind 'seek', (event, time) => @time = time
describe 'when the video speed is 1.0x', ->
beforeEach ->
@video.setSpeed '1.0'
$('.subtitles li[data-start="30000"]').click()
it 'trigger seek event with the correct time', ->
expect(@time).toEqual 30.000
describe 'when the video speed is not 1.0x', ->
beforeEach ->
@video.setSpeed '0.75'
$('.subtitles li[data-start="30000"]').click()
it 'trigger seek event with the correct time', ->
expect(@time).toEqual 40.000
describe 'toggle', ->
beforeEach ->
@caption = new VideoCaption @player, 'def456'
$('.subtitles li[data-index=1]').addClass 'current'
describe 'when the caption is visible', ->
beforeEach ->
@player.element.removeClass 'closed'
@caption.toggle jQuery.Event('click')
it 'hide the caption', ->
expect(@player.element).toHaveClass 'closed'
describe 'when the caption is hidden', ->
beforeEach ->
@player.element.addClass 'closed'
@caption.toggle jQuery.Event('click')
it 'show the caption', ->
expect(@player.element).not.toHaveClass 'closed'
it 'scroll the caption', ->
expect($.fn.scrollTo).toHaveBeenCalled()
(function() {
describe('VideoCaption', function() {
beforeEach(function() {
return this.player = jasmine.stubVideoPlayer(this);
});
afterEach(function() {
YT.Player = void 0;
return $.fn.scrollTo.reset();
});
describe('constructor', function() {
beforeEach(function() {
spyOn($, 'getWithPrefix').andCallThrough();
return this.caption = new VideoCaption(this.player, 'def456');
});
it('set the player', function() {
return expect(this.caption.player).toEqual(this.player);
});
it('set the youtube id', function() {
return expect(this.caption.youtubeId).toEqual('def456');
});
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() {
return expect($.getWithPrefix).toHaveBeenCalledWith(this.caption.captionURL(), jasmine.any(Function));
});
it('render the caption', function() {
return expect($('.subtitles').html()).toMatch(new RegExp('<li data-index="0" data-start="0">Caption at 0</li>\n<li data-index="1" data-start="10000">Caption at 10000</li>\n<li data-index="2" data-start="20000">Caption at 20000</li>\n<li data-index="3" data-start="30000">Caption at 30000</li>\n<li data-index="4" data-start="40000">Caption at 40000</li>\n<li data-index="5" data-start="50000">Caption at 50000</li>\n<li data-index="6" data-start="60000">Caption at 60000</li>\n<li data-index="7" data-start="70000">Caption at 70000</li>\n<li data-index="8" data-start="80000">Caption at 80000</li>\n<li data-index="9" data-start="90000">Caption at 90000</li>\n<li data-index="10" data-start="100000">Caption at 100000</li>\n<li data-index="11" data-start="110000">Caption at 110000</li>\n<li data-index="12" data-start="120000">Caption at 120000</li>'.replace(/\n/g, '')));
});
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('bind window resize event', function() {
return expect($(window)).toHandleWith('resize', this.caption.onWindowResize);
});
it('bind player resize event', function() {
return expect($(this.player)).toHandleWith('resize', this.caption.onWindowResize);
});
it('bind player updatePlayTime event', function() {
return expect($(this.player)).toHandleWith('updatePlayTime', this.caption.onUpdatePlayTime);
});
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('mouseenter', this.caption.onMouseEnter);
expect($('.subtitles')).toHandleWith('mouseleave', 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('mouse movement', function() {
beforeEach(function() {
spyOn(window, 'setTimeout').andReturn(100);
spyOn(window, 'clearTimeout');
return this.caption = new VideoCaption(this.player, 'def456');
});
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() {
spyOn(this.player, 'isPlaying').andReturn(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() {
spyOn(this.player, 'isPlaying').andReturn(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() {
return this.caption = new VideoCaption(this.player, 'def456');
});
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(120000)).toEqual(12);
return expect(this.caption.search(120001)).toEqual(12);
});
});
describe('onUpdatePlayTime', function() {
beforeEach(function() {
return this.caption = new VideoCaption(this.player, 'def456');
});
describe('when the video speed is 1.0x', function() {
beforeEach(function() {
this.video.setSpeed('1.0');
return this.caption.onUpdatePlayTime({}, 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.video.setSpeed('0.75');
return this.caption.onUpdatePlayTime({}, 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.onUpdatePlayTime({}, 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.onUpdatePlayTime({}, 15.000);
});
return it('does not change current subtitle', function() {
return expect($('.subtitles li[data-index=1]')).toHaveClass('current');
});
});
});
describe('onWindowResize', function() {
beforeEach(function() {
this.caption = new VideoCaption(this.player, 'def456');
$('.subtitles li[data-index=1]').addClass('current');
return this.caption.onWindowResize();
});
it('set the height of caption container', function() {
return expect(parseInt($('.subtitles').css('maxHeight'))).toEqual($('.video-wrapper').height());
});
it('set the height of caption spacing', function() {
expect(parseInt($('.subtitles .spacing:first').css('height'))).toEqual($('.video-wrapper').height() / 2 - $('.subtitles li:not(.spacing):first').height() / 2);
return expect(parseInt($('.subtitles .spacing:last').css('height'))).toEqual($('.video-wrapper').height() / 2 - $('.subtitles li:not(.spacing):last').height() / 2);
});
return it('scroll caption to new position', function() {
return expect($.fn.scrollTo).toHaveBeenCalled();
});
});
describe('scrollCaption', function() {
beforeEach(function() {
return this.caption = new VideoCaption(this.player, 'def456');
});
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() {
return expect($.fn.scrollTo).toHaveBeenCalledWith($('.subtitles .current:first', this.player.element), {
offset: -($('.video-wrapper').height() / 2 - $('.subtitles .current:first').height() / 2)
});
});
});
});
});
describe('seekPlayer', function() {
beforeEach(function() {
var _this = this;
this.caption = new VideoCaption(this.player, 'def456');
this.time = null;
return $(this.player).bind('seek', function(event, time) {
return _this.time = time;
});
});
describe('when the video speed is 1.0x', function() {
beforeEach(function() {
this.video.setSpeed('1.0');
return $('.subtitles li[data-start="30000"]').click();
});
return it('trigger seek event with the correct time', function() {
return expect(this.time).toEqual(30.000);
});
});
return describe('when the video speed is not 1.0x', function() {
beforeEach(function() {
this.video.setSpeed('0.75');
return $('.subtitles li[data-start="30000"]').click();
});
return it('trigger seek event with the correct time', function() {
return expect(this.time).toEqual(40.000);
});
});
});
return describe('toggle', function() {
beforeEach(function() {
this.caption = new VideoCaption(this.player, 'def456');
return $('.subtitles li[data-index=1]').addClass('current');
});
describe('when the caption is visible', function() {
beforeEach(function() {
this.player.element.removeClass('closed');
return this.caption.toggle(jQuery.Event('click'));
});
return it('hide the caption', function() {
return expect(this.player.element).toHaveClass('closed');
});
});
return describe('when the caption is hidden', function() {
beforeEach(function() {
this.player.element.addClass('closed');
return this.caption.toggle(jQuery.Event('click'));
});
it('show the caption', function() {
return expect(this.player.element).not.toHaveClass('closed');
});
return it('scroll the caption', function() {
return expect($.fn.scrollTo).toHaveBeenCalled();
});
});
});
});
}).call(this);
describe 'VideoControl', ->
beforeEach ->
@player = jasmine.stubVideoPlayer @
describe 'constructor', ->
beforeEach ->
@control = new VideoControl @player
it 'render the video controls', ->
expect($('.video-controls').html()).toContain '''
<div class="slider"></div>
<div>
<ul class="vcr">
<li><a class="video_control play">Play</a></li>
<li>
<div class="vidtime">0:00 / 0:00</div>
</li>
</ul>
<div class="secondary-controls">
<a href="#" class="add-fullscreen" title="Fill browser">Fill Browser</a>
</div>
</div>
'''
it 'bind player events', ->
expect($(@player)).toHandleWith 'play', @control.onPlay
expect($(@player)).toHandleWith 'pause', @control.onPause
expect($(@player)).toHandleWith 'ended', @control.onPause
it 'bind the playback button', ->
expect($('.video_control')).toHandleWith 'click', @control.togglePlayback
describe 'onPlay', ->
beforeEach ->
@control = new VideoControl @player
@control.onPlay()
it 'switch playback button to play state', ->
expect($('.video_control')).not.toHaveClass 'play'
expect($('.video_control')).toHaveClass 'pause'
expect($('.video_control')).toHaveHtml 'Pause'
describe 'onPause', ->
beforeEach ->
@control = new VideoControl @player
@control.onPause()
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 VideoControl @player
describe 'when the video is playing', ->
beforeEach ->
spyOn(@player, 'isPlaying').andReturn true
spyOnEvent @player, 'pause'
@control.togglePlayback jQuery.Event('click')
it 'trigger the pause event', ->
expect('pause').toHaveBeenTriggeredOn @player
describe 'when the video is paused', ->
beforeEach ->
spyOn(@player, 'isPlaying').andReturn false
spyOnEvent @player, 'play'
@control.togglePlayback jQuery.Event('click')
it 'trigger the play event', ->
expect('play').toHaveBeenTriggeredOn @player
(function() {
describe('VideoControl', function() {
beforeEach(function() {
return this.player = jasmine.stubVideoPlayer(this);
});
describe('constructor', function() {
beforeEach(function() {
return this.control = new VideoControl(this.player);
});
it('render the video controls', function() {
return expect($('.video-controls').html()).toContain('<div class="slider"></div>\n<div>\n <ul class="vcr">\n <li><a class="video_control play">Play</a></li>\n <li>\n <div class="vidtime">0:00 / 0:00</div>\n </li>\n </ul>\n <div class="secondary-controls">\n <a href="#" class="add-fullscreen" title="Fill browser">Fill Browser</a>\n </div>\n</div>');
});
it('bind player events', function() {
expect($(this.player)).toHandleWith('play', this.control.onPlay);
expect($(this.player)).toHandleWith('pause', this.control.onPause);
return expect($(this.player)).toHandleWith('ended', this.control.onPause);
});
return it('bind the playback button', function() {
return expect($('.video_control')).toHandleWith('click', this.control.togglePlayback);
});
});
describe('onPlay', function() {
beforeEach(function() {
this.control = new VideoControl(this.player);
return this.control.onPlay();
});
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('onPause', function() {
beforeEach(function() {
this.control = new VideoControl(this.player);
return this.control.onPause();
});
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 VideoControl(this.player);
});
describe('when the video is playing', function() {
beforeEach(function() {
spyOn(this.player, 'isPlaying').andReturn(true);
spyOnEvent(this.player, 'pause');
return this.control.togglePlayback(jQuery.Event('click'));
});
return it('trigger the pause event', function() {
return expect('pause').toHaveBeenTriggeredOn(this.player);
});
});
return describe('when the video is paused', function() {
beforeEach(function() {
spyOn(this.player, 'isPlaying').andReturn(false);
spyOnEvent(this.player, 'play');
return this.control.togglePlayback(jQuery.Event('click'));
});
return it('trigger the play event', function() {
return expect('play').toHaveBeenTriggeredOn(this.player);
});
});
});
});
}).call(this);
describe 'VideoPlayer', ->
beforeEach ->
jasmine.stubVideoPlayer @
afterEach ->
YT.Player = undefined
describe 'constructor', ->
beforeEach ->
spyOn window, 'VideoControl'
spyOn YT, 'Player'
$.fn.qtip.andCallFake ->
$(this).data('qtip', true)
$('.video').append $('<div class="hide-subtitles" />')
@player = new VideoPlayer @video
it 'instanticate current time to zero', ->
expect(@player.currentTime).toEqual 0
it 'set the element', ->
expect(@player.element).toBe '#video_example'
it 'create video control', ->
expect(window.VideoControl).toHaveBeenCalledWith @player
it 'create video caption', ->
expect(window.VideoCaption).toHaveBeenCalledWith @player, 'def456'
it 'create video speed control', ->
expect(window.VideoSpeedControl).toHaveBeenCalledWith @player, ['0.75', '1.0']
it 'create video progress slider', ->
expect(window.VideoProgressSlider).toHaveBeenCalledWith @player
it 'create Youtube player', ->
expect(YT.Player).toHaveBeenCalledWith 'example'
playerVars:
controls: 0
wmode: 'transparent'
rel: 0
showinfo: 0
enablejsapi: 1
videoId: 'def456'
events:
onReady: @player.onReady
onStateChange: @player.onStateChange
it 'bind to seek event', ->
expect($(@player)).toHandleWith 'seek', @player.onSeek
it 'bind to updatePlayTime event', ->
expect($(@player)).toHandleWith 'updatePlayTime', @player.onUpdatePlayTime
it 'bidn to speedChange event', ->
expect($(@player)).toHandleWith 'speedChange', @player.onSpeedChange
it 'bind to play event', ->
expect($(@player)).toHandleWith 'play', @player.onPlay
it 'bind to paused event', ->
expect($(@player)).toHandleWith 'pause', @player.onPause
it 'bind to ended event', ->
expect($(@player)).toHandleWith 'ended', @player.onPause
it 'bind to key press', ->
expect($(document)).toHandleWith 'keyup', @player.bindExitFullScreen
it 'bind to fullscreen switching button', ->
expect($('.add-fullscreen')).toHandleWith 'click', @player.toggleFullScreen
describe 'when not on a touch based device', ->
it 'add the tooltip to fullscreen and subtitle button', ->
expect($('.add-fullscreen')).toHaveData 'qtip'
expect($('.hide-subtitles')).toHaveData 'qtip'
describe 'onReady', ->
beforeEach ->
@video.embed()
@player = @video.player
spyOnEvent @player, 'ready'
spyOnEvent @player, 'updatePlayTime'
@player.onReady()
it 'reset the progress to zero', ->
expect('updatePlayTime').toHaveBeenTriggeredOn @player
it 'trigger ready event on the player', ->
expect('ready').toHaveBeenTriggeredOn @player
describe 'when not on a touch based device', ->
beforeEach ->
window.onTouchBasedDevice = -> false
spyOn @player, 'play'
@player.onReady()
it 'autoplay the first video', ->
expect(@player.play).toHaveBeenCalled()
describe 'when on a touch based device', ->
beforeEach ->
window.onTouchBasedDevice = -> true
spyOn @player, 'play'
@player.onReady()
it 'does not autoplay the first video', ->
expect(@player.play).not.toHaveBeenCalled()
describe 'onStateChange', ->
beforeEach ->
@player = new VideoPlayer @video
describe 'when the video is playing', ->
beforeEach ->
spyOnEvent @player, 'play'
@player.onStateChange data: YT.PlayerState.PLAYING
it 'trigger play event', ->
expect('play').toHaveBeenTriggeredOn @player
describe 'when the video is paused', ->
beforeEach ->
spyOnEvent @player, 'pause'
@player.onStateChange data: YT.PlayerState.PAUSED
it 'trigger pause event', ->
expect('pause').toHaveBeenTriggeredOn @player
describe 'when the video is ended', ->
beforeEach ->
spyOnEvent @player, 'ended'
@player.onStateChange data: YT.PlayerState.ENDED
it 'trigger ended event', ->
expect('ended').toHaveBeenTriggeredOn @player
describe 'onPlay', ->
beforeEach ->
@player = new VideoPlayer @video
@anotherPlayer = jasmine.createSpyObj 'AnotherPlayer', ['pauseVideo']
window.player = @anotherPlayer
spyOn Logger, 'log'
spyOn(window, 'setInterval').andReturn 100
@player.player.getVideoEmbedCode.andReturn 'embedCode'
@player.onPlay()
it 'log the play_video event', ->
expect(Logger.log).toHaveBeenCalledWith 'play_video', id: @player.currentTime, code: 'embedCode'
it 'pause other video player', ->
expect(@anotherPlayer.pauseVideo).toHaveBeenCalled()
it 'set current video player as active player', ->
expect(window.player).toEqual @player.player
it 'set update interval', ->
expect(window.setInterval).toHaveBeenCalledWith @player.update, 200
expect(@player.player.interval).toEqual 100
describe 'onPause', ->
beforeEach ->
@player = new VideoPlayer @video
window.player = @player.player
spyOn Logger, 'log'
spyOn window, 'clearInterval'
@player.player.interval = 100
@player.player.getVideoEmbedCode.andReturn 'embedCode'
@player.onPause()
it 'log the pause_video event', ->
expect(Logger.log).toHaveBeenCalledWith 'pause_video', id: @player.currentTime, code: 'embedCode'
it 'set current video player as inactive', ->
expect(window.player).toBeNull()
it 'clear update interval', ->
expect(window.clearInterval).toHaveBeenCalledWith 100
expect(@player.player.interval).toBeNull()
describe 'onSeek', ->
beforeEach ->
@player = new VideoPlayer @video
spyOn window, 'clearInterval'
@player.player.interval = 100
@player.onSeek {}, 60
it 'seek the player', ->
expect(@player.player.seekTo).toHaveBeenCalledWith 60, true
describe 'when the player is playing', ->
beforeEach ->
@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.player.getPlayerState.andReturn YT.PlayerState.PAUSED
spyOnEvent @player, 'updatePlayTime'
@player.onSeek {}, 60
it 'set the current time', ->
expect(@player.currentTime).toEqual 60
it 'trigger updatePlayTime event', ->
expect('updatePlayTime').toHaveBeenTriggeredOn @player
describe 'onSpeedChange', ->
beforeEach ->
@player = new VideoPlayer @video
@player.currentTime = 60
spyOn(@video, 'setSpeed').andCallThrough()
describe 'always', ->
beforeEach ->
@player.onSpeedChange {}, '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'
describe 'when the video is playing', ->
beforeEach ->
@player.player.getPlayerState.andReturn YT.PlayerState.PLAYING
spyOnEvent @player, 'updatePlayTime'
@player.onSpeedChange {}, '0.75'
it 'load the video', ->
expect(@player.player.loadVideoById).toHaveBeenCalledWith 'abc123', '80.000'
it 'trigger updatePlayTime event', ->
expect('updatePlayTime').toHaveBeenTriggeredOn @player
describe 'when the video is not playing', ->
beforeEach ->
@player.player.getPlayerState.andReturn YT.PlayerState.PAUSED
spyOnEvent @player, 'updatePlayTime'
@player.onSpeedChange {}, '0.75'
it 'cue the video', ->
expect(@player.player.cueVideoById).toHaveBeenCalledWith 'abc123', '80.000'
it 'trigger updatePlayTime event', ->
expect('updatePlayTime').toHaveBeenTriggeredOn @player
describe 'update', ->
beforeEach ->
@player = new VideoPlayer @video
spyOnEvent @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('updatePlayTime').not.toHaveBeenTriggeredOn @player
describe 'when the current time is available from the player', ->
beforeEach ->
@player.player.getCurrentTime.andReturn 60
@player.update()
it 'trigger updatePlayTime event', ->
expect('updatePlayTime').toHaveBeenTriggeredOn @player
describe 'onUpdatePlaytime', ->
beforeEach ->
@player = new VideoPlayer @video
spyOn(@video, 'getDuration').andReturn 1800
@player.onUpdatePlayTime {}, 60
it 'update the video playback time', ->
expect($('.vidtime')).toHaveHtml '1:00 / 30:00'
describe 'toggleFullScreen', ->
beforeEach ->
@player = new VideoPlayer @video
describe 'when the video player is not full screen', ->
beforeEach ->
@player.element.removeClass 'fullscreen'
spyOnEvent @player, 'resize'
@player.toggleFullScreen(jQuery.Event("click"))
it 'replace the full screen button tooltip', ->
expect($('.add-fullscreen')).toHaveAttr 'title', 'Exit fill browser'
it 'add a new exit from fullscreen button', ->
expect(@player.element).toContain 'a.exit'
it 'add the fullscreen class', ->
expect(@player.element).toHaveClass 'fullscreen'
it 'trigger resize event', ->
expect('resize').toHaveBeenTriggeredOn @player
describe 'when the video player already full screen', ->
beforeEach ->
@player.element.addClass 'fullscreen'
spyOnEvent @player, 'resize'
@player.toggleFullScreen(jQuery.Event("click"))
it 'replace the full screen button tooltip', ->
expect($('.add-fullscreen')).toHaveAttr 'title', 'Fill browser'
it 'remove exit full screen button', ->
expect(@player.element).not.toContain 'a.exit'
it 'remove the fullscreen class', ->
expect(@player.element).not.toHaveClass 'fullscreen'
it 'trigger resize event', ->
expect('resize').toHaveBeenTriggeredOn @player
describe 'play', ->
beforeEach ->
@player = new VideoPlayer @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 ->
@player = new VideoPlayer @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 ->
@player = new VideoPlayer @video
@player.pause()
it 'delegate to the Youtube player', ->
expect(@player.player.pauseVideo).toHaveBeenCalled()
describe 'duration', ->
beforeEach ->
@player = new VideoPlayer @video
spyOn @video, 'getDuration'
@player.duration()
it 'delegate to the video', ->
expect(@video.getDuration).toHaveBeenCalled()
describe 'currentSpeed', ->
beforeEach ->
@player = new VideoPlayer @video
@video.speed = '3.0'
it 'delegate to the video', ->
expect(@player.currentSpeed()).toEqual '3.0'
(function() {
describe('VideoPlayer', function() {
beforeEach(function() {
return jasmine.stubVideoPlayer(this);
});
afterEach(function() {
return YT.Player = void 0;
});
describe('constructor', function() {
beforeEach(function() {
spyOn(window, 'VideoControl');
spyOn(YT, 'Player');
$.fn.qtip.andCallFake(function() {
return $(this).data('qtip', true);
});
$('.video').append($('<div class="hide-subtitles" />'));
return this.player = new VideoPlayer(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.element).toBe('#video_example');
});
it('create video control', function() {
return expect(window.VideoControl).toHaveBeenCalledWith(this.player);
});
it('create video caption', function() {
return expect(window.VideoCaption).toHaveBeenCalledWith(this.player, 'def456');
});
it('create video speed control', function() {
return expect(window.VideoSpeedControl).toHaveBeenCalledWith(this.player, ['0.75', '1.0']);
});
it('create video progress slider', function() {
return expect(window.VideoProgressSlider).toHaveBeenCalledWith(this.player);
});
it('create Youtube player', function() {
return expect(YT.Player).toHaveBeenCalledWith('example', {
playerVars: {
controls: 0,
wmode: 'transparent',
rel: 0,
showinfo: 0,
enablejsapi: 1
},
videoId: 'def456',
events: {
onReady: this.player.onReady,
onStateChange: this.player.onStateChange
}
});
});
it('bind to seek event', function() {
return expect($(this.player)).toHandleWith('seek', this.player.onSeek);
});
it('bind to updatePlayTime event', function() {
return expect($(this.player)).toHandleWith('updatePlayTime', this.player.onUpdatePlayTime);
});
it('bidn to speedChange event', function() {
return expect($(this.player)).toHandleWith('speedChange', this.player.onSpeedChange);
});
it('bind to play event', function() {
return expect($(this.player)).toHandleWith('play', this.player.onPlay);
});
it('bind to paused event', function() {
return expect($(this.player)).toHandleWith('pause', this.player.onPause);
});
it('bind to ended event', function() {
return expect($(this.player)).toHandleWith('ended', this.player.onPause);
});
it('bind to key press', function() {
return expect($(document)).toHandleWith('keyup', this.player.bindExitFullScreen);
});
it('bind to fullscreen switching button', function() {
return expect($('.add-fullscreen')).toHandleWith('click', this.player.toggleFullScreen);
});
return describe('when not on a touch based device', function() {
return it('add the tooltip to fullscreen and subtitle button', function() {
expect($('.add-fullscreen')).toHaveData('qtip');
return expect($('.hide-subtitles')).toHaveData('qtip');
});
});
});
describe('onReady', function() {
beforeEach(function() {
this.video.embed();
this.player = this.video.player;
spyOnEvent(this.player, 'ready');
spyOnEvent(this.player, 'updatePlayTime');
return this.player.onReady();
});
it('reset the progress to zero', function() {
return expect('updatePlayTime').toHaveBeenTriggeredOn(this.player);
});
it('trigger ready event on the player', function() {
return expect('ready').toHaveBeenTriggeredOn(this.player);
});
describe('when not on a touch based device', function() {
beforeEach(function() {
window.onTouchBasedDevice = function() {
return false;
};
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 = function() {
return 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() {
return this.player = new VideoPlayer(this.video);
});
describe('when the video is playing', function() {
beforeEach(function() {
spyOnEvent(this.player, 'play');
return this.player.onStateChange({
data: YT.PlayerState.PLAYING
});
});
return it('trigger play event', function() {
return expect('play').toHaveBeenTriggeredOn(this.player);
});
});
describe('when the video is paused', function() {
beforeEach(function() {
spyOnEvent(this.player, 'pause');
return this.player.onStateChange({
data: YT.PlayerState.PAUSED
});
});
return it('trigger pause event', function() {
return expect('pause').toHaveBeenTriggeredOn(this.player);
});
});
return describe('when the video is ended', function() {
beforeEach(function() {
spyOnEvent(this.player, 'ended');
return this.player.onStateChange({
data: YT.PlayerState.ENDED
});
});
return it('trigger ended event', function() {
return expect('ended').toHaveBeenTriggeredOn(this.player);
});
});
});
describe('onPlay', function() {
beforeEach(function() {
this.player = new VideoPlayer(this.video);
this.anotherPlayer = jasmine.createSpyObj('AnotherPlayer', ['pauseVideo']);
window.player = this.anotherPlayer;
spyOn(Logger, 'log');
spyOn(window, 'setInterval').andReturn(100);
this.player.player.getVideoEmbedCode.andReturn('embedCode');
return this.player.onPlay();
});
it('log the play_video event', function() {
return expect(Logger.log).toHaveBeenCalledWith('play_video', {
id: this.player.currentTime,
code: 'embedCode'
});
});
it('pause other video player', function() {
return expect(this.anotherPlayer.pauseVideo).toHaveBeenCalled();
});
it('set current video player as active player', function() {
return expect(window.player).toEqual(this.player.player);
});
return it('set update interval', function() {
expect(window.setInterval).toHaveBeenCalledWith(this.player.update, 200);
return expect(this.player.player.interval).toEqual(100);
});
});
describe('onPause', function() {
beforeEach(function() {
this.player = new VideoPlayer(this.video);
window.player = this.player.player;
spyOn(Logger, 'log');
spyOn(window, 'clearInterval');
this.player.player.interval = 100;
this.player.player.getVideoEmbedCode.andReturn('embedCode');
return this.player.onPause();
});
it('log the pause_video event', function() {
return expect(Logger.log).toHaveBeenCalledWith('pause_video', {
id: this.player.currentTime,
code: 'embedCode'
});
});
it('set current video player as inactive', function() {
return expect(window.player).toBeNull();
});
return it('clear update interval', function() {
expect(window.clearInterval).toHaveBeenCalledWith(100);
return expect(this.player.player.interval).toBeNull();
});
});
describe('onSeek', function() {
beforeEach(function() {
this.player = new VideoPlayer(this.video);
spyOn(window, 'clearInterval');
this.player.player.interval = 100;
return this.player.onSeek({}, 60);
});
it('seek the player', function() {
return expect(this.player.player.seekTo).toHaveBeenCalledWith(60, true);
});
describe('when the player is playing', function() {
beforeEach(function() {
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.player.getPlayerState.andReturn(YT.PlayerState.PAUSED);
spyOnEvent(this.player, 'updatePlayTime');
return this.player.onSeek({}, 60);
});
it('set the current time', function() {
return expect(this.player.currentTime).toEqual(60);
});
return it('trigger updatePlayTime event', function() {
return expect('updatePlayTime').toHaveBeenTriggeredOn(this.player);
});
});
});
describe('onSpeedChange', function() {
beforeEach(function() {
this.player = new VideoPlayer(this.video);
this.player.currentTime = 60;
return spyOn(this.video, 'setSpeed').andCallThrough();
});
describe('always', function() {
beforeEach(function() {
return this.player.onSpeedChange({}, '0.75');
});
it('convert the current time to the new speed', function() {
return expect(this.player.currentTime).toEqual('80.000');
});
return it('set video speed to the new speed', function() {
return expect(this.video.setSpeed).toHaveBeenCalledWith('0.75');
});
});
describe('when the video is playing', function() {
beforeEach(function() {
this.player.player.getPlayerState.andReturn(YT.PlayerState.PLAYING);
spyOnEvent(this.player, 'updatePlayTime');
return this.player.onSpeedChange({}, '0.75');
});
it('load the video', function() {
return expect(this.player.player.loadVideoById).toHaveBeenCalledWith('abc123', '80.000');
});
return it('trigger updatePlayTime event', function() {
return expect('updatePlayTime').toHaveBeenTriggeredOn(this.player);
});
});
return describe('when the video is not playing', function() {
beforeEach(function() {
this.player.player.getPlayerState.andReturn(YT.PlayerState.PAUSED);
spyOnEvent(this.player, 'updatePlayTime');
return this.player.onSpeedChange({}, '0.75');
});
it('cue the video', function() {
return expect(this.player.player.cueVideoById).toHaveBeenCalledWith('abc123', '80.000');
});
return it('trigger updatePlayTime event', function() {
return expect('updatePlayTime').toHaveBeenTriggeredOn(this.player);
});
});
});
describe('update', function() {
beforeEach(function() {
this.player = new VideoPlayer(this.video);
return spyOnEvent(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('updatePlayTime').not.toHaveBeenTriggeredOn(this.player);
});
});
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('updatePlayTime').toHaveBeenTriggeredOn(this.player);
});
});
});
describe('onUpdatePlaytime', function() {
beforeEach(function() {
this.player = new VideoPlayer(this.video);
spyOn(this.video, 'getDuration').andReturn(1800);
return this.player.onUpdatePlayTime({}, 60);
});
return it('update the video playback time', function() {
return expect($('.vidtime')).toHaveHtml('1:00 / 30:00');
});
});
describe('toggleFullScreen', function() {
beforeEach(function() {
return this.player = new VideoPlayer(this.video);
});
describe('when the video player is not full screen', function() {
beforeEach(function() {
this.player.element.removeClass('fullscreen');
spyOnEvent(this.player, 'resize');
return this.player.toggleFullScreen(jQuery.Event("click"));
});
it('replace the full screen button tooltip', function() {
return expect($('.add-fullscreen')).toHaveAttr('title', 'Exit fill browser');
});
it('add a new exit from fullscreen button', function() {
return expect(this.player.element).toContain('a.exit');
});
it('add the fullscreen class', function() {
return expect(this.player.element).toHaveClass('fullscreen');
});
return it('trigger resize event', function() {
return expect('resize').toHaveBeenTriggeredOn(this.player);
});
});
return describe('when the video player already full screen', function() {
beforeEach(function() {
this.player.element.addClass('fullscreen');
spyOnEvent(this.player, 'resize');
return this.player.toggleFullScreen(jQuery.Event("click"));
});
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.element).not.toContain('a.exit');
});
it('remove the fullscreen class', function() {
return expect(this.player.element).not.toHaveClass('fullscreen');
});
return it('trigger resize event', function() {
return expect('resize').toHaveBeenTriggeredOn(this.player);
});
});
});
describe('play', function() {
beforeEach(function() {
return this.player = new VideoPlayer(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() {
return this.player = new VideoPlayer(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() {
this.player = new VideoPlayer(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() {
this.player = new VideoPlayer(this.video);
spyOn(this.video, 'getDuration');
return this.player.duration();
});
return it('delegate to the video', function() {
return expect(this.video.getDuration).toHaveBeenCalled();
});
});
return describe('currentSpeed', function() {
beforeEach(function() {
this.player = new VideoPlayer(this.video);
return this.video.speed = '3.0';
});
return it('delegate to the video', function() {
return expect(this.player.currentSpeed()).toEqual('3.0');
});
});
});
}).call(this);
describe 'VideoProgressSlider', ->
beforeEach ->
@player = jasmine.stubVideoPlayer @
describe 'constructor', ->
beforeEach ->
spyOn($.fn, 'slider').andCallThrough()
@slider = new VideoProgressSlider @player
it 'build the slider', ->
expect(@slider.slider).toBe '.slider'
expect($.fn.slider).toHaveBeenCalledWith
range: 'min'
change: @slider.onChange
slide: @slider.onSlide
stop: @slider.onStop
it 'build the seek handle', ->
expect(@slider.handle).toBe '.ui-slider-handle'
expect($.fn.qtip).toHaveBeenCalledWith
content: "0:00"
position:
my: 'bottom center'
at: 'top center'
container: @slider.handle
hide:
delay: 700
style:
classes: 'ui-tooltip-slider'
widget: true
it 'bind player events', ->
expect($(@player)).toHandleWith 'updatePlayTime', @slider.onUpdatePlayTime
describe 'onReady', ->
beforeEach ->
spyOn(@player, 'duration').andReturn 120
@slider = new VideoProgressSlider @player
@slider.onReady()
it 'set the max value to the length of video', ->
expect(@slider.slider.slider('option', 'max')).toEqual 120
describe 'onUpdatePlayTime', ->
beforeEach ->
@slider = new VideoProgressSlider @player
spyOn(@player, 'duration').andReturn 120
spyOn($.fn, 'slider').andCallThrough()
describe 'when frozen', ->
beforeEach ->
@slider.frozen = true
@slider.onUpdatePlayTime {}, 20
it 'does not update the slider', ->
expect($.fn.slider).not.toHaveBeenCalled()
describe 'when not frozen', ->
beforeEach ->
@slider.frozen = false
@slider.onUpdatePlayTime {}, 20
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 ->
@slider = new VideoProgressSlider @player
@time = null
$(@player).bind 'seek', (event, time) => @time = time
spyOnEvent @player, 'seek'
@slider.onSlide {}, value: 20
it 'freeze the slider', ->
expect(@slider.frozen).toBeTruthy()
it 'update the tooltip', ->
expect($.fn.qtip).toHaveBeenCalled()
it 'trigger seek event', ->
expect('seek').toHaveBeenTriggeredOn @player
expect(@time).toEqual 20
describe 'onChange', ->
beforeEach ->
@slider = new VideoProgressSlider @player
@slider.onChange {}, value: 20
it 'update the tooltip', ->
expect($.fn.qtip).toHaveBeenCalled()
describe 'onStop', ->
beforeEach ->
@slider = new VideoProgressSlider @player
@time = null
$(@player).bind 'seek', (event, time) => @time = time
spyOnEvent @player, 'seek'
spyOn(window, 'setTimeout')
@slider.onStop {}, value: 20
it 'freeze the slider', ->
expect(@slider.frozen).toBeTruthy()
it 'trigger seek event', ->
expect('seek').toHaveBeenTriggeredOn @player
expect(@time).toEqual 20
it 'set timeout to unfreeze the slider', ->
expect(window.setTimeout).toHaveBeenCalledWith jasmine.any(Function), 200
window.setTimeout.mostRecentCall.args[0]()
expect(@slider.frozen).toBeFalsy()
describe 'updateTooltip', ->
beforeEach ->
@slider = new VideoProgressSlider @player
@slider.updateTooltip 90
it 'set the tooltip value', ->
expect($.fn.qtip).toHaveBeenCalledWith 'option', 'content.text', '1:30'
(function() {
describe('VideoProgressSlider', function() {
beforeEach(function() {
return this.player = jasmine.stubVideoPlayer(this);
});
describe('constructor', function() {
beforeEach(function() {
spyOn($.fn, 'slider').andCallThrough();
return this.slider = new VideoProgressSlider(this.player);
});
it('build the slider', function() {
expect(this.slider.slider).toBe('.slider');
return expect($.fn.slider).toHaveBeenCalledWith({
range: 'min',
change: this.slider.onChange,
slide: this.slider.onSlide,
stop: this.slider.onStop
});
});
it('build the seek handle', function() {
expect(this.slider.handle).toBe('.ui-slider-handle');
return expect($.fn.qtip).toHaveBeenCalledWith({
content: "0:00",
position: {
my: 'bottom center',
at: 'top center',
container: this.slider.handle
},
hide: {
delay: 700
},
style: {
classes: 'ui-tooltip-slider',
widget: true
}
});
});
return it('bind player events', function() {
return expect($(this.player)).toHandleWith('updatePlayTime', this.slider.onUpdatePlayTime);
});
});
describe('onReady', function() {
beforeEach(function() {
spyOn(this.player, 'duration').andReturn(120);
this.slider = new VideoProgressSlider(this.player);
return this.slider.onReady();
});
return it('set the max value to the length of video', function() {
return expect(this.slider.slider.slider('option', 'max')).toEqual(120);
});
});
describe('onUpdatePlayTime', function() {
beforeEach(function() {
this.slider = new VideoProgressSlider(this.player);
spyOn(this.player, 'duration').andReturn(120);
return spyOn($.fn, 'slider').andCallThrough();
});
describe('when frozen', function() {
beforeEach(function() {
this.slider.frozen = true;
return this.slider.onUpdatePlayTime({}, 20);
});
return it('does not update the slider', function() {
return expect($.fn.slider).not.toHaveBeenCalled();
});
});
return describe('when not frozen', function() {
beforeEach(function() {
this.slider.frozen = false;
return this.slider.onUpdatePlayTime({}, 20);
});
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() {
var _this = this;
this.slider = new VideoProgressSlider(this.player);
this.time = null;
$(this.player).bind('seek', function(event, time) {
return _this.time = time;
});
spyOnEvent(this.player, 'seek');
return this.slider.onSlide({}, {
value: 20
});
});
it('freeze the slider', function() {
return expect(this.slider.frozen).toBeTruthy();
});
it('update the tooltip', function() {
return expect($.fn.qtip).toHaveBeenCalled();
});
return it('trigger seek event', function() {
expect('seek').toHaveBeenTriggeredOn(this.player);
return expect(this.time).toEqual(20);
});
});
describe('onChange', function() {
beforeEach(function() {
this.slider = new VideoProgressSlider(this.player);
return this.slider.onChange({}, {
value: 20
});
});
return it('update the tooltip', function() {
return expect($.fn.qtip).toHaveBeenCalled();
});
});
describe('onStop', function() {
beforeEach(function() {
var _this = this;
this.slider = new VideoProgressSlider(this.player);
this.time = null;
$(this.player).bind('seek', function(event, time) {
return _this.time = time;
});
spyOnEvent(this.player, 'seek');
spyOn(window, 'setTimeout');
return this.slider.onStop({}, {
value: 20
});
});
it('freeze the slider', function() {
return expect(this.slider.frozen).toBeTruthy();
});
it('trigger seek event', function() {
expect('seek').toHaveBeenTriggeredOn(this.player);
return expect(this.time).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.slider.frozen).toBeFalsy();
});
});
return describe('updateTooltip', function() {
beforeEach(function() {
this.slider = new VideoProgressSlider(this.player);
return this.slider.updateTooltip(90);
});
return it('set the tooltip value', function() {
return expect($.fn.qtip).toHaveBeenCalledWith('option', 'content.text', '1:30');
});
});
});
}).call(this);
describe 'VideoSpeedControl', ->
beforeEach ->
@player = jasmine.stubVideoPlayer @
$('.speeds').remove()
afterEach ->
describe 'constructor', ->
describe 'always', ->
beforeEach ->
@speedControl = new VideoSpeedControl @player, @video.speeds
it 'add the video speed control to player', ->
expect($('.secondary-controls').html()).toContain '''
<div class="speeds">
<a href="#">
<h3>Speed</h3>
<p class="active">1.0x</p>
</a>
<ol class="video_speeds"><li data-speed="1.0" class="active"><a href="#">1.0x</a></li><li data-speed="0.75"><a href="#">0.75x</a></li></ol>
</div>
'''
it 'bind to player speedChange event', ->
expect($(@player)).toHandleWith 'speedChange', @speedControl.onSpeedChange
it 'bind to change video speed link', ->
expect($('.video_speeds a')).toHandleWith 'click', @speedControl.changeVideoSpeed
describe 'when running on touch based device', ->
beforeEach ->
spyOn(window, 'onTouchBasedDevice').andReturn true
$('.speeds').removeClass 'open'
@speedControl = new VideoSpeedControl @player, @video.speeds
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 ->
spyOn(window, 'onTouchBasedDevice').andReturn false
$('.speeds').removeClass 'open'
@speedControl = new VideoSpeedControl @player, @video.speeds
it 'open the speed toggle on hover', ->
$('.speeds').mouseover()
expect($('.speeds')).toHaveClass 'open'
$('.speeds').mouseout()
expect($('.speeds')).not.toHaveClass 'open'
it 'close the speed toggle on mouse out', ->
$('.speeds').mouseover().mouseout()
expect($('.speeds')).not.toHaveClass 'open'
it 'close the speed toggle on click', ->
$('.speeds').mouseover().click()
expect($('.speeds')).not.toHaveClass 'open'
describe 'changeVideoSpeed', ->
beforeEach ->
@speedControl = new VideoSpeedControl @player, @video.speeds
@video.setSpeed '1.0'
describe 'when new speed is the same', ->
beforeEach ->
spyOnEvent @player, 'speedChange'
$('li[data-speed="1.0"] a').click()
it 'does not trigger speedChange event', ->
expect('speedChange').not.toHaveBeenTriggeredOn @player
describe 'when new speed is not the same', ->
beforeEach ->
@newSpeed = null
$(@player).bind 'speedChange', (event, newSpeed) => @newSpeed = newSpeed
spyOnEvent @player, 'speedChange'
$('li[data-speed="0.75"] a').click()
it 'trigger player speedChange event', ->
expect('speedChange').toHaveBeenTriggeredOn @player
expect(@newSpeed).toEqual 0.75
describe 'onSpeedChange', ->
beforeEach ->
@speedControl = new VideoSpeedControl @player, @video.speeds
$('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'
(function() {
describe('VideoSpeedControl', function() {
beforeEach(function() {
this.player = jasmine.stubVideoPlayer(this);
return $('.speeds').remove();
});
afterEach(function() {});
describe('constructor', function() {
describe('always', function() {
beforeEach(function() {
return this.speedControl = new VideoSpeedControl(this.player, this.video.speeds);
});
it('add the video speed control to player', function() {
return expect($('.secondary-controls').html()).toContain('<div class="speeds">\n <a href="#">\n <h3>Speed</h3>\n <p class="active">1.0x</p>\n </a>\n <ol class="video_speeds"><li data-speed="1.0" class="active"><a href="#">1.0x</a></li><li data-speed="0.75"><a href="#">0.75x</a></li></ol>\n</div>');
});
it('bind to player speedChange event', function() {
return expect($(this.player)).toHandleWith('speedChange', this.speedControl.onSpeedChange);
});
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() {
spyOn(window, 'onTouchBasedDevice').andReturn(true);
$('.speeds').removeClass('open');
return this.speedControl = new VideoSpeedControl(this.player, this.video.speeds);
});
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() {
spyOn(window, 'onTouchBasedDevice').andReturn(false);
$('.speeds').removeClass('open');
return this.speedControl = new VideoSpeedControl(this.player, this.video.speeds);
});
it('open the speed toggle on hover', function() {
$('.speeds').mouseover();
expect($('.speeds')).toHaveClass('open');
$('.speeds').mouseout();
return expect($('.speeds')).not.toHaveClass('open');
});
it('close the speed toggle on mouse out', function() {
$('.speeds').mouseover().mouseout();
return expect($('.speeds')).not.toHaveClass('open');
});
return it('close the speed toggle on click', function() {
$('.speeds').mouseover().click();
return expect($('.speeds')).not.toHaveClass('open');
});
});
});
describe('changeVideoSpeed', function() {
beforeEach(function() {
this.speedControl = new VideoSpeedControl(this.player, this.video.speeds);
return this.video.setSpeed('1.0');
});
describe('when new speed is the same', function() {
beforeEach(function() {
spyOnEvent(this.player, 'speedChange');
return $('li[data-speed="1.0"] a').click();
});
return it('does not trigger speedChange event', function() {
return expect('speedChange').not.toHaveBeenTriggeredOn(this.player);
});
});
return describe('when new speed is not the same', function() {
beforeEach(function() {
var _this = this;
this.newSpeed = null;
$(this.player).bind('speedChange', function(event, newSpeed) {
return _this.newSpeed = newSpeed;
});
spyOnEvent(this.player, 'speedChange');
return $('li[data-speed="0.75"] a').click();
});
return it('trigger player speedChange event', function() {
expect('speedChange').toHaveBeenTriggeredOn(this.player);
return expect(this.newSpeed).toEqual(0.75);
});
});
});
return describe('onSpeedChange', function() {
beforeEach(function() {
this.speedControl = new VideoSpeedControl(this.player, this.video.speeds);
$('li[data-speed="1.0"] a').addClass('active');
return this.speedControl.setSpeed('0.75');
});
return it('set the new speed as active', function() {
expect($('.video_speeds li[data-speed="1.0"]')).not.toHaveClass('active');
expect($('.video_speeds li[data-speed="0.75"]')).toHaveClass('active');
return expect($('.speeds p.active')).toHaveHtml('0.75x');
});
});
});
}).call(this);
describe 'Video', ->
beforeEach ->
loadFixtures 'video.html'
jasmine.stubRequests()
afterEach ->
window.player = undefined
window.onYouTubePlayerAPIReady = undefined
describe 'constructor', ->
beforeEach ->
$.cookie.andReturn '0.75'
window.player = 100
describe 'by default', ->
beforeEach ->
@video = new Video 'example', '.75:abc123,1.0:def456'
it 'reset the current video player', ->
expect(window.player).toBeNull()
it 'set the elements', ->
expect(@video.element).toBe '#video_example'
it 'parse the videos', ->
expect(@video.videos).toEqual
'0.75': 'abc123'
'1.0': 'def456'
it 'fetch the video metadata', ->
expect(@video.metadata).toEqual
abc123:
id: 'abc123'
duration: 100
def456:
id: 'def456'
duration: 200
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 }
@stubVideoPlayer = jasmine.createSpy('VideoPlayer')
spyOn(window, 'VideoPlayer').andReturn(@stubVideoPlayer)
@video = new Video 'example', '.75:abc123,1.0:def456'
afterEach ->
window.YT = @originalYT
it 'create the Video Player', ->
expect(window.VideoPlayer).toHaveBeenCalledWith @video
expect(@video.player).toEqual @stubVideoPlayer
describe 'when the Youtube API is not ready', ->
beforeEach ->
@video = new Video 'example', '.75:abc123,1.0:def456'
it 'set the callback on the window object', ->
expect(window.onYouTubePlayerAPIReady).toEqual jasmine.any(Function)
describe 'when the Youtube API becoming ready', ->
beforeEach ->
@stubVideoPlayer = jasmine.createSpy('VideoPlayer')
spyOn(window, 'VideoPlayer').andReturn(@stubVideoPlayer)
@video = new Video 'example', '.75:abc123,1.0:def456'
window.onYouTubePlayerAPIReady()
it 'create the Video Player for all video elements', ->
expect(window.VideoPlayer).toHaveBeenCalledWith @video
expect(@video.player).toEqual @stubVideoPlayer
describe 'youtubeId', ->
beforeEach ->
$.cookie.andReturn '1.0'
@video = new Video 'example', '.75:abc123,1.0:def456'
describe 'with speed', ->
it 'return the video id for given speed', ->
expect(@video.youtubeId('0.75')).toEqual 'abc123'
expect(@video.youtubeId('1.0')).toEqual 'def456'
describe 'without speed', ->
it 'return the video id for current speed', ->
expect(@video.youtubeId()).toEqual 'def456'
describe 'setSpeed', ->
beforeEach ->
@video = new Video 'example', '.75:abc123,1.0:def456'
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 ->
@video = new Video 'example', '.75:abc123,1.0:def456'
it 'return duration for current video', ->
expect(@video.getDuration()).toEqual 200
(function() {
describe('Video', function() {
beforeEach(function() {
loadFixtures('video.html');
return jasmine.stubRequests();
});
afterEach(function() {
window.player = void 0;
return window.onYouTubePlayerAPIReady = void 0;
});
describe('constructor', function() {
beforeEach(function() {
$.cookie.andReturn('0.75');
return window.player = 100;
});
describe('by default', function() {
beforeEach(function() {
return this.video = new Video('example', '.75:abc123,1.0:def456');
});
it('reset the current video player', function() {
return expect(window.player).toBeNull();
});
it('set the elements', function() {
return expect(this.video.element).toBe('#video_example');
});
it('parse the videos', function() {
return expect(this.video.videos).toEqual({
'0.75': 'abc123',
'1.0': 'def456'
});
});
it('fetch the video metadata', function() {
return expect(this.video.metadata).toEqual({
abc123: {
id: 'abc123',
duration: 100
},
def456: {
id: 'def456',
duration: 200
}
});
});
it('parse available video speeds', function() {
return expect(this.video.speeds).toEqual(['0.75', '1.0']);
});
it('set current video speed via cookie', function() {
return expect(this.video.speed).toEqual('0.75');
});
return it('store a reference for this video player in the element', function() {
return expect($('.video').data('video')).toEqual(this.video);
});
});
describe('when the Youtube API is already available', function() {
beforeEach(function() {
this.originalYT = window.YT;
window.YT = {
Player: true
};
this.stubVideoPlayer = jasmine.createSpy('VideoPlayer');
spyOn(window, 'VideoPlayer').andReturn(this.stubVideoPlayer);
return this.video = new Video('example', '.75:abc123,1.0:def456');
});
afterEach(function() {
return window.YT = this.originalYT;
});
return it('create the Video Player', function() {
expect(window.VideoPlayer).toHaveBeenCalledWith(this.video);
return expect(this.video.player).toEqual(this.stubVideoPlayer);
});
});
describe('when the Youtube API is not ready', function() {
beforeEach(function() {
return this.video = new Video('example', '.75:abc123,1.0:def456');
});
return it('set the callback on the window object', function() {
return expect(window.onYouTubePlayerAPIReady).toEqual(jasmine.any(Function));
});
});
return describe('when the Youtube API becoming ready', function() {
beforeEach(function() {
this.stubVideoPlayer = jasmine.createSpy('VideoPlayer');
spyOn(window, 'VideoPlayer').andReturn(this.stubVideoPlayer);
this.video = new Video('example', '.75:abc123,1.0:def456');
return window.onYouTubePlayerAPIReady();
});
return it('create the Video Player for all video elements', function() {
expect(window.VideoPlayer).toHaveBeenCalledWith(this.video);
return expect(this.video.player).toEqual(this.stubVideoPlayer);
});
});
});
describe('youtubeId', function() {
beforeEach(function() {
$.cookie.andReturn('1.0');
return this.video = new Video('example', '.75:abc123,1.0:def456');
});
describe('with speed', function() {
return it('return the video id for given speed', function() {
expect(this.video.youtubeId('0.75')).toEqual('abc123');
return expect(this.video.youtubeId('1.0')).toEqual('def456');
});
});
return describe('without speed', function() {
return it('return the video id for current speed', function() {
return expect(this.video.youtubeId()).toEqual('def456');
});
});
});
describe('setSpeed', function() {
beforeEach(function() {
return this.video = new Video('example', '.75:abc123,1.0:def456');
});
describe('when new speed is available', function() {
beforeEach(function() {
return this.video.setSpeed('0.75');
});
it('set new speed', function() {
return expect(this.video.speed).toEqual('0.75');
});
return it('save setting for new speed', function() {
return expect($.cookie).toHaveBeenCalledWith('video_speed', '0.75', {
expires: 3650,
path: '/'
});
});
});
return describe('when new speed is not available', function() {
beforeEach(function() {
return this.video.setSpeed('1.75');
});
return it('set speed to 1.0x', function() {
return expect(this.video.speed).toEqual('1.0');
});
});
});
return describe('getDuration', function() {
beforeEach(function() {
return this.video = new Video('example', '.75:abc123,1.0:def456');
});
return it('return duration for current video', function() {
return expect(this.video.getDuration()).toEqual(200);
});
});
});
}).call(this);
describe 'Time', ->
describe 'format', ->
describe 'with duration more than or equal to 1 hour', ->
it 'return a correct time format', ->
expect(Time.format(3600)).toEqual '1:00:00'
expect(Time.format(7272)).toEqual '2:01:12'
describe 'with duration less than 1 hour', ->
it 'return a correct time format', ->
expect(Time.format(1)).toEqual '0:01'
expect(Time.format(61)).toEqual '1:01'
expect(Time.format(3599)).toEqual '59:59'
describe 'convert', ->
it 'return a correct time based on speed modifier', ->
expect(Time.convert(0, 1, 1.5)).toEqual '0.000'
expect(Time.convert(100, 1, 1.5)).toEqual '66.667'
expect(Time.convert(100, 1.5, 1)).toEqual '150.000'
(function() {
describe('Time', function() {
describe('format', function() {
describe('with duration more than or equal to 1 hour', function() {
return it('return a correct time format', function() {
expect(Time.format(3600)).toEqual('1:00:00');
return expect(Time.format(7272)).toEqual('2:01:12');
});
});
return describe('with duration less than 1 hour', function() {
return it('return a correct time format', function() {
expect(Time.format(1)).toEqual('0:01');
expect(Time.format(61)).toEqual('1:01');
return expect(Time.format(3599)).toEqual('59:59');
});
});
});
return describe('convert', function() {
return it('return a correct time based on speed modifier', function() {
expect(Time.convert(0, 1, 1.5)).toEqual('0.000');
expect(Time.convert(100, 1, 1.5)).toEqual('66.667');
return expect(Time.convert(100, 1.5, 1)).toEqual('150.000');
});
});
});
}).call(this);
...@@ -25,11 +25,11 @@ class @Histogram ...@@ -25,11 +25,11 @@ class @Histogram
], ],
xaxis: xaxis:
min: -1 min: -1
max: Math.max $.map(@xTicks, (data) -> data[0] + 1) max: Math.max.apply Math, $.map(@xTicks, (data) -> data[0] + 1)
ticks: @xTicks ticks: @xTicks
tickLength: 0 tickLength: 0
yaxis: yaxis:
min: 0.0 min: 0.0
max: Math.max $.map(@yTicks, (data) -> data[0] * 1.1) max: Math.max.apply Math, $.map(@yTicks, (data) -> data[0] * 1.1)
ticks: @yTicks ticks: @yTicks
labelWidth: 50 labelWidth: 50
...@@ -14,7 +14,6 @@ class @Logger ...@@ -14,7 +14,6 @@ class @Logger
event: '' event: ''
page: window.location.href page: window.location.href
async: false async: false
return true
# Keeping this for conpatibility issue only. # Keeping this for conpatibility issue only.
@log_event = Logger.log @log_event = Logger.log
...@@ -8,7 +8,7 @@ class @Problem ...@@ -8,7 +8,7 @@ class @Problem
$(selector, @element) $(selector, @element)
bind: => bind: =>
MathJax.Hub.Queue ["Typeset",MathJax.Hub] MathJax.Hub.Queue ["Typeset", MathJax.Hub]
window.update_schematics() window.update_schematics()
@$('section.action input:button').click @refreshAnswers @$('section.action input:button').click @refreshAnswers
@$('section.action input.check').click @check @$('section.action input.check').click @check
...@@ -43,15 +43,16 @@ class @Problem ...@@ -43,15 +43,16 @@ class @Problem
$.postWithPrefix "/modx/problem/#{@id}/problem_show", (response) => $.postWithPrefix "/modx/problem/#{@id}/problem_show", (response) =>
$.each response, (key, value) => $.each response, (key, value) =>
if $.isArray(value) if $.isArray(value)
$.each value, (index, answer_index) => for choice in value
@$("#label[for='input_#{key}_#{value[answer_index]}']").attr @$("label[for='input_#{key}_#{choice}']").attr
correct_answer: 'true' correct_answer: 'true'
@$("#answer_#{key}").text(value) else
@$("#answer_#{key}").text(value)
@$('.show').val 'Hide Answer' @$('.show').val 'Hide Answer'
@element.addClass 'showed' @element.addClass 'showed'
else else
@$('[id^=answer_]').text('') @$('[id^=answer_]').text ''
@$('[correct_answer]').attr(correct_answer: null) @$('[correct_answer]').attr correct_answer: null
@element.removeClass 'showed' @element.removeClass 'showed'
@$('.show').val 'Show Answer' @$('.show').val 'Show Answer'
......
...@@ -11,7 +11,6 @@ class @Sequence ...@@ -11,7 +11,6 @@ class @Sequence
bind: -> bind: ->
@element.bind 'contentChanged', @toggleArrows @element.bind 'contentChanged', @toggleArrows
@$('#sequence-list a').click @goto @$('#sequence-list a').click @goto
@$('.sequence-nav li a').hover @navHover
buildNavigation: -> buildNavigation: ->
$.each @elements, (index, item) => $.each @elements, (index, item) =>
...@@ -34,37 +33,34 @@ class @Sequence ...@@ -34,37 +33,34 @@ class @Sequence
@$('.sequence-nav-buttons .next a').removeClass('disabled').click(@next) @$('.sequence-nav-buttons .next a').removeClass('disabled').click(@next)
render: (new_position) -> render: (new_position) ->
if @position != undefined
@mark_visited @position
$.postWithPrefix "/modx/#{@tag}/#{@id}/goto_position", position: new_position
if @position != new_position if @position != new_position
if @position != undefined
@mark_visited @position
$.postWithPrefix "/modx/#{@tag}/#{@id}/goto_position", position: new_position
@mark_active new_position @mark_active new_position
@$('#seq_content').html eval(@elements[new_position - 1].content) @$('#seq_content').html eval(@elements[new_position - 1].content)
MathJax.Hub.Queue(["Typeset",MathJax.Hub]) MathJax.Hub.Queue(["Typeset", MathJax.Hub])
@position = new_position @position = new_position
@element.trigger 'contentChanged' @element.trigger 'contentChanged'
navHover: (event) =>
$(event.target).siblings().toggleClass("shown")
goto: (event) => goto: (event) =>
event.preventDefault() event.preventDefault()
new_position = $(event.target).data('element') new_position = $(event.target).data('element')
log_event("seq_goto", old: @position, new: new_position, id: @id) Logger.log "seq_goto", old: @position, new: new_position, id: @id
@render new_position @render new_position
next: (event) => next: (event) =>
event.preventDefault() event.preventDefault()
new_position = @position + 1 new_position = @position + 1
log_event("seq_next", old: @position, new: new_position, id: @id) Logger.log "seq_next", old: @position, new: new_position, id: @id
@render new_position @render new_position
previous: (event) => previous: (event) =>
event.preventDefault() event.preventDefault()
new_position = @position - 1 new_position = @position - 1
log_event("seq_prev", old: @position, new: new_position, id: @id) Logger.log "seq_prev", old: @position, new: new_position, id: @id
@render new_position @render new_position
link_for: (position) -> link_for: (position) ->
......
...@@ -3,6 +3,8 @@ class @Video ...@@ -3,6 +3,8 @@ class @Video
window.player = null window.player = null
@element = $("#video_#{@id}") @element = $("#video_#{@id}")
@parseVideos videos @parseVideos videos
@fetchMetadata()
@parseSpeed()
$("#video_#{@id}").data('video', this) $("#video_#{@id}").data('video', this)
if YT.Player if YT.Player
...@@ -21,8 +23,6 @@ class @Video ...@@ -21,8 +23,6 @@ class @Video
video = video.split(/:/) video = video.split(/:/)
speed = parseFloat(video[0]).toFixed(2).replace /\.00$/, '.0' speed = parseFloat(video[0]).toFixed(2).replace /\.00$/, '.0'
@videos[speed] = video[1] @videos[speed] = video[1]
@fetchMetadata()
@parseSpeed()
parseSpeed: -> parseSpeed: ->
@setSpeed($.cookie('video_speed')) @setSpeed($.cookie('video_speed'))
......
class VideoCaption class @VideoCaption
constructor: (@player, @youtubeId) -> constructor: (@player, @youtubeId) ->
@render() @render()
@bind() @bind()
...@@ -85,7 +85,7 @@ class VideoCaption ...@@ -85,7 +85,7 @@ class VideoCaption
clearTimeout @frozen if @frozen clearTimeout @frozen if @frozen
@frozen = setTimeout @onMouseLeave, 10000 @frozen = setTimeout @onMouseLeave, 10000
onMovement: (event) => onMovement: =>
@onMouseEnter() @onMouseEnter()
onMouseLeave: => onMouseLeave: =>
...@@ -107,24 +107,23 @@ class VideoCaption ...@@ -107,24 +107,23 @@ class VideoCaption
@captionHeight() / 2 - element.height() / 2 @captionHeight() / 2 - element.height() / 2
topSpacingHeight: -> topSpacingHeight: ->
@calculateOffset(@$('.subtitles li:not(.spacing).first')) @calculateOffset(@$('.subtitles li:not(.spacing):first'))
bottomSpacingHeight: -> bottomSpacingHeight: ->
@calculateOffset(@$('.subtitles li:not(.spacing).last')) @calculateOffset(@$('.subtitles li:not(.spacing):last'))
toggle: (event) => toggle: (event) =>
event.preventDefault() event.preventDefault()
if @player.element.hasClass('closed') if @player.element.hasClass('closed')
@$('.hide-subtitles').attr('title', 'Turn off captions') @$('.hide-subtitles').attr('title', 'Turn off captions')
@player.element.removeClass('closed') @player.element.removeClass('closed')
@scrollCaption()
else else
@$('.hide-subtitles').attr('title', 'Turn on captions') @$('.hide-subtitles').attr('title', 'Turn on captions')
@player.element.addClass('closed') @player.element.addClass('closed')
@scrollCaption()
captionHeight: -> captionHeight: ->
if @player.element.hasClass('fullscreen') if @player.element.hasClass('fullscreen')
$(window).height() - @$('.video-controls').height() $(window).height() - @$('.video-controls').height()
else else
@$('.video-wrapper').height() @$('.video-wrapper').height()
class VideoControl class @VideoControl
constructor: (@player) -> constructor: (@player) ->
@render() @render()
@bind() @bind()
...@@ -36,7 +36,7 @@ class VideoControl ...@@ -36,7 +36,7 @@ class VideoControl
togglePlayback: (event) => togglePlayback: (event) =>
event.preventDefault() event.preventDefault()
if $(event.target).hasClass('play') if @player.isPlaying()
$(@player).trigger('play')
else
$(@player).trigger('pause') $(@player).trigger('pause')
else
$(@player).trigger('play')
class VideoPlayer class @VideoPlayer
constructor: (@video) -> constructor: (@video) ->
@currentTime = 0 @currentTime = 0
@element = $("#video_#{@video.id}") @element = $("#video_#{@video.id}")
...@@ -18,17 +18,17 @@ class VideoPlayer ...@@ -18,17 +18,17 @@ class VideoPlayer
$(document).keyup @bindExitFullScreen $(document).keyup @bindExitFullScreen
@$('.add-fullscreen').click @toggleFullScreen @$('.add-fullscreen').click @toggleFullScreen
@addToolTip unless onTouchBasedDevice() @addToolTip() unless onTouchBasedDevice()
bindExitFullScreen: (event) => bindExitFullScreen: (event) =>
if @element.hasClass('fullscreen') && event.keyCode == 27 if @element.hasClass('fullscreen') && event.keyCode == 27
@toggleFullScreen(event) @toggleFullScreen(event)
render: -> render: ->
new VideoControl(this) new VideoControl @
new VideoCaption(this, @video.youtubeId('1.0')) new VideoCaption @, @video.youtubeId('1.0')
new VideoSpeedControl(this, @video.speeds) new VideoSpeedControl @, @video.speeds
new VideoProgressSlider(this) new VideoProgressSlider @
@player = new YT.Player @video.id, @player = new YT.Player @video.id,
playerVars: playerVars:
controls: 0 controls: 0
...@@ -48,9 +48,9 @@ class VideoPlayer ...@@ -48,9 +48,9 @@ class VideoPlayer
at: 'top center' at: 'top center'
onReady: => onReady: =>
@setProgress(0, @duration())
$(@).trigger('ready') $(@).trigger('ready')
unless true || onTouchBasedDevice() $(@).trigger('updatePlayTime', 0)
unless onTouchBasedDevice()
$('.course-content .video:first').data('video').player.play() $('.course-content .video:first').data('video').player.play()
onStateChange: (event) => onStateChange: (event) =>
...@@ -92,7 +92,6 @@ class VideoPlayer ...@@ -92,7 +92,6 @@ class VideoPlayer
@player.loadVideoById(@video.youtubeId(), @currentTime) @player.loadVideoById(@video.youtubeId(), @currentTime)
else else
@player.cueVideoById(@video.youtubeId(), @currentTime) @player.cueVideoById(@video.youtubeId(), @currentTime)
@setProgress(@currentTime, @duration())
$(@).trigger('updatePlayTime', @currentTime) $(@).trigger('updatePlayTime', @currentTime)
update: => update: =>
...@@ -100,13 +99,8 @@ class VideoPlayer ...@@ -100,13 +99,8 @@ class VideoPlayer
$(@).trigger('updatePlayTime', @currentTime) $(@).trigger('updatePlayTime', @currentTime)
onUpdatePlayTime: (event, time) => onUpdatePlayTime: (event, time) =>
@setProgress(@currentTime) if time
setProgress: (time) =>
progress = Time.format(time) + ' / ' + Time.format(@duration()) progress = Time.format(time) + ' / ' + Time.format(@duration())
if @progress != progress @$(".vidtime").html(progress)
@$(".vidtime").html(progress)
@progress = progress
toggleFullScreen: (event) => toggleFullScreen: (event) =>
event.preventDefault() event.preventDefault()
......
class VideoProgressSlider class @VideoProgressSlider
constructor: (@player) -> constructor: (@player) ->
@buildSlider() @buildSlider()
@buildHandle() @buildHandle()
......
class VideoSpeedControl class @VideoSpeedControl
constructor: (@player, @speeds) -> constructor: (@player, @speeds) ->
@render() @render()
@bind() @bind()
...@@ -13,8 +13,8 @@ class VideoSpeedControl ...@@ -13,8 +13,8 @@ class VideoSpeedControl
@$('.speeds').click -> $(this).toggleClass('open') @$('.speeds').click -> $(this).toggleClass('open')
else else
@$('.speeds').mouseover -> $(this).addClass('open') @$('.speeds').mouseover -> $(this).addClass('open')
.mouseout -> $(this).removeClass('open') @$('.speeds').mouseout -> $(this).removeClass('open')
.click (event) -> @$('.speeds').click (event) ->
event.preventDefault() event.preventDefault()
$(this).removeClass('open') $(this).removeClass('open')
......
class @Time class @Time
@format: (time) -> @format: (time) ->
pad = (number) -> if number < 10 then "0#{number}" else number
seconds = Math.floor time seconds = Math.floor time
minutes = Math.floor seconds / 60 minutes = Math.floor seconds / 60
hours = Math.floor minutes / 60 hours = Math.floor minutes / 60
...@@ -7,15 +9,9 @@ class @Time ...@@ -7,15 +9,9 @@ class @Time
minutes = minutes % 60 minutes = minutes % 60
if hours if hours
"#{hours}:#{@pad(minutes)}:#{@pad(seconds % 60)}" "#{hours}:#{pad(minutes)}:#{pad(seconds % 60)}"
else
"#{minutes}:#{@pad(seconds % 60)}"
@pad: (number) ->
if number < 10
"0#{number}"
else else
number "#{minutes}:#{pad(seconds % 60)}"
@convert: (time, oldSpeed, newSpeed) -> @convert: (time, oldSpeed, newSpeed) ->
(time * oldSpeed / newSpeed).toFixed(3) (time * oldSpeed / newSpeed).toFixed(3)
<!doctype html>
<html>
<head>
<title>Jasmine Spec Runner</title>
<link rel="stylesheet" href="{{ STATIC_URL }}jasmine-latest/jasmine.css"
media="screen">
{# core files #}
<script src="{{ STATIC_URL }}jasmine-latest/jasmine.js"></script>
<script src="{{ STATIC_URL }}jasmine-latest/jasmine-html.js"></script>
<script src="https://raw.github.com/sikachu/jasmine-jquery/master/lib/jasmine-jquery.js"></script>
{# source files #}
{% for url in suite.js_files %}
<script src="{{ url }}"></script>
{% endfor %}
{# static files #}
{% for url in suite.static_files %}
<script src="{{ STATIC_URL }}{{ url }}"></script>
{% endfor %}
{# spec files #}
{% for file in files %}
<script src="{% url jasmine_test file %}"></script>
{% endfor %}
</head>
<body>
<h1>Jasmine Spec Runner</h1>
<script>
{% block jasmine %}
(function() {
var jasmineEnv = jasmine.getEnv();
jasmineEnv.updateInterval = 1000;
var trivialReporter = new jasmine.TrivialReporter();
jasmineEnv.addReporter(trivialReporter);
jasmineEnv.specFilter = function(spec) {
return trivialReporter.specFilter(spec);
};
// Additional configuration can be done in this block
{% block jasmine_extra %}{% endblock %}
var currentWindowOnload = window.onload;
window.onload = function() {
if (currentWindowOnload) {
currentWindowOnload();
}
execJasmine();
};
function execJasmine() {
jasmineEnv.execute();
}
})();
{% endblock %}
</script>
</body>
</html>
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment