Commit 93248a30 by Prem Sichanugrist

Add test coverage for JavaScript

parent c7b5ec92
......@@ -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();
});
});
......
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);
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')
[
{
"content": "\"Video 1\"",
"type": "video",
"title": "Video 1"
}, {
"content": "\"Video 2\"",
"type": "video",
"title": "Video 2"
}, {
"content": "\"Sample Problem\"",
"type": "problem",
"title": "Sample Problem"
}
]
<section id="problem_1" class="problems-wrapper" data-url="/problem/url/"></section>
<h2 class="problem-header">Problem Header</h2>
<section class="problem">
<p>Problem Content</p>
<section class="action">
<input type="hidden" name="problem_id" value="1">
<input class="check" type="button" value="Check">
<input class="reset" type="button" value="Reset">
<input class="save" type="button" value="Save">
<input class="show" type="button" value="Show Answer">
<a href="/courseware/6.002_Spring_2012/${ explain }" class="new-page">Explanation</a>
<section class="submission_feedback"></section>
</section>
</section>
<div id="sequence_1" class="sequence">
<nav class="sequence-nav">
<ol id="sequence-list">
</ol>
<ul class="sequence-nav-buttons">
<li class="prev"><a href="#">Previous</a></li>
<li class="next"><a href="#">Next</a></li>
</ul>
</nav>
<div id="seq_content"></div>
<nav class="sequence-bottom">
<ul class="sequence-nav-buttons">
<li class="prev"><a href="#">Previous</a></li>
<li class="next"><a href="#">Next</a></li>
</ul>
</nav>
</div>
<div id="tab_1" class="tab">
<ul class="navigation"></ul>
</div>
<div class="course-content">
<div id="video_example" class="video">
<div class="tc-wrapper">
<article class="video-wrapper">
<section class="video-player">
<div id="example"></div>
</section>
<section class="video-controls"></section>
</article>
</div>
</div>
</div>
describe 'Histogram', ->
beforeEach ->
spyOn $, 'plot'
describe 'constructor', ->
it 'instantiate the data arrays', ->
histogram = new Histogram 1, []
expect(histogram.xTicks).toEqual []
expect(histogram.yTicks).toEqual []
expect(histogram.data).toEqual []
describe 'calculate', ->
beforeEach ->
@histogram = new Histogram(1, [[1, 1], [2, 2], [3, 3]])
it 'store the correct value for data', ->
expect(@histogram.data).toEqual [[1, Math.log(2)], [2, Math.log(3)], [3, Math.log(4)]]
it 'store the correct value for x ticks', ->
expect(@histogram.xTicks).toEqual [[1, '1'], [2, '2'], [3, '3']]
it 'store the correct value for y ticks', ->
expect(@histogram.yTicks).toEqual
describe 'render', ->
it 'call flot with correct option', ->
new Histogram(1, [[1, 1], [2, 2], [3, 3]])
expect($.plot).toHaveBeenCalledWith $("#histogram_1"), [
data: [[1, Math.log(2)], [2, Math.log(3)], [3, Math.log(4)]]
bars:
show: true
align: 'center'
lineWidth: 0
fill: 1.0
color: "#b72121"
],
xaxis:
min: -1
max: 4
ticks: [[1, '1'], [2, '2'], [3, '3']]
tickLength: 0
yaxis:
min: 0.0
max: Math.log(4) * 1.1
ticks: [[Math.log(2), '1'], [Math.log(3), '2'], [Math.log(4), '3']]
labelWidth: 50
(function() {
describe('Histogram', function() {
beforeEach(function() {
return spyOn($, 'plot');
});
describe('constructor', function() {
return it('instantiate the data arrays', function() {
var histogram;
histogram = new Histogram(1, []);
expect(histogram.xTicks).toEqual([]);
expect(histogram.yTicks).toEqual([]);
return expect(histogram.data).toEqual([]);
});
});
describe('calculate', function() {
beforeEach(function() {
return this.histogram = new Histogram(1, [[1, 1], [2, 2], [3, 3]]);
});
it('store the correct value for data', function() {
return expect(this.histogram.data).toEqual([[1, Math.log(2)], [2, Math.log(3)], [3, Math.log(4)]]);
});
it('store the correct value for x ticks', function() {
return expect(this.histogram.xTicks).toEqual([[1, '1'], [2, '2'], [3, '3']]);
});
return it('store the correct value for y ticks', function() {
return expect(this.histogram.yTicks).toEqual;
});
});
return describe('render', function() {
return it('call flot with correct option', function() {
new Histogram(1, [[1, 1], [2, 2], [3, 3]]);
return expect($.plot).toHaveBeenCalledWith($("#histogram_1"), [
{
data: [[1, Math.log(2)], [2, Math.log(3)], [3, Math.log(4)]],
bars: {
show: true,
align: 'center',
lineWidth: 0,
fill: 1.0
},
color: "#b72121"
}
], {
xaxis: {
min: -1,
max: 4,
ticks: [[1, '1'], [2, '2'], [3, '3']],
tickLength: 0
},
yaxis: {
min: 0.0,
max: Math.log(4) * 1.1,
ticks: [[Math.log(2), '1'], [Math.log(3), '2'], [Math.log(4), '3']],
labelWidth: 50
}
});
});
});
});
}).call(this);
describe 'Logger', ->
it 'expose window.log_event', ->
jasmine.stubRequests()
expect(window.log_event).toBe Logger.log
describe 'log', ->
it 'send a request to log event', ->
spyOn $, 'getWithPrefix'
Logger.log 'example', 'data'
expect($.getWithPrefix).toHaveBeenCalledWith '/event',
event_type: 'example'
event: '"data"'
page: window.location.href
describe 'bind', ->
beforeEach ->
Logger.bind()
Courseware.prefix = '/6002x'
afterEach ->
window.onunload = null
it 'bind the onunload event', ->
expect(window.onunload).toEqual jasmine.any(Function)
it 'send a request to log event', ->
spyOn($, 'ajax')
$(window).trigger('onunload')
expect($.ajax).toHaveBeenCalledWith
url: "#{Courseware.prefix}/event",
data:
event_type: 'page_close'
event: ''
page: window.location.href
async: false
(function() {
describe('Logger', function() {
it('expose window.log_event', function() {
jasmine.stubRequests();
return expect(window.log_event).toBe(Logger.log);
});
describe('log', function() {
return it('send a request to log event', function() {
spyOn($, 'getWithPrefix');
Logger.log('example', 'data');
return expect($.getWithPrefix).toHaveBeenCalledWith('/event', {
event_type: 'example',
event: '"data"',
page: window.location.href
});
});
});
return describe('bind', function() {
beforeEach(function() {
Logger.bind();
return Courseware.prefix = '/6002x';
});
afterEach(function() {
return window.onunload = null;
});
it('bind the onunload event', function() {
return expect(window.onunload).toEqual(jasmine.any(Function));
});
return it('send a request to log event', function() {
spyOn($, 'ajax');
$(window).trigger('onunload');
return expect($.ajax).toHaveBeenCalledWith({
url: "" + Courseware.prefix + "/event",
data: {
event_type: 'page_close',
event: '',
page: window.location.href
},
async: false
});
});
});
});
}).call(this);
describe 'Problem', ->
beforeEach ->
# Stub MathJax
window.MathJax = { Hub: { Queue: -> } }
window.update_schematics = ->
loadFixtures 'problem.html'
spyOn Logger, 'log'
spyOn($.fn, 'load').andCallFake (url, callback) ->
$(@).html readFixtures('problem_content.html')
callback()
describe 'constructor', ->
beforeEach ->
@problem = new Problem 1, '/problem/url/'
it 'set the element', ->
expect(@problem.element).toBe '#problem_1'
it 'set the content url', ->
expect(@problem.content_url).toEqual '/problem/url/problem_get?id=1'
it 'render the content', ->
expect($.fn.load).toHaveBeenCalledWith @problem.content_url, @problem.bind
describe 'bind', ->
beforeEach ->
spyOn MathJax.Hub, 'Queue'
spyOn window, 'update_schematics'
@problem = new Problem 1, '/problem/url/'
it 'set mathjax typeset', ->
expect(MathJax.Hub.Queue).toHaveBeenCalled()
it 'update schematics', ->
expect(window.update_schematics).toHaveBeenCalled()
it 'bind answer refresh on button click', ->
expect($('section.action input:button')).toHandleWith 'click', @problem.refreshAnswers
it 'bind the check button', ->
expect($('section.action input.check')).toHandleWith 'click', @problem.check
it 'bind the reset button', ->
expect($('section.action input.reset')).toHandleWith 'click', @problem.reset
it 'bind the show button', ->
expect($('section.action input.show')).toHandleWith 'click', @problem.show
it 'bind the save button', ->
expect($('section.action input.save')).toHandleWith 'click', @problem.save
describe 'render', ->
beforeEach ->
@problem = new Problem 1, '/problem/url/'
@bind = @problem.bind
spyOn @problem, 'bind'
describe 'with content given', ->
beforeEach ->
@problem.render 'Hello World'
it 'render the content', ->
expect(@problem.element.html()).toEqual 'Hello World'
it 're-bind the content', ->
expect(@problem.bind).toHaveBeenCalled()
describe 'with no content given', ->
it 'load the content via ajax', ->
expect($.fn.load).toHaveBeenCalledWith @problem.content_url, @bind
describe 'check', ->
beforeEach ->
jasmine.stubRequests()
@problem = new Problem 1, '/problem/url/'
@problem.answers = 'foo=1&bar=2'
it 'log the problem_check event', ->
@problem.check()
expect(Logger.log).toHaveBeenCalledWith 'problem_check', 'foo=1&bar=2'
it 'submit the answer for check', ->
spyOn $, 'postWithPrefix'
@problem.check()
expect($.postWithPrefix).toHaveBeenCalledWith '/modx/problem/1/problem_check', 'foo=1&bar=2', jasmine.any(Function)
describe 'when the response is correct', ->
it 'call render with returned content', ->
spyOn($, 'postWithPrefix').andCallFake (url, answers, callback) -> callback(success: 'correct', contents: 'Correct!')
@problem.check()
expect(@problem.element.html()).toEqual 'Correct!'
describe 'when the response is incorrect', ->
it 'call render with returned content', ->
spyOn($, 'postWithPrefix').andCallFake (url, answers, callback) -> callback(success: 'incorrect', contents: 'Correct!')
@problem.check()
expect(@problem.element.html()).toEqual 'Correct!'
describe 'when the response is undetermined', ->
it 'alert the response', ->
spyOn window, 'alert'
spyOn($, 'postWithPrefix').andCallFake (url, answers, callback) -> callback(success: 'Number Only!')
@problem.check()
expect(window.alert).toHaveBeenCalledWith 'Number Only!'
describe 'reset', ->
beforeEach ->
jasmine.stubRequests()
@problem = new Problem 1, '/problem/url/'
it 'log the problem_reset event', ->
@problem.answers = 'foo=1&bar=2'
@problem.reset()
expect(Logger.log).toHaveBeenCalledWith 'problem_reset', 'foo=1&bar=2'
it 'POST to the problem reset page', ->
spyOn $, 'postWithPrefix'
@problem.reset()
expect($.postWithPrefix).toHaveBeenCalledWith '/modx/problem/1/problem_reset', { id: 1 }, jasmine.any(Function)
it 'render the returned content', ->
spyOn($, 'postWithPrefix').andCallFake (url, answers, callback) -> callback("Reset!")
@problem.reset()
expect(@problem.element.html()).toEqual 'Reset!'
describe 'show', ->
beforeEach ->
jasmine.stubRequests()
@problem = new Problem 1, '/problem/url/'
@problem.element.prepend '<div id="answer_1_1" /><div id="answer_1_2" />'
describe 'when the answer has not yet shown', ->
beforeEach ->
@problem.element.removeClass 'showed'
it 'log the problem_show event', ->
@problem.show()
expect(Logger.log).toHaveBeenCalledWith 'problem_show', problem: 1
it 'fetch the answers', ->
spyOn $, 'postWithPrefix'
@problem.show()
expect($.postWithPrefix).toHaveBeenCalledWith '/modx/problem/1/problem_show', jasmine.any(Function)
it 'show the answers', ->
spyOn($, 'postWithPrefix').andCallFake (url, callback) -> callback('1_1': 'One', '1_2': 'Two')
@problem.show()
expect($('#answer_1_1')).toHaveHtml 'One'
expect($('#answer_1_2')).toHaveHtml 'Two'
it 'toggle the show answer button', ->
spyOn($, 'postWithPrefix').andCallFake (url, callback) -> callback({})
@problem.show()
expect($('.show')).toHaveValue 'Hide Answer'
it 'add the showed class to element', ->
spyOn($, 'postWithPrefix').andCallFake (url, callback) -> callback({})
@problem.show()
expect(@problem.element).toHaveClass 'showed'
describe 'multiple choice question', ->
beforeEach ->
@problem.element.prepend '''
<label for="input_1_1_1"><input type="checkbox" name="input_1_1" id="input_1_1_1" value="1"> One</label>
<label for="input_1_1_2"><input type="checkbox" name="input_1_1" id="input_1_1_2" value="2"> Two</label>
<label for="input_1_1_3"><input type="checkbox" name="input_1_1" id="input_1_1_3" value="3"> Three</label>
<label for="input_1_2_1"><input type="radio" name="input_1_2" id="input_1_2_1" value="1"> Other</label>
'''
it 'set the correct_answer attribute on the choice', ->
spyOn($, 'postWithPrefix').andCallFake (url, callback) -> callback('1_1': [2, 3])
@problem.show()
expect($('label[for="input_1_1_1"]')).not.toHaveAttr 'correct_answer', 'true'
expect($('label[for="input_1_1_2"]')).toHaveAttr 'correct_answer', 'true'
expect($('label[for="input_1_1_3"]')).toHaveAttr 'correct_answer', 'true'
expect($('label[for="input_1_2_1"]')).not.toHaveAttr 'correct_answer', 'true'
describe 'when the answers are alreay shown', ->
beforeEach ->
@problem.element.addClass 'showed'
@problem.element.prepend '''
<label for="input_1_1_1" correct_answer="true">
<input type="checkbox" name="input_1_1" id="input_1_1_1" value="1" />
One
</label>
'''
$('#answer_1_1').html('One')
$('#answer_1_2').html('Two')
it 'hide the answers', ->
@problem.show()
expect($('#answer_1_1')).toHaveHtml ''
expect($('#answer_1_2')).toHaveHtml ''
expect($('label[for="input_1_1_1"]')).not.toHaveAttr 'correct_answer'
it 'toggle the show answer button', ->
@problem.show()
expect($('.show')).toHaveValue 'Show Answer'
it 'remove the showed class from element', ->
@problem.show()
expect(@problem.element).not.toHaveClass 'showed'
describe 'save', ->
beforeEach ->
jasmine.stubRequests()
@problem = new Problem 1, '/problem/url/'
@problem.answers = 'foo=1&bar=2'
it 'log the problem_save event', ->
@problem.save()
expect(Logger.log).toHaveBeenCalledWith 'problem_save', 'foo=1&bar=2'
it 'POST to save problem', ->
spyOn $, 'postWithPrefix'
@problem.save()
expect($.postWithPrefix).toHaveBeenCalledWith '/modx/problem/1/problem_save', 'foo=1&bar=2', jasmine.any(Function)
it 'alert to the user', ->
spyOn window, 'alert'
spyOn($, 'postWithPrefix').andCallFake (url, answers, callback) -> callback(success: 'OK')
@problem.save()
expect(window.alert).toHaveBeenCalledWith 'Saved'
describe 'refreshAnswers', ->
beforeEach ->
@problem = new Problem 1, '/problem/url/'
@problem.element.html '''
<textarea class="CodeMirror" />
<input id="input_1_1" name="input_1_1" class="schematic" value="one" />
<input id="input_1_2" name="input_1_2" value="two" />
<input id="input_bogus_3" name="input_bogus_3" value="three" />
'''
@stubSchematic = { update_value: jasmine.createSpy('schematic') }
@stubCodeMirror = { save: jasmine.createSpy('CodeMirror') }
$('input.schematic').get(0).schematic = @stubSchematic
$('textarea.CodeMirror').get(0).CodeMirror = @stubCodeMirror
it 'update each schematic', ->
@problem.refreshAnswers()
expect(@stubSchematic.update_value).toHaveBeenCalled()
it 'update each code block', ->
@problem.refreshAnswers()
expect(@stubCodeMirror.save).toHaveBeenCalled()
it 'serialize all answers', ->
@problem.refreshAnswers()
expect(@problem.answers).toEqual "input_1_1=one&input_1_2=two"
describe 'Sequence', ->
beforeEach ->
# Stub MathJax
window.MathJax = { Hub: { Queue: -> } }
spyOn Logger, 'log'
loadFixtures 'sequence.html'
@items = $.parseJSON readFixtures('items.json')
describe 'constructor', ->
beforeEach ->
@sequence = new Sequence '1', @items, 1
it 'set the element', ->
expect(@sequence.element).toEqual $('#sequence_1')
it 'build the navigation', ->
classes = $('#sequence-list li>a').map(-> $(this).attr('class')).get()
elements = $('#sequence-list li>a').map(-> $(this).attr('data-element')).get()
titles = $('#sequence-list li>a>p').map(-> $(this).html()).get()
expect(classes).toEqual ['seq_video_active', 'seq_video_inactive', 'seq_problem_inactive']
expect(elements).toEqual ['1', '2', '3']
expect(titles).toEqual ['Video 1', 'Video 2', 'Sample Problem']
it 'bind the page events', ->
expect(@sequence.element).toHandleWith 'contentChanged', @sequence.toggleArrows
expect($('#sequence-list a')).toHandleWith 'click', @sequence.goto
it 'render the active sequence content', ->
expect($('#seq_content').html()).toEqual 'Video 1'
describe 'toggleArrows', ->
beforeEach ->
@sequence = new Sequence '1', @items, 1
describe 'when the first tab is active', ->
beforeEach ->
@sequence.position = 1
@sequence.toggleArrows()
it 'disable the previous button', ->
expect($('.sequence-nav-buttons .prev a')).toHaveClass 'disabled'
it 'enable the next button', ->
expect($('.sequence-nav-buttons .next a')).not.toHaveClass 'disabled'
expect($('.sequence-nav-buttons .next a')).toHandleWith 'click', @sequence.next
describe 'when the middle tab is active', ->
beforeEach ->
@sequence.position = 2
@sequence.toggleArrows()
it 'enable the previous button', ->
expect($('.sequence-nav-buttons .prev a')).not.toHaveClass 'disabled'
expect($('.sequence-nav-buttons .prev a')).toHandleWith 'click', @sequence.previous
it 'enable the next button', ->
expect($('.sequence-nav-buttons .next a')).not.toHaveClass 'disabled'
expect($('.sequence-nav-buttons .next a')).toHandleWith 'click', @sequence.next
describe 'when the last tab is active', ->
beforeEach ->
@sequence.position = 3
@sequence.toggleArrows()
it 'enable the previous button', ->
expect($('.sequence-nav-buttons .prev a')).not.toHaveClass 'disabled'
expect($('.sequence-nav-buttons .prev a')).toHandleWith 'click', @sequence.previous
it 'disable the next button', ->
expect($('.sequence-nav-buttons .next a')).toHaveClass 'disabled'
describe 'render', ->
beforeEach ->
spyOn $, 'postWithPrefix'
@sequence = new Sequence '1', @items
spyOnEvent @sequence.element, 'contentChanged'
describe 'with a different position than the current one', ->
beforeEach ->
@sequence.render 1
describe 'with no previous position', ->
it 'does not save the new position', ->
expect($.postWithPrefix).not.toHaveBeenCalled()
describe 'with previous position', ->
beforeEach ->
@sequence.position = 2
@sequence.render 1
it 'mark the previous tab as visited', ->
expect($('[data-element="2"]')).toHaveClass 'seq_video_visited'
it 'save the new position', ->
expect($.postWithPrefix).toHaveBeenCalledWith '/modx/sequential/1/goto_position', position: 1
it 'mark new tab as active', ->
expect($('[data-element="1"]')).toHaveClass 'seq_video_active'
it 'render the new content', ->
expect($('#seq_content').html()).toEqual 'Video 1'
it 'update the position', ->
expect(@sequence.position).toEqual 1
it 'trigger contentChanged event', ->
expect('contentChanged').toHaveBeenTriggeredOn @sequence.element
describe 'with the same position as the current one', ->
it 'should not trigger contentChanged event', ->
@sequence.position = 2
@sequence.render 2
expect('contentChanged').not.toHaveBeenTriggeredOn @sequence.element
describe 'goto', ->
beforeEach ->
jasmine.stubRequests()
@sequence = new Sequence '1', @items, 2
$('[data-element="3"]').click()
it 'log the sequence goto event', ->
expect(Logger.log).toHaveBeenCalledWith 'seq_goto', old: 2, new: 3, id: '1'
it 'call render on the right sequence', ->
expect($('#seq_content').html()).toEqual 'Sample Problem'
describe 'next', ->
beforeEach ->
jasmine.stubRequests()
@sequence = new Sequence '1', @items, 2
$('.sequence-nav-buttons .next a').click()
it 'log the next sequence event', ->
expect(Logger.log).toHaveBeenCalledWith 'seq_next', old: 2, new: 3, id: '1'
it 'call render on the next sequence', ->
expect($('#seq_content').html()).toEqual 'Sample Problem'
describe 'previous', ->
beforeEach ->
jasmine.stubRequests()
@sequence = new Sequence '1', @items, 2
$('.sequence-nav-buttons .prev a').click()
it 'log the previous sequence event', ->
expect(Logger.log).toHaveBeenCalledWith 'seq_prev', old: 2, new: 1, id: '1'
it 'call render on the previous sequence', ->
expect($('#seq_content').html()).toEqual 'Video 1'
describe 'link_for', ->
it 'return a link for specific position', ->
sequence = new Sequence '1', @items, 2
expect(sequence.link_for(2)).toBe '[data-element="2"]'
(function() {
describe('Sequence', function() {
beforeEach(function() {
window.MathJax = {
Hub: {
Queue: function() {}
}
};
spyOn(Logger, 'log');
loadFixtures('sequence.html');
return this.items = $.parseJSON(readFixtures('items.json'));
});
describe('constructor', function() {
beforeEach(function() {
return this.sequence = new Sequence('1', this.items, 1);
});
it('set the element', function() {
return expect(this.sequence.element).toEqual($('#sequence_1'));
});
it('build the navigation', function() {
var classes, elements, titles;
classes = $('#sequence-list li>a').map(function() {
return $(this).attr('class');
}).get();
elements = $('#sequence-list li>a').map(function() {
return $(this).attr('data-element');
}).get();
titles = $('#sequence-list li>a>p').map(function() {
return $(this).html();
}).get();
expect(classes).toEqual(['seq_video_active', 'seq_video_inactive', 'seq_problem_inactive']);
expect(elements).toEqual(['1', '2', '3']);
return expect(titles).toEqual(['Video 1', 'Video 2', 'Sample Problem']);
});
it('bind the page events', function() {
expect(this.sequence.element).toHandleWith('contentChanged', this.sequence.toggleArrows);
return expect($('#sequence-list a')).toHandleWith('click', this.sequence.goto);
});
return it('render the active sequence content', function() {
return expect($('#seq_content').html()).toEqual('Video 1');
});
});
describe('toggleArrows', function() {
beforeEach(function() {
return this.sequence = new Sequence('1', this.items, 1);
});
describe('when the first tab is active', function() {
beforeEach(function() {
this.sequence.position = 1;
return this.sequence.toggleArrows();
});
it('disable the previous button', function() {
return expect($('.sequence-nav-buttons .prev a')).toHaveClass('disabled');
});
return it('enable the next button', function() {
expect($('.sequence-nav-buttons .next a')).not.toHaveClass('disabled');
return expect($('.sequence-nav-buttons .next a')).toHandleWith('click', this.sequence.next);
});
});
describe('when the middle tab is active', function() {
beforeEach(function() {
this.sequence.position = 2;
return this.sequence.toggleArrows();
});
it('enable the previous button', function() {
expect($('.sequence-nav-buttons .prev a')).not.toHaveClass('disabled');
return expect($('.sequence-nav-buttons .prev a')).toHandleWith('click', this.sequence.previous);
});
return it('enable the next button', function() {
expect($('.sequence-nav-buttons .next a')).not.toHaveClass('disabled');
return expect($('.sequence-nav-buttons .next a')).toHandleWith('click', this.sequence.next);
});
});
return describe('when the last tab is active', function() {
beforeEach(function() {
this.sequence.position = 3;
return this.sequence.toggleArrows();
});
it('enable the previous button', function() {
expect($('.sequence-nav-buttons .prev a')).not.toHaveClass('disabled');
return expect($('.sequence-nav-buttons .prev a')).toHandleWith('click', this.sequence.previous);
});
return it('disable the next button', function() {
return expect($('.sequence-nav-buttons .next a')).toHaveClass('disabled');
});
});
});
describe('render', function() {
beforeEach(function() {
spyOn($, 'postWithPrefix');
this.sequence = new Sequence('1', this.items);
return spyOnEvent(this.sequence.element, 'contentChanged');
});
describe('with a different position than the current one', function() {
beforeEach(function() {
return this.sequence.render(1);
});
describe('with no previous position', function() {
return it('does not save the new position', function() {
return expect($.postWithPrefix).not.toHaveBeenCalled();
});
});
describe('with previous position', function() {
beforeEach(function() {
this.sequence.position = 2;
return this.sequence.render(1);
});
it('mark the previous tab as visited', function() {
return expect($('[data-element="2"]')).toHaveClass('seq_video_visited');
});
return it('save the new position', function() {
return expect($.postWithPrefix).toHaveBeenCalledWith('/modx/sequential/1/goto_position', {
position: 1
});
});
});
it('mark new tab as active', function() {
return expect($('[data-element="1"]')).toHaveClass('seq_video_active');
});
it('render the new content', function() {
return expect($('#seq_content').html()).toEqual('Video 1');
});
it('update the position', function() {
return expect(this.sequence.position).toEqual(1);
});
return it('trigger contentChanged event', function() {
return expect('contentChanged').toHaveBeenTriggeredOn(this.sequence.element);
});
});
return describe('with the same position as the current one', function() {
return it('should not trigger contentChanged event', function() {
this.sequence.position = 2;
this.sequence.render(2);
return expect('contentChanged').not.toHaveBeenTriggeredOn(this.sequence.element);
});
});
});
describe('goto', function() {
beforeEach(function() {
jasmine.stubRequests();
this.sequence = new Sequence('1', this.items, 2);
return $('[data-element="3"]').click();
});
it('log the sequence goto event', function() {
return expect(Logger.log).toHaveBeenCalledWith('seq_goto', {
old: 2,
"new": 3,
id: '1'
});
});
return it('call render on the right sequence', function() {
return expect($('#seq_content').html()).toEqual('Sample Problem');
});
});
describe('next', function() {
beforeEach(function() {
jasmine.stubRequests();
this.sequence = new Sequence('1', this.items, 2);
return $('.sequence-nav-buttons .next a').click();
});
it('log the next sequence event', function() {
return expect(Logger.log).toHaveBeenCalledWith('seq_next', {
old: 2,
"new": 3,
id: '1'
});
});
return it('call render on the next sequence', function() {
return expect($('#seq_content').html()).toEqual('Sample Problem');
});
});
describe('previous', function() {
beforeEach(function() {
jasmine.stubRequests();
this.sequence = new Sequence('1', this.items, 2);
return $('.sequence-nav-buttons .prev a').click();
});
it('log the previous sequence event', function() {
return expect(Logger.log).toHaveBeenCalledWith('seq_prev', {
old: 2,
"new": 1,
id: '1'
});
});
return it('call render on the previous sequence', function() {
return expect($('#seq_content').html()).toEqual('Video 1');
});
});
return describe('link_for', function() {
return it('return a link for specific position', function() {
var sequence;
sequence = new Sequence('1', this.items, 2);
return expect(sequence.link_for(2)).toBe('[data-element="2"]');
});
});
});
}).call(this);
describe 'Tab', ->
beforeEach ->
loadFixtures 'tab.html'
@items = $.parseJSON readFixtures('items.json')
describe 'constructor', ->
beforeEach ->
spyOn($.fn, 'tabs')
@tab = new Tab 1, @items
it 'set the element', ->
expect(@tab.element).toEqual $('#tab_1')
it 'build the tabs', ->
links = $('.navigation li>a').map(-> $(this).attr('href')).get()
expect(links).toEqual ['#tab-1-0', '#tab-1-1', '#tab-1-2']
it 'build the container', ->
containers = $('section').map(-> $(this).attr('id')).get()
expect(containers).toEqual ['tab-1-0', 'tab-1-1', 'tab-1-2']
it 'bind the tabs', ->
expect($.fn.tabs).toHaveBeenCalledWith show: @tab.onShow
describe 'onShow', ->
beforeEach ->
@tab = new Tab 1, @items
$('[href="#tab-1-0"]').click()
it 'replace content in the container', ->
$('[href="#tab-1-1"]').click()
expect($('#tab-1-0').html()).toEqual ''
expect($('#tab-1-1').html()).toEqual 'Video 2'
expect($('#tab-1-2').html()).toEqual ''
it 'trigger contentChanged event on the element', ->
spyOnEvent @tab.element, 'contentChanged'
$('[href="#tab-1-1"]').click()
expect('contentChanged').toHaveBeenTriggeredOn @tab.element
(function() {
describe('Tab', function() {
beforeEach(function() {
loadFixtures('tab.html');
return this.items = $.parseJSON(readFixtures('items.json'));
});
describe('constructor', function() {
beforeEach(function() {
spyOn($.fn, 'tabs');
return this.tab = new Tab(1, this.items);
});
it('set the element', function() {
return expect(this.tab.element).toEqual($('#tab_1'));
});
it('build the tabs', function() {
var links;
links = $('.navigation li>a').map(function() {
return $(this).attr('href');
}).get();
return expect(links).toEqual(['#tab-1-0', '#tab-1-1', '#tab-1-2']);
});
it('build the container', function() {
var containers;
containers = $('section').map(function() {
return $(this).attr('id');
}).get();
return expect(containers).toEqual(['tab-1-0', 'tab-1-1', 'tab-1-2']);
});
return it('bind the tabs', function() {
return expect($.fn.tabs).toHaveBeenCalledWith({
show: this.tab.onShow
});
});
});
return describe('onShow', function() {
beforeEach(function() {
this.tab = new Tab(1, this.items);
return $('[href="#tab-1-0"]').click();
});
it('replace content in the container', function() {
$('[href="#tab-1-1"]').click();
expect($('#tab-1-0').html()).toEqual('');
expect($('#tab-1-1').html()).toEqual('Video 2');
return expect($('#tab-1-2').html()).toEqual('');
});
return it('trigger contentChanged event on the element', function() {
spyOnEvent(this.tab.element, 'contentChanged');
$('[href="#tab-1-1"]').click();
return expect('contentChanged').toHaveBeenTriggeredOn(this.tab.element);
});
});
});
}).call(this);
describe 'VideoControl', ->
beforeEach ->
@player = jasmine.stubVideoPlayer @
describe 'constructor', ->
beforeEach ->
@control = new VideoControl @player
it 'render the video controls', ->
expect($('.video-controls').html()).toContain '''
<div class="slider"></div>
<div>
<ul class="vcr">
<li><a class="video_control play">Play</a></li>
<li>
<div class="vidtime">0:00 / 0:00</div>
</li>
</ul>
<div class="secondary-controls">
<a href="#" class="add-fullscreen" title="Fill browser">Fill Browser</a>
</div>
</div>
'''
it 'bind player events', ->
expect($(@player)).toHandleWith 'play', @control.onPlay
expect($(@player)).toHandleWith 'pause', @control.onPause
expect($(@player)).toHandleWith 'ended', @control.onPause
it 'bind the playback button', ->
expect($('.video_control')).toHandleWith 'click', @control.togglePlayback
describe 'onPlay', ->
beforeEach ->
@control = new VideoControl @player
@control.onPlay()
it 'switch playback button to play state', ->
expect($('.video_control')).not.toHaveClass 'play'
expect($('.video_control')).toHaveClass 'pause'
expect($('.video_control')).toHaveHtml 'Pause'
describe 'onPause', ->
beforeEach ->
@control = new VideoControl @player
@control.onPause()
it 'switch playback button to pause state', ->
expect($('.video_control')).not.toHaveClass 'pause'
expect($('.video_control')).toHaveClass 'play'
expect($('.video_control')).toHaveHtml 'Play'
describe 'togglePlayback', ->
beforeEach ->
@control = new VideoControl @player
describe 'when the video is playing', ->
beforeEach ->
spyOn(@player, 'isPlaying').andReturn true
spyOnEvent @player, 'pause'
@control.togglePlayback jQuery.Event('click')
it 'trigger the pause event', ->
expect('pause').toHaveBeenTriggeredOn @player
describe 'when the video is paused', ->
beforeEach ->
spyOn(@player, 'isPlaying').andReturn false
spyOnEvent @player, 'play'
@control.togglePlayback jQuery.Event('click')
it 'trigger the play event', ->
expect('play').toHaveBeenTriggeredOn @player
(function() {
describe('VideoControl', function() {
beforeEach(function() {
return this.player = jasmine.stubVideoPlayer(this);
});
describe('constructor', function() {
beforeEach(function() {
return this.control = new VideoControl(this.player);
});
it('render the video controls', function() {
return expect($('.video-controls').html()).toContain('<div class="slider"></div>\n<div>\n <ul class="vcr">\n <li><a class="video_control play">Play</a></li>\n <li>\n <div class="vidtime">0:00 / 0:00</div>\n </li>\n </ul>\n <div class="secondary-controls">\n <a href="#" class="add-fullscreen" title="Fill browser">Fill Browser</a>\n </div>\n</div>');
});
it('bind player events', function() {
expect($(this.player)).toHandleWith('play', this.control.onPlay);
expect($(this.player)).toHandleWith('pause', this.control.onPause);
return expect($(this.player)).toHandleWith('ended', this.control.onPause);
});
return it('bind the playback button', function() {
return expect($('.video_control')).toHandleWith('click', this.control.togglePlayback);
});
});
describe('onPlay', function() {
beforeEach(function() {
this.control = new VideoControl(this.player);
return this.control.onPlay();
});
return it('switch playback button to play state', function() {
expect($('.video_control')).not.toHaveClass('play');
expect($('.video_control')).toHaveClass('pause');
return expect($('.video_control')).toHaveHtml('Pause');
});
});
describe('onPause', function() {
beforeEach(function() {
this.control = new VideoControl(this.player);
return this.control.onPause();
});
return it('switch playback button to pause state', function() {
expect($('.video_control')).not.toHaveClass('pause');
expect($('.video_control')).toHaveClass('play');
return expect($('.video_control')).toHaveHtml('Play');
});
});
return describe('togglePlayback', function() {
beforeEach(function() {
return this.control = new VideoControl(this.player);
});
describe('when the video is playing', function() {
beforeEach(function() {
spyOn(this.player, 'isPlaying').andReturn(true);
spyOnEvent(this.player, 'pause');
return this.control.togglePlayback(jQuery.Event('click'));
});
return it('trigger the pause event', function() {
return expect('pause').toHaveBeenTriggeredOn(this.player);
});
});
return describe('when the video is paused', function() {
beforeEach(function() {
spyOn(this.player, 'isPlaying').andReturn(false);
spyOnEvent(this.player, 'play');
return this.control.togglePlayback(jQuery.Event('click'));
});
return it('trigger the play event', function() {
return expect('play').toHaveBeenTriggeredOn(this.player);
});
});
});
});
}).call(this);
describe 'VideoProgressSlider', ->
beforeEach ->
@player = jasmine.stubVideoPlayer @
describe 'constructor', ->
beforeEach ->
spyOn($.fn, 'slider').andCallThrough()
@slider = new VideoProgressSlider @player
it 'build the slider', ->
expect(@slider.slider).toBe '.slider'
expect($.fn.slider).toHaveBeenCalledWith
range: 'min'
change: @slider.onChange
slide: @slider.onSlide
stop: @slider.onStop
it 'build the seek handle', ->
expect(@slider.handle).toBe '.ui-slider-handle'
expect($.fn.qtip).toHaveBeenCalledWith
content: "0:00"
position:
my: 'bottom center'
at: 'top center'
container: @slider.handle
hide:
delay: 700
style:
classes: 'ui-tooltip-slider'
widget: true
it 'bind player events', ->
expect($(@player)).toHandleWith 'updatePlayTime', @slider.onUpdatePlayTime
describe 'onReady', ->
beforeEach ->
spyOn(@player, 'duration').andReturn 120
@slider = new VideoProgressSlider @player
@slider.onReady()
it 'set the max value to the length of video', ->
expect(@slider.slider.slider('option', 'max')).toEqual 120
describe 'onUpdatePlayTime', ->
beforeEach ->
@slider = new VideoProgressSlider @player
spyOn(@player, 'duration').andReturn 120
spyOn($.fn, 'slider').andCallThrough()
describe 'when frozen', ->
beforeEach ->
@slider.frozen = true
@slider.onUpdatePlayTime {}, 20
it 'does not update the slider', ->
expect($.fn.slider).not.toHaveBeenCalled()
describe 'when not frozen', ->
beforeEach ->
@slider.frozen = false
@slider.onUpdatePlayTime {}, 20
it 'update the max value of the slider', ->
expect($.fn.slider).toHaveBeenCalledWith 'option', 'max', 120
it 'update current value of the slider', ->
expect($.fn.slider).toHaveBeenCalledWith 'value', 20
describe 'onSlide', ->
beforeEach ->
@slider = new VideoProgressSlider @player
@time = null
$(@player).bind 'seek', (event, time) => @time = time
spyOnEvent @player, 'seek'
@slider.onSlide {}, value: 20
it 'freeze the slider', ->
expect(@slider.frozen).toBeTruthy()
it 'update the tooltip', ->
expect($.fn.qtip).toHaveBeenCalled()
it 'trigger seek event', ->
expect('seek').toHaveBeenTriggeredOn @player
expect(@time).toEqual 20
describe 'onChange', ->
beforeEach ->
@slider = new VideoProgressSlider @player
@slider.onChange {}, value: 20
it 'update the tooltip', ->
expect($.fn.qtip).toHaveBeenCalled()
describe 'onStop', ->
beforeEach ->
@slider = new VideoProgressSlider @player
@time = null
$(@player).bind 'seek', (event, time) => @time = time
spyOnEvent @player, 'seek'
spyOn(window, 'setTimeout')
@slider.onStop {}, value: 20
it 'freeze the slider', ->
expect(@slider.frozen).toBeTruthy()
it 'trigger seek event', ->
expect('seek').toHaveBeenTriggeredOn @player
expect(@time).toEqual 20
it 'set timeout to unfreeze the slider', ->
expect(window.setTimeout).toHaveBeenCalledWith jasmine.any(Function), 200
window.setTimeout.mostRecentCall.args[0]()
expect(@slider.frozen).toBeFalsy()
describe 'updateTooltip', ->
beforeEach ->
@slider = new VideoProgressSlider @player
@slider.updateTooltip 90
it 'set the tooltip value', ->
expect($.fn.qtip).toHaveBeenCalledWith 'option', 'content.text', '1:30'
(function() {
describe('VideoProgressSlider', function() {
beforeEach(function() {
return this.player = jasmine.stubVideoPlayer(this);
});
describe('constructor', function() {
beforeEach(function() {
spyOn($.fn, 'slider').andCallThrough();
return this.slider = new VideoProgressSlider(this.player);
});
it('build the slider', function() {
expect(this.slider.slider).toBe('.slider');
return expect($.fn.slider).toHaveBeenCalledWith({
range: 'min',
change: this.slider.onChange,
slide: this.slider.onSlide,
stop: this.slider.onStop
});
});
it('build the seek handle', function() {
expect(this.slider.handle).toBe('.ui-slider-handle');
return expect($.fn.qtip).toHaveBeenCalledWith({
content: "0:00",
position: {
my: 'bottom center',
at: 'top center',
container: this.slider.handle
},
hide: {
delay: 700
},
style: {
classes: 'ui-tooltip-slider',
widget: true
}
});
});
return it('bind player events', function() {
return expect($(this.player)).toHandleWith('updatePlayTime', this.slider.onUpdatePlayTime);
});
});
describe('onReady', function() {
beforeEach(function() {
spyOn(this.player, 'duration').andReturn(120);
this.slider = new VideoProgressSlider(this.player);
return this.slider.onReady();
});
return it('set the max value to the length of video', function() {
return expect(this.slider.slider.slider('option', 'max')).toEqual(120);
});
});
describe('onUpdatePlayTime', function() {
beforeEach(function() {
this.slider = new VideoProgressSlider(this.player);
spyOn(this.player, 'duration').andReturn(120);
return spyOn($.fn, 'slider').andCallThrough();
});
describe('when frozen', function() {
beforeEach(function() {
this.slider.frozen = true;
return this.slider.onUpdatePlayTime({}, 20);
});
return it('does not update the slider', function() {
return expect($.fn.slider).not.toHaveBeenCalled();
});
});
return describe('when not frozen', function() {
beforeEach(function() {
this.slider.frozen = false;
return this.slider.onUpdatePlayTime({}, 20);
});
it('update the max value of the slider', function() {
return expect($.fn.slider).toHaveBeenCalledWith('option', 'max', 120);
});
return it('update current value of the slider', function() {
return expect($.fn.slider).toHaveBeenCalledWith('value', 20);
});
});
});
describe('onSlide', function() {
beforeEach(function() {
var _this = this;
this.slider = new VideoProgressSlider(this.player);
this.time = null;
$(this.player).bind('seek', function(event, time) {
return _this.time = time;
});
spyOnEvent(this.player, 'seek');
return this.slider.onSlide({}, {
value: 20
});
});
it('freeze the slider', function() {
return expect(this.slider.frozen).toBeTruthy();
});
it('update the tooltip', function() {
return expect($.fn.qtip).toHaveBeenCalled();
});
return it('trigger seek event', function() {
expect('seek').toHaveBeenTriggeredOn(this.player);
return expect(this.time).toEqual(20);
});
});
describe('onChange', function() {
beforeEach(function() {
this.slider = new VideoProgressSlider(this.player);
return this.slider.onChange({}, {
value: 20
});
});
return it('update the tooltip', function() {
return expect($.fn.qtip).toHaveBeenCalled();
});
});
describe('onStop', function() {
beforeEach(function() {
var _this = this;
this.slider = new VideoProgressSlider(this.player);
this.time = null;
$(this.player).bind('seek', function(event, time) {
return _this.time = time;
});
spyOnEvent(this.player, 'seek');
spyOn(window, 'setTimeout');
return this.slider.onStop({}, {
value: 20
});
});
it('freeze the slider', function() {
return expect(this.slider.frozen).toBeTruthy();
});
it('trigger seek event', function() {
expect('seek').toHaveBeenTriggeredOn(this.player);
return expect(this.time).toEqual(20);
});
return it('set timeout to unfreeze the slider', function() {
expect(window.setTimeout).toHaveBeenCalledWith(jasmine.any(Function), 200);
window.setTimeout.mostRecentCall.args[0]();
return expect(this.slider.frozen).toBeFalsy();
});
});
return describe('updateTooltip', function() {
beforeEach(function() {
this.slider = new VideoProgressSlider(this.player);
return this.slider.updateTooltip(90);
});
return it('set the tooltip value', function() {
return expect($.fn.qtip).toHaveBeenCalledWith('option', 'content.text', '1:30');
});
});
});
}).call(this);
describe 'VideoSpeedControl', ->
beforeEach ->
@player = jasmine.stubVideoPlayer @
$('.speeds').remove()
afterEach ->
describe 'constructor', ->
describe 'always', ->
beforeEach ->
@speedControl = new VideoSpeedControl @player, @video.speeds
it 'add the video speed control to player', ->
expect($('.secondary-controls').html()).toContain '''
<div class="speeds">
<a href="#">
<h3>Speed</h3>
<p class="active">1.0x</p>
</a>
<ol class="video_speeds"><li data-speed="1.0" class="active"><a href="#">1.0x</a></li><li data-speed="0.75"><a href="#">0.75x</a></li></ol>
</div>
'''
it 'bind to player speedChange event', ->
expect($(@player)).toHandleWith 'speedChange', @speedControl.onSpeedChange
it 'bind to change video speed link', ->
expect($('.video_speeds a')).toHandleWith 'click', @speedControl.changeVideoSpeed
describe 'when running on touch based device', ->
beforeEach ->
spyOn(window, 'onTouchBasedDevice').andReturn true
$('.speeds').removeClass 'open'
@speedControl = new VideoSpeedControl @player, @video.speeds
it 'open the speed toggle on click', ->
$('.speeds').click()
expect($('.speeds')).toHaveClass 'open'
$('.speeds').click()
expect($('.speeds')).not.toHaveClass 'open'
describe 'when running on non-touch based device', ->
beforeEach ->
spyOn(window, 'onTouchBasedDevice').andReturn false
$('.speeds').removeClass 'open'
@speedControl = new VideoSpeedControl @player, @video.speeds
it 'open the speed toggle on hover', ->
$('.speeds').mouseover()
expect($('.speeds')).toHaveClass 'open'
$('.speeds').mouseout()
expect($('.speeds')).not.toHaveClass 'open'
it 'close the speed toggle on mouse out', ->
$('.speeds').mouseover().mouseout()
expect($('.speeds')).not.toHaveClass 'open'
it 'close the speed toggle on click', ->
$('.speeds').mouseover().click()
expect($('.speeds')).not.toHaveClass 'open'
describe 'changeVideoSpeed', ->
beforeEach ->
@speedControl = new VideoSpeedControl @player, @video.speeds
@video.setSpeed '1.0'
describe 'when new speed is the same', ->
beforeEach ->
spyOnEvent @player, 'speedChange'
$('li[data-speed="1.0"] a').click()
it 'does not trigger speedChange event', ->
expect('speedChange').not.toHaveBeenTriggeredOn @player
describe 'when new speed is not the same', ->
beforeEach ->
@newSpeed = null
$(@player).bind 'speedChange', (event, newSpeed) => @newSpeed = newSpeed
spyOnEvent @player, 'speedChange'
$('li[data-speed="0.75"] a').click()
it 'trigger player speedChange event', ->
expect('speedChange').toHaveBeenTriggeredOn @player
expect(@newSpeed).toEqual 0.75
describe 'onSpeedChange', ->
beforeEach ->
@speedControl = new VideoSpeedControl @player, @video.speeds
$('li[data-speed="1.0"] a').addClass 'active'
@speedControl.setSpeed '0.75'
it 'set the new speed as active', ->
expect($('.video_speeds li[data-speed="1.0"]')).not.toHaveClass 'active'
expect($('.video_speeds li[data-speed="0.75"]')).toHaveClass 'active'
expect($('.speeds p.active')).toHaveHtml '0.75x'
(function() {
describe('VideoSpeedControl', function() {
beforeEach(function() {
this.player = jasmine.stubVideoPlayer(this);
return $('.speeds').remove();
});
afterEach(function() {});
describe('constructor', function() {
describe('always', function() {
beforeEach(function() {
return this.speedControl = new VideoSpeedControl(this.player, this.video.speeds);
});
it('add the video speed control to player', function() {
return expect($('.secondary-controls').html()).toContain('<div class="speeds">\n <a href="#">\n <h3>Speed</h3>\n <p class="active">1.0x</p>\n </a>\n <ol class="video_speeds"><li data-speed="1.0" class="active"><a href="#">1.0x</a></li><li data-speed="0.75"><a href="#">0.75x</a></li></ol>\n</div>');
});
it('bind to player speedChange event', function() {
return expect($(this.player)).toHandleWith('speedChange', this.speedControl.onSpeedChange);
});
return it('bind to change video speed link', function() {
return expect($('.video_speeds a')).toHandleWith('click', this.speedControl.changeVideoSpeed);
});
});
describe('when running on touch based device', function() {
beforeEach(function() {
spyOn(window, 'onTouchBasedDevice').andReturn(true);
$('.speeds').removeClass('open');
return this.speedControl = new VideoSpeedControl(this.player, this.video.speeds);
});
return it('open the speed toggle on click', function() {
$('.speeds').click();
expect($('.speeds')).toHaveClass('open');
$('.speeds').click();
return expect($('.speeds')).not.toHaveClass('open');
});
});
return describe('when running on non-touch based device', function() {
beforeEach(function() {
spyOn(window, 'onTouchBasedDevice').andReturn(false);
$('.speeds').removeClass('open');
return this.speedControl = new VideoSpeedControl(this.player, this.video.speeds);
});
it('open the speed toggle on hover', function() {
$('.speeds').mouseover();
expect($('.speeds')).toHaveClass('open');
$('.speeds').mouseout();
return expect($('.speeds')).not.toHaveClass('open');
});
it('close the speed toggle on mouse out', function() {
$('.speeds').mouseover().mouseout();
return expect($('.speeds')).not.toHaveClass('open');
});
return it('close the speed toggle on click', function() {
$('.speeds').mouseover().click();
return expect($('.speeds')).not.toHaveClass('open');
});
});
});
describe('changeVideoSpeed', function() {
beforeEach(function() {
this.speedControl = new VideoSpeedControl(this.player, this.video.speeds);
return this.video.setSpeed('1.0');
});
describe('when new speed is the same', function() {
beforeEach(function() {
spyOnEvent(this.player, 'speedChange');
return $('li[data-speed="1.0"] a').click();
});
return it('does not trigger speedChange event', function() {
return expect('speedChange').not.toHaveBeenTriggeredOn(this.player);
});
});
return describe('when new speed is not the same', function() {
beforeEach(function() {
var _this = this;
this.newSpeed = null;
$(this.player).bind('speedChange', function(event, newSpeed) {
return _this.newSpeed = newSpeed;
});
spyOnEvent(this.player, 'speedChange');
return $('li[data-speed="0.75"] a').click();
});
return it('trigger player speedChange event', function() {
expect('speedChange').toHaveBeenTriggeredOn(this.player);
return expect(this.newSpeed).toEqual(0.75);
});
});
});
return describe('onSpeedChange', function() {
beforeEach(function() {
this.speedControl = new VideoSpeedControl(this.player, this.video.speeds);
$('li[data-speed="1.0"] a').addClass('active');
return this.speedControl.setSpeed('0.75');
});
return it('set the new speed as active', function() {
expect($('.video_speeds li[data-speed="1.0"]')).not.toHaveClass('active');
expect($('.video_speeds li[data-speed="0.75"]')).toHaveClass('active');
return expect($('.speeds p.active')).toHaveHtml('0.75x');
});
});
});
}).call(this);
describe 'Video', ->
beforeEach ->
loadFixtures 'video.html'
jasmine.stubRequests()
afterEach ->
window.player = undefined
window.onYouTubePlayerAPIReady = undefined
describe 'constructor', ->
beforeEach ->
$.cookie.andReturn '0.75'
window.player = 100
describe 'by default', ->
beforeEach ->
@video = new Video 'example', '.75:abc123,1.0:def456'
it 'reset the current video player', ->
expect(window.player).toBeNull()
it 'set the elements', ->
expect(@video.element).toBe '#video_example'
it 'parse the videos', ->
expect(@video.videos).toEqual
'0.75': 'abc123'
'1.0': 'def456'
it 'fetch the video metadata', ->
expect(@video.metadata).toEqual
abc123:
id: 'abc123'
duration: 100
def456:
id: 'def456'
duration: 200
it 'parse available video speeds', ->
expect(@video.speeds).toEqual ['0.75', '1.0']
it 'set current video speed via cookie', ->
expect(@video.speed).toEqual '0.75'
it 'store a reference for this video player in the element', ->
expect($('.video').data('video')).toEqual @video
describe 'when the Youtube API is already available', ->
beforeEach ->
@originalYT = window.YT
window.YT = { Player: true }
@stubVideoPlayer = jasmine.createSpy('VideoPlayer')
spyOn(window, 'VideoPlayer').andReturn(@stubVideoPlayer)
@video = new Video 'example', '.75:abc123,1.0:def456'
afterEach ->
window.YT = @originalYT
it 'create the Video Player', ->
expect(window.VideoPlayer).toHaveBeenCalledWith @video
expect(@video.player).toEqual @stubVideoPlayer
describe 'when the Youtube API is not ready', ->
beforeEach ->
@video = new Video 'example', '.75:abc123,1.0:def456'
it 'set the callback on the window object', ->
expect(window.onYouTubePlayerAPIReady).toEqual jasmine.any(Function)
describe 'when the Youtube API becoming ready', ->
beforeEach ->
@stubVideoPlayer = jasmine.createSpy('VideoPlayer')
spyOn(window, 'VideoPlayer').andReturn(@stubVideoPlayer)
@video = new Video 'example', '.75:abc123,1.0:def456'
window.onYouTubePlayerAPIReady()
it 'create the Video Player for all video elements', ->
expect(window.VideoPlayer).toHaveBeenCalledWith @video
expect(@video.player).toEqual @stubVideoPlayer
describe 'youtubeId', ->
beforeEach ->
$.cookie.andReturn '1.0'
@video = new Video 'example', '.75:abc123,1.0:def456'
describe 'with speed', ->
it 'return the video id for given speed', ->
expect(@video.youtubeId('0.75')).toEqual 'abc123'
expect(@video.youtubeId('1.0')).toEqual 'def456'
describe 'without speed', ->
it 'return the video id for current speed', ->
expect(@video.youtubeId()).toEqual 'def456'
describe 'setSpeed', ->
beforeEach ->
@video = new Video 'example', '.75:abc123,1.0:def456'
describe 'when new speed is available', ->
beforeEach ->
@video.setSpeed '0.75'
it 'set new speed', ->
expect(@video.speed).toEqual '0.75'
it 'save setting for new speed', ->
expect($.cookie).toHaveBeenCalledWith 'video_speed', '0.75', expires: 3650, path: '/'
describe 'when new speed is not available', ->
beforeEach ->
@video.setSpeed '1.75'
it 'set speed to 1.0x', ->
expect(@video.speed).toEqual '1.0'
describe 'getDuration', ->
beforeEach ->
@video = new Video 'example', '.75:abc123,1.0:def456'
it 'return duration for current video', ->
expect(@video.getDuration()).toEqual 200
(function() {
describe('Video', function() {
beforeEach(function() {
loadFixtures('video.html');
return jasmine.stubRequests();
});
afterEach(function() {
window.player = void 0;
return window.onYouTubePlayerAPIReady = void 0;
});
describe('constructor', function() {
beforeEach(function() {
$.cookie.andReturn('0.75');
return window.player = 100;
});
describe('by default', function() {
beforeEach(function() {
return this.video = new Video('example', '.75:abc123,1.0:def456');
});
it('reset the current video player', function() {
return expect(window.player).toBeNull();
});
it('set the elements', function() {
return expect(this.video.element).toBe('#video_example');
});
it('parse the videos', function() {
return expect(this.video.videos).toEqual({
'0.75': 'abc123',
'1.0': 'def456'
});
});
it('fetch the video metadata', function() {
return expect(this.video.metadata).toEqual({
abc123: {
id: 'abc123',
duration: 100
},
def456: {
id: 'def456',
duration: 200
}
});
});
it('parse available video speeds', function() {
return expect(this.video.speeds).toEqual(['0.75', '1.0']);
});
it('set current video speed via cookie', function() {
return expect(this.video.speed).toEqual('0.75');
});
return it('store a reference for this video player in the element', function() {
return expect($('.video').data('video')).toEqual(this.video);
});
});
describe('when the Youtube API is already available', function() {
beforeEach(function() {
this.originalYT = window.YT;
window.YT = {
Player: true
};
this.stubVideoPlayer = jasmine.createSpy('VideoPlayer');
spyOn(window, 'VideoPlayer').andReturn(this.stubVideoPlayer);
return this.video = new Video('example', '.75:abc123,1.0:def456');
});
afterEach(function() {
return window.YT = this.originalYT;
});
return it('create the Video Player', function() {
expect(window.VideoPlayer).toHaveBeenCalledWith(this.video);
return expect(this.video.player).toEqual(this.stubVideoPlayer);
});
});
describe('when the Youtube API is not ready', function() {
beforeEach(function() {
return this.video = new Video('example', '.75:abc123,1.0:def456');
});
return it('set the callback on the window object', function() {
return expect(window.onYouTubePlayerAPIReady).toEqual(jasmine.any(Function));
});
});
return describe('when the Youtube API becoming ready', function() {
beforeEach(function() {
this.stubVideoPlayer = jasmine.createSpy('VideoPlayer');
spyOn(window, 'VideoPlayer').andReturn(this.stubVideoPlayer);
this.video = new Video('example', '.75:abc123,1.0:def456');
return window.onYouTubePlayerAPIReady();
});
return it('create the Video Player for all video elements', function() {
expect(window.VideoPlayer).toHaveBeenCalledWith(this.video);
return expect(this.video.player).toEqual(this.stubVideoPlayer);
});
});
});
describe('youtubeId', function() {
beforeEach(function() {
$.cookie.andReturn('1.0');
return this.video = new Video('example', '.75:abc123,1.0:def456');
});
describe('with speed', function() {
return it('return the video id for given speed', function() {
expect(this.video.youtubeId('0.75')).toEqual('abc123');
return expect(this.video.youtubeId('1.0')).toEqual('def456');
});
});
return describe('without speed', function() {
return it('return the video id for current speed', function() {
return expect(this.video.youtubeId()).toEqual('def456');
});
});
});
describe('setSpeed', function() {
beforeEach(function() {
return this.video = new Video('example', '.75:abc123,1.0:def456');
});
describe('when new speed is available', function() {
beforeEach(function() {
return this.video.setSpeed('0.75');
});
it('set new speed', function() {
return expect(this.video.speed).toEqual('0.75');
});
return it('save setting for new speed', function() {
return expect($.cookie).toHaveBeenCalledWith('video_speed', '0.75', {
expires: 3650,
path: '/'
});
});
});
return describe('when new speed is not available', function() {
beforeEach(function() {
return this.video.setSpeed('1.75');
});
return it('set speed to 1.0x', function() {
return expect(this.video.speed).toEqual('1.0');
});
});
});
return describe('getDuration', function() {
beforeEach(function() {
return this.video = new Video('example', '.75:abc123,1.0:def456');
});
return it('return duration for current video', function() {
return expect(this.video.getDuration()).toEqual(200);
});
});
});
}).call(this);
describe 'Time', ->
describe 'format', ->
describe 'with duration more than or equal to 1 hour', ->
it 'return a correct time format', ->
expect(Time.format(3600)).toEqual '1:00:00'
expect(Time.format(7272)).toEqual '2:01:12'
describe 'with duration less than 1 hour', ->
it 'return a correct time format', ->
expect(Time.format(1)).toEqual '0:01'
expect(Time.format(61)).toEqual '1:01'
expect(Time.format(3599)).toEqual '59:59'
describe 'convert', ->
it 'return a correct time based on speed modifier', ->
expect(Time.convert(0, 1, 1.5)).toEqual '0.000'
expect(Time.convert(100, 1, 1.5)).toEqual '66.667'
expect(Time.convert(100, 1.5, 1)).toEqual '150.000'
(function() {
describe('Time', function() {
describe('format', function() {
describe('with duration more than or equal to 1 hour', function() {
return it('return a correct time format', function() {
expect(Time.format(3600)).toEqual('1:00:00');
return expect(Time.format(7272)).toEqual('2:01:12');
});
});
return describe('with duration less than 1 hour', function() {
return it('return a correct time format', function() {
expect(Time.format(1)).toEqual('0:01');
expect(Time.format(61)).toEqual('1:01');
return expect(Time.format(3599)).toEqual('59:59');
});
});
});
return describe('convert', function() {
return it('return a correct time based on speed modifier', function() {
expect(Time.convert(0, 1, 1.5)).toEqual('0.000');
expect(Time.convert(100, 1, 1.5)).toEqual('66.667');
return expect(Time.convert(100, 1.5, 1)).toEqual('150.000');
});
});
});
}).call(this);
......@@ -25,11 +25,11 @@ class @Histogram
],
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'))
......
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()
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')
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()
......
class VideoProgressSlider
class @VideoProgressSlider
constructor: (@player) ->
@buildSlider()
@buildHandle()
......
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')
......
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)}"
else
"#{minutes}:#{@pad(seconds % 60)}"
@pad: (number) ->
if number < 10
"0#{number}"
"#{hours}:#{pad(minutes)}:#{pad(seconds % 60)}"
else
number
"#{minutes}:#{pad(seconds % 60)}"
@convert: (time, oldSpeed, newSpeed) ->
(time * oldSpeed / newSpeed).toFixed(3)
<!doctype html>
<html>
<head>
<title>Jasmine Spec Runner</title>
<link rel="stylesheet" href="{{ STATIC_URL }}jasmine-latest/jasmine.css"
media="screen">
{# core files #}
<script src="{{ STATIC_URL }}jasmine-latest/jasmine.js"></script>
<script src="{{ STATIC_URL }}jasmine-latest/jasmine-html.js"></script>
<script src="https://raw.github.com/sikachu/jasmine-jquery/master/lib/jasmine-jquery.js"></script>
{# source files #}
{% for url in suite.js_files %}
<script src="{{ url }}"></script>
{% endfor %}
{# static files #}
{% for url in suite.static_files %}
<script src="{{ STATIC_URL }}{{ url }}"></script>
{% endfor %}
{# spec files #}
{% for file in files %}
<script src="{% url jasmine_test file %}"></script>
{% endfor %}
</head>
<body>
<h1>Jasmine Spec Runner</h1>
<script>
{% block jasmine %}
(function() {
var jasmineEnv = jasmine.getEnv();
jasmineEnv.updateInterval = 1000;
var trivialReporter = new jasmine.TrivialReporter();
jasmineEnv.addReporter(trivialReporter);
jasmineEnv.specFilter = function(spec) {
return trivialReporter.specFilter(spec);
};
// Additional configuration can be done in this block
{% block jasmine_extra %}{% endblock %}
var currentWindowOnload = window.onload;
window.onload = function() {
if (currentWindowOnload) {
currentWindowOnload();
}
execJasmine();
};
function execJasmine() {
jasmineEnv.execute();
}
})();
{% endblock %}
</script>
</body>
</html>
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment