Add test coverage for JavaScript
This commit is contained in:
@@ -3,7 +3,7 @@
|
||||
"/static/js/jquery-1.6.2.min.js",
|
||||
"/static/js/jquery-ui-1.8.16.custom.min.js",
|
||||
"/static/js/jquery.leanModal.js",
|
||||
"/static/js/jquery.cookie.js"
|
||||
"/static/js/flot/jquery.flot.js"
|
||||
],
|
||||
"static_files": [
|
||||
"js/application.js"
|
||||
|
||||
@@ -24,6 +24,7 @@ describe 'Calculator', ->
|
||||
expect($('form#calculator')).toHandleWith 'submit', @calculator.calculate
|
||||
|
||||
it 'prevent default behavior on form submit', ->
|
||||
jasmine.stubRequests()
|
||||
$('form#calculator').submit (e) ->
|
||||
expect(e.isDefaultPrevented()).toBeTruthy()
|
||||
e.preventDefault()
|
||||
@@ -55,7 +56,7 @@ describe 'Calculator', ->
|
||||
describe 'calculate', ->
|
||||
beforeEach ->
|
||||
$('#calculator_input').val '1+2'
|
||||
spyOn($, 'getJSON').andCallFake (url, data, callback) ->
|
||||
spyOn($, 'getWithPrefix').andCallFake (url, data, callback) ->
|
||||
callback({ result: 3 })
|
||||
@calculator.calculate()
|
||||
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
return expect($('form#calculator')).toHandleWith('submit', this.calculator.calculate);
|
||||
});
|
||||
return it('prevent default behavior on form submit', function() {
|
||||
jasmine.stubRequests();
|
||||
$('form#calculator').submit(function(e) {
|
||||
expect(e.isDefaultPrevented()).toBeTruthy();
|
||||
return e.preventDefault();
|
||||
@@ -58,7 +59,7 @@
|
||||
return describe('calculate', function() {
|
||||
beforeEach(function() {
|
||||
$('#calculator_input').val('1+2');
|
||||
spyOn($, 'getJSON').andCallFake(function(url, data, callback) {
|
||||
spyOn($, 'getWithPrefix').andCallFake(function(url, data, callback) {
|
||||
return callback({
|
||||
result: 3
|
||||
});
|
||||
|
||||
@@ -35,16 +35,28 @@ describe 'Courseware', ->
|
||||
|
||||
describe 'render', ->
|
||||
beforeEach ->
|
||||
jasmine.stubRequests()
|
||||
@courseware = new Courseware
|
||||
spyOn(window, 'Histogram')
|
||||
spyOn(window, 'Problem')
|
||||
spyOn(window, 'Video')
|
||||
setFixtures """
|
||||
<div class="course-content">
|
||||
<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="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>
|
||||
"""
|
||||
|
||||
it 'detect the video element and convert them', ->
|
||||
spyOn(window, 'Video')
|
||||
@courseware.render()
|
||||
|
||||
it 'detect the video elements and convert them', ->
|
||||
expect(window.Video).toHaveBeenCalledWith('1', '1.0:abc1234')
|
||||
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 @@
|
||||
});
|
||||
return describe('render', function() {
|
||||
beforeEach(function() {
|
||||
jasmine.stubRequests();
|
||||
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>");
|
||||
});
|
||||
return it('detect the video element and convert them', function() {
|
||||
spyOn(window, 'Histogram');
|
||||
spyOn(window, 'Problem');
|
||||
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');
|
||||
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', ->
|
||||
describe 'constructor', ->
|
||||
beforeEach ->
|
||||
new FeedbackForm
|
||||
spyOn($, 'post').andCallFake (url, data, callback, format) ->
|
||||
spyOn($, 'postWithPrefix').andCallFake (url, data, callback, format) ->
|
||||
callback()
|
||||
|
||||
it 'binds to the #feedback_button', ->
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
return describe('constructor', function() {
|
||||
beforeEach(function() {
|
||||
new FeedbackForm;
|
||||
return spyOn($, 'post').andCallFake(function(url, data, callback, format) {
|
||||
return spyOn($, 'postWithPrefix').andCallFake(function(url, data, callback, format) {
|
||||
return callback();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1 +1,76 @@
|
||||
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 @@
|
||||
|
||||
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);
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
class @Courseware
|
||||
@prefix: ''
|
||||
|
||||
constructor: ->
|
||||
Courseware.prefix = $("meta[name='path_prefix']").attr('content')
|
||||
new Navigation
|
||||
@@ -25,4 +27,3 @@ class @Courseware
|
||||
$('.course-content .histogram').each ->
|
||||
id = $(this).attr('id').replace(/histogram_/, '')
|
||||
new Histogram id, $(this).data('histogram')
|
||||
|
||||
|
||||
15
templates/coffee/fixtures/items.json
Normal file
15
templates/coffee/fixtures/items.json
Normal file
@@ -0,0 +1,15 @@
|
||||
[
|
||||
{
|
||||
"content": "\"Video 1\"",
|
||||
"type": "video",
|
||||
"title": "Video 1"
|
||||
}, {
|
||||
"content": "\"Video 2\"",
|
||||
"type": "video",
|
||||
"title": "Video 2"
|
||||
}, {
|
||||
"content": "\"Sample Problem\"",
|
||||
"type": "problem",
|
||||
"title": "Sample Problem"
|
||||
}
|
||||
]
|
||||
1
templates/coffee/fixtures/problem.html
Normal file
1
templates/coffee/fixtures/problem.html
Normal file
@@ -0,0 +1 @@
|
||||
<section id="problem_1" class="problems-wrapper" data-url="/problem/url/"></section>
|
||||
16
templates/coffee/fixtures/problem_content.html
Normal file
16
templates/coffee/fixtures/problem_content.html
Normal file
@@ -0,0 +1,16 @@
|
||||
<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>
|
||||
20
templates/coffee/fixtures/sequence.html
Normal file
20
templates/coffee/fixtures/sequence.html
Normal file
@@ -0,0 +1,20 @@
|
||||
<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>
|
||||
3
templates/coffee/fixtures/tab.html
Normal file
3
templates/coffee/fixtures/tab.html
Normal file
@@ -0,0 +1,3 @@
|
||||
<div id="tab_1" class="tab">
|
||||
<ul class="navigation"></ul>
|
||||
</div>
|
||||
12
templates/coffee/fixtures/video.html
Normal file
12
templates/coffee/fixtures/video.html
Normal file
@@ -0,0 +1,12 @@
|
||||
<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>
|
||||
46
templates/coffee/spec/histogram_spec.coffee
Normal file
46
templates/coffee/spec/histogram_spec.coffee
Normal file
@@ -0,0 +1,46 @@
|
||||
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
|
||||
62
templates/coffee/spec/histogram_spec.js
Normal file
62
templates/coffee/spec/histogram_spec.js
Normal file
@@ -0,0 +1,62 @@
|
||||
(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);
|
||||
35
templates/coffee/spec/logger_spec.coffee
Normal file
35
templates/coffee/spec/logger_spec.coffee
Normal file
@@ -0,0 +1,35 @@
|
||||
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
|
||||
46
templates/coffee/spec/logger_spec.js
Normal file
46
templates/coffee/spec/logger_spec.js
Normal file
@@ -0,0 +1,46 @@
|
||||
(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);
|
||||
250
templates/coffee/spec/modules/problem_spec.coffee
Normal file
250
templates/coffee/spec/modules/problem_spec.coffee
Normal file
@@ -0,0 +1,250 @@
|
||||
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"
|
||||
301
templates/coffee/spec/modules/problem_spec.js
Normal file
301
templates/coffee/spec/modules/problem_spec.js
Normal file
@@ -0,0 +1,301 @@
|
||||
(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);
|
||||
156
templates/coffee/spec/modules/sequence_spec.coffee
Normal file
156
templates/coffee/spec/modules/sequence_spec.coffee
Normal file
@@ -0,0 +1,156 @@
|
||||
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"]'
|
||||
199
templates/coffee/spec/modules/sequence_spec.js
Normal file
199
templates/coffee/spec/modules/sequence_spec.js
Normal file
@@ -0,0 +1,199 @@
|
||||
(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);
|
||||
39
templates/coffee/spec/modules/tab_spec.coffee
Normal file
39
templates/coffee/spec/modules/tab_spec.coffee
Normal file
@@ -0,0 +1,39 @@
|
||||
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
|
||||
55
templates/coffee/spec/modules/tab_spec.js
Normal file
55
templates/coffee/spec/modules/tab_spec.js
Normal file
@@ -0,0 +1,55 @@
|
||||
(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);
|
||||
294
templates/coffee/spec/modules/video/video_caption_spec.coffee
Normal file
294
templates/coffee/spec/modules/video/video_caption_spec.coffee
Normal file
@@ -0,0 +1,294 @@
|
||||
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()
|
||||
317
templates/coffee/spec/modules/video/video_caption_spec.js
Normal file
317
templates/coffee/spec/modules/video/video_caption_spec.js
Normal file
@@ -0,0 +1,317 @@
|
||||
(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);
|
||||
@@ -0,0 +1,73 @@
|
||||
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
|
||||
72
templates/coffee/spec/modules/video/video_control_spec.js
Normal file
72
templates/coffee/spec/modules/video/video_control_spec.js
Normal file
@@ -0,0 +1,72 @@
|
||||
(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);
|
||||
381
templates/coffee/spec/modules/video/video_player_spec.coffee
Normal file
381
templates/coffee/spec/modules/video/video_player_spec.coffee
Normal file
@@ -0,0 +1,381 @@
|
||||
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'
|
||||
441
templates/coffee/spec/modules/video/video_player_spec.js
Normal file
441
templates/coffee/spec/modules/video/video_player_spec.js
Normal file
@@ -0,0 +1,441 @@
|
||||
(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);
|
||||
@@ -0,0 +1,122 @@
|
||||
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'
|
||||
@@ -0,0 +1,154 @@
|
||||
(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);
|
||||
@@ -0,0 +1,95 @@
|
||||
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'
|
||||
103
templates/coffee/spec/modules/video/video_speed_control_spec.js
Normal file
103
templates/coffee/spec/modules/video/video_speed_control_spec.js
Normal file
@@ -0,0 +1,103 @@
|
||||
(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);
|
||||
121
templates/coffee/spec/modules/video_spec.coffee
Normal file
121
templates/coffee/spec/modules/video_spec.coffee
Normal file
@@ -0,0 +1,121 @@
|
||||
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
|
||||
148
templates/coffee/spec/modules/video_spec.js
Normal file
148
templates/coffee/spec/modules/video_spec.js
Normal file
@@ -0,0 +1,148 @@
|
||||
(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);
|
||||
18
templates/coffee/spec/time_spec.coffee
Normal file
18
templates/coffee/spec/time_spec.coffee
Normal file
@@ -0,0 +1,18 @@
|
||||
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'
|
||||
28
templates/coffee/spec/time_spec.js
Normal file
28
templates/coffee/spec/time_spec.js
Normal file
@@ -0,0 +1,28 @@
|
||||
(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
|
||||
],
|
||||
xaxis:
|
||||
min: -1
|
||||
max: Math.max $.map(@xTicks, (data) -> data[0] + 1)
|
||||
max: Math.max.apply Math, $.map(@xTicks, (data) -> data[0] + 1)
|
||||
ticks: @xTicks
|
||||
tickLength: 0
|
||||
yaxis:
|
||||
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
|
||||
labelWidth: 50
|
||||
|
||||
@@ -14,7 +14,6 @@ class @Logger
|
||||
event: ''
|
||||
page: window.location.href
|
||||
async: false
|
||||
return true
|
||||
|
||||
# Keeping this for conpatibility issue only.
|
||||
@log_event = Logger.log
|
||||
|
||||
@@ -8,7 +8,7 @@ class @Problem
|
||||
$(selector, @element)
|
||||
|
||||
bind: =>
|
||||
MathJax.Hub.Queue ["Typeset",MathJax.Hub]
|
||||
MathJax.Hub.Queue ["Typeset", MathJax.Hub]
|
||||
window.update_schematics()
|
||||
@$('section.action input:button').click @refreshAnswers
|
||||
@$('section.action input.check').click @check
|
||||
@@ -43,15 +43,16 @@ class @Problem
|
||||
$.postWithPrefix "/modx/problem/#{@id}/problem_show", (response) =>
|
||||
$.each response, (key, value) =>
|
||||
if $.isArray(value)
|
||||
$.each value, (index, answer_index) =>
|
||||
@$("#label[for='input_#{key}_#{value[answer_index]}']").attr
|
||||
for choice in value
|
||||
@$("label[for='input_#{key}_#{choice}']").attr
|
||||
correct_answer: 'true'
|
||||
@$("#answer_#{key}").text(value)
|
||||
else
|
||||
@$("#answer_#{key}").text(value)
|
||||
@$('.show').val 'Hide Answer'
|
||||
@element.addClass 'showed'
|
||||
else
|
||||
@$('[id^=answer_]').text('')
|
||||
@$('[correct_answer]').attr(correct_answer: null)
|
||||
@$('[id^=answer_]').text ''
|
||||
@$('[correct_answer]').attr correct_answer: null
|
||||
@element.removeClass 'showed'
|
||||
@$('.show').val 'Show Answer'
|
||||
|
||||
|
||||
@@ -11,7 +11,6 @@ class @Sequence
|
||||
bind: ->
|
||||
@element.bind 'contentChanged', @toggleArrows
|
||||
@$('#sequence-list a').click @goto
|
||||
@$('.sequence-nav li a').hover @navHover
|
||||
|
||||
buildNavigation: ->
|
||||
$.each @elements, (index, item) =>
|
||||
@@ -34,37 +33,34 @@ class @Sequence
|
||||
@$('.sequence-nav-buttons .next a').removeClass('disabled').click(@next)
|
||||
|
||||
render: (new_position) ->
|
||||
if @position != undefined
|
||||
@mark_visited @position
|
||||
$.postWithPrefix "/modx/#{@tag}/#{@id}/goto_position", 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
|
||||
@$('#seq_content').html eval(@elements[new_position - 1].content)
|
||||
|
||||
MathJax.Hub.Queue(["Typeset",MathJax.Hub])
|
||||
MathJax.Hub.Queue(["Typeset", MathJax.Hub])
|
||||
@position = new_position
|
||||
@element.trigger 'contentChanged'
|
||||
|
||||
navHover: (event) =>
|
||||
$(event.target).siblings().toggleClass("shown")
|
||||
|
||||
goto: (event) =>
|
||||
event.preventDefault()
|
||||
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
|
||||
|
||||
next: (event) =>
|
||||
event.preventDefault()
|
||||
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
|
||||
|
||||
previous: (event) =>
|
||||
event.preventDefault()
|
||||
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
|
||||
|
||||
link_for: (position) ->
|
||||
|
||||
@@ -3,6 +3,8 @@ class @Video
|
||||
window.player = null
|
||||
@element = $("#video_#{@id}")
|
||||
@parseVideos videos
|
||||
@fetchMetadata()
|
||||
@parseSpeed()
|
||||
$("#video_#{@id}").data('video', this)
|
||||
|
||||
if YT.Player
|
||||
@@ -21,8 +23,6 @@ class @Video
|
||||
video = video.split(/:/)
|
||||
speed = parseFloat(video[0]).toFixed(2).replace /\.00$/, '.0'
|
||||
@videos[speed] = video[1]
|
||||
@fetchMetadata()
|
||||
@parseSpeed()
|
||||
|
||||
parseSpeed: ->
|
||||
@setSpeed($.cookie('video_speed'))
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
class VideoCaption
|
||||
class @VideoCaption
|
||||
constructor: (@player, @youtubeId) ->
|
||||
@render()
|
||||
@bind()
|
||||
@@ -85,7 +85,7 @@ class VideoCaption
|
||||
clearTimeout @frozen if @frozen
|
||||
@frozen = setTimeout @onMouseLeave, 10000
|
||||
|
||||
onMovement: (event) =>
|
||||
onMovement: =>
|
||||
@onMouseEnter()
|
||||
|
||||
onMouseLeave: =>
|
||||
@@ -107,24 +107,23 @@ class VideoCaption
|
||||
@captionHeight() / 2 - element.height() / 2
|
||||
|
||||
topSpacingHeight: ->
|
||||
@calculateOffset(@$('.subtitles li:not(.spacing).first'))
|
||||
@calculateOffset(@$('.subtitles li:not(.spacing):first'))
|
||||
|
||||
bottomSpacingHeight: ->
|
||||
@calculateOffset(@$('.subtitles li:not(.spacing).last'))
|
||||
@calculateOffset(@$('.subtitles li:not(.spacing):last'))
|
||||
|
||||
toggle: (event) =>
|
||||
event.preventDefault()
|
||||
if @player.element.hasClass('closed')
|
||||
@$('.hide-subtitles').attr('title', 'Turn off captions')
|
||||
@player.element.removeClass('closed')
|
||||
@scrollCaption()
|
||||
else
|
||||
@$('.hide-subtitles').attr('title', 'Turn on captions')
|
||||
@player.element.addClass('closed')
|
||||
@scrollCaption()
|
||||
|
||||
captionHeight: ->
|
||||
if @player.element.hasClass('fullscreen')
|
||||
$(window).height() - @$('.video-controls').height()
|
||||
else
|
||||
@$('.video-wrapper').height()
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
class VideoControl
|
||||
class @VideoControl
|
||||
constructor: (@player) ->
|
||||
@render()
|
||||
@bind()
|
||||
@@ -36,7 +36,7 @@ class VideoControl
|
||||
|
||||
togglePlayback: (event) =>
|
||||
event.preventDefault()
|
||||
if $(event.target).hasClass('play')
|
||||
$(@player).trigger('play')
|
||||
else
|
||||
if @player.isPlaying()
|
||||
$(@player).trigger('pause')
|
||||
else
|
||||
$(@player).trigger('play')
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
class VideoPlayer
|
||||
class @VideoPlayer
|
||||
constructor: (@video) ->
|
||||
@currentTime = 0
|
||||
@element = $("#video_#{@video.id}")
|
||||
@@ -18,17 +18,17 @@ class VideoPlayer
|
||||
$(document).keyup @bindExitFullScreen
|
||||
|
||||
@$('.add-fullscreen').click @toggleFullScreen
|
||||
@addToolTip unless onTouchBasedDevice()
|
||||
@addToolTip() unless onTouchBasedDevice()
|
||||
|
||||
bindExitFullScreen: (event) =>
|
||||
if @element.hasClass('fullscreen') && event.keyCode == 27
|
||||
@toggleFullScreen(event)
|
||||
|
||||
render: ->
|
||||
new VideoControl(this)
|
||||
new VideoCaption(this, @video.youtubeId('1.0'))
|
||||
new VideoSpeedControl(this, @video.speeds)
|
||||
new VideoProgressSlider(this)
|
||||
new VideoControl @
|
||||
new VideoCaption @, @video.youtubeId('1.0')
|
||||
new VideoSpeedControl @, @video.speeds
|
||||
new VideoProgressSlider @
|
||||
@player = new YT.Player @video.id,
|
||||
playerVars:
|
||||
controls: 0
|
||||
@@ -48,9 +48,9 @@ class VideoPlayer
|
||||
at: 'top center'
|
||||
|
||||
onReady: =>
|
||||
@setProgress(0, @duration())
|
||||
$(@).trigger('ready')
|
||||
unless true || onTouchBasedDevice()
|
||||
$(@).trigger('updatePlayTime', 0)
|
||||
unless onTouchBasedDevice()
|
||||
$('.course-content .video:first').data('video').player.play()
|
||||
|
||||
onStateChange: (event) =>
|
||||
@@ -92,7 +92,6 @@ class VideoPlayer
|
||||
@player.loadVideoById(@video.youtubeId(), @currentTime)
|
||||
else
|
||||
@player.cueVideoById(@video.youtubeId(), @currentTime)
|
||||
@setProgress(@currentTime, @duration())
|
||||
$(@).trigger('updatePlayTime', @currentTime)
|
||||
|
||||
update: =>
|
||||
@@ -100,13 +99,8 @@ class VideoPlayer
|
||||
$(@).trigger('updatePlayTime', @currentTime)
|
||||
|
||||
onUpdatePlayTime: (event, time) =>
|
||||
@setProgress(@currentTime) if time
|
||||
|
||||
setProgress: (time) =>
|
||||
progress = Time.format(time) + ' / ' + Time.format(@duration())
|
||||
if @progress != progress
|
||||
@$(".vidtime").html(progress)
|
||||
@progress = progress
|
||||
@$(".vidtime").html(progress)
|
||||
|
||||
toggleFullScreen: (event) =>
|
||||
event.preventDefault()
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
class VideoProgressSlider
|
||||
class @VideoProgressSlider
|
||||
constructor: (@player) ->
|
||||
@buildSlider()
|
||||
@buildHandle()
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
class VideoSpeedControl
|
||||
class @VideoSpeedControl
|
||||
constructor: (@player, @speeds) ->
|
||||
@render()
|
||||
@bind()
|
||||
@@ -13,8 +13,8 @@ class VideoSpeedControl
|
||||
@$('.speeds').click -> $(this).toggleClass('open')
|
||||
else
|
||||
@$('.speeds').mouseover -> $(this).addClass('open')
|
||||
.mouseout -> $(this).removeClass('open')
|
||||
.click (event) ->
|
||||
@$('.speeds').mouseout -> $(this).removeClass('open')
|
||||
@$('.speeds').click (event) ->
|
||||
event.preventDefault()
|
||||
$(this).removeClass('open')
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
class @Time
|
||||
@format: (time) ->
|
||||
pad = (number) -> if number < 10 then "0#{number}" else number
|
||||
|
||||
seconds = Math.floor time
|
||||
minutes = Math.floor seconds / 60
|
||||
hours = Math.floor minutes / 60
|
||||
@@ -7,15 +9,9 @@ class @Time
|
||||
minutes = minutes % 60
|
||||
|
||||
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
|
||||
number
|
||||
"#{minutes}:#{pad(seconds % 60)}"
|
||||
|
||||
@convert: (time, oldSpeed, newSpeed) ->
|
||||
(time * oldSpeed / newSpeed).toFixed(3)
|
||||
|
||||
68
templates/jasmine/base.html
Normal file
68
templates/jasmine/base.html
Normal file
@@ -0,0 +1,68 @@
|
||||
<!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>
|
||||
Reference in New Issue
Block a user