Merge pull request #15846 from edx/ormsbee/decaffeinate_tests_2
WIP: Decaffeinate Test Files (Part 2)
This commit is contained in:
@@ -1,4 +0,0 @@
|
||||
test_problem_display.js
|
||||
test_problem_generator.js
|
||||
test_problem_grader.js
|
||||
xproblem.js
|
||||
@@ -1,21 +0,0 @@
|
||||
class MinimaxProblemDisplay extends XProblemDisplay
|
||||
|
||||
constructor: (@state, @submission, @evaluation, @container, @submissionField, @parameters={}) ->
|
||||
|
||||
super(@state, @submission, @evaluation, @container, @submissionField, @parameters)
|
||||
|
||||
render: () ->
|
||||
|
||||
createSubmission: () ->
|
||||
|
||||
@newSubmission = {}
|
||||
|
||||
if @submission?
|
||||
for id, value of @submission
|
||||
@newSubmission[id] = value
|
||||
|
||||
getCurrentSubmission: () ->
|
||||
return @newSubmission
|
||||
|
||||
root = exports ? this
|
||||
root.TestProblemDisplay = TestProblemDisplay
|
||||
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* decaffeinate suggestions:
|
||||
* DS001: Remove Babel/TypeScript constructor workaround
|
||||
* DS102: Remove unnecessary code created because of implicit returns
|
||||
* DS205: Consider reworking code to avoid use of IIFEs
|
||||
* DS207: Consider shorter variations of null checks
|
||||
* DS208: Avoid top-level this
|
||||
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
|
||||
*/
|
||||
class MinimaxProblemDisplay extends XProblemDisplay {
|
||||
|
||||
constructor(state, submission, evaluation, container, submissionField, parameters) {
|
||||
|
||||
{
|
||||
// Hack: trick Babel/TypeScript into allowing this before super.
|
||||
if (false) { super(); }
|
||||
let thisFn = (() => { this; }).toString();
|
||||
let thisName = thisFn.slice(thisFn.indexOf('{') + 1, thisFn.indexOf(';')).trim();
|
||||
eval(`${thisName} = this;`);
|
||||
}
|
||||
this.state = state;
|
||||
this.submission = submission;
|
||||
this.evaluation = evaluation;
|
||||
this.container = container;
|
||||
this.submissionField = submissionField;
|
||||
if (parameters == null) { parameters = {}; }
|
||||
this.parameters = parameters;
|
||||
super(this.state, this.submission, this.evaluation, this.container, this.submissionField, this.parameters);
|
||||
}
|
||||
|
||||
render() {}
|
||||
|
||||
createSubmission() {
|
||||
|
||||
this.newSubmission = {};
|
||||
|
||||
if (this.submission != null) {
|
||||
return (() => {
|
||||
const result = [];
|
||||
for (let id in this.submission) {
|
||||
const value = this.submission[id];
|
||||
result.push(this.newSubmission[id] = value);
|
||||
}
|
||||
return result;
|
||||
})();
|
||||
}
|
||||
}
|
||||
|
||||
getCurrentSubmission() {
|
||||
return this.newSubmission;
|
||||
}
|
||||
}
|
||||
|
||||
const root = typeof exports !== 'undefined' && exports !== null ? exports : this;
|
||||
root.TestProblemDisplay = TestProblemDisplay;
|
||||
@@ -1,14 +0,0 @@
|
||||
class TestProblemGenerator extends XProblemGenerator
|
||||
|
||||
constructor: (seed, @parameters = {}) ->
|
||||
|
||||
super(seed, @parameters)
|
||||
|
||||
generate: () ->
|
||||
|
||||
@problemState.value = @parameters.value
|
||||
|
||||
return @problemState
|
||||
|
||||
root = exports ? this
|
||||
root.generatorClass = TestProblemGenerator
|
||||
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* decaffeinate suggestions:
|
||||
* DS001: Remove Babel/TypeScript constructor workaround
|
||||
* DS207: Consider shorter variations of null checks
|
||||
* DS208: Avoid top-level this
|
||||
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
|
||||
*/
|
||||
class TestProblemGenerator extends XProblemGenerator {
|
||||
|
||||
constructor(seed, parameters) {
|
||||
|
||||
{
|
||||
// Hack: trick Babel/TypeScript into allowing this before super.
|
||||
if (false) { super(); }
|
||||
let thisFn = (() => { this; }).toString();
|
||||
let thisName = thisFn.slice(thisFn.indexOf('{') + 1, thisFn.indexOf(';')).trim();
|
||||
eval(`${thisName} = this;`);
|
||||
}
|
||||
if (parameters == null) { parameters = {}; }
|
||||
this.parameters = parameters;
|
||||
super(seed, this.parameters);
|
||||
}
|
||||
|
||||
generate() {
|
||||
|
||||
this.problemState.value = this.parameters.value;
|
||||
|
||||
return this.problemState;
|
||||
}
|
||||
}
|
||||
|
||||
const root = typeof exports !== 'undefined' && exports !== null ? exports : this;
|
||||
root.generatorClass = TestProblemGenerator;
|
||||
@@ -1,27 +0,0 @@
|
||||
class TestProblemGrader extends XProblemGrader
|
||||
|
||||
constructor: (@submission, @problemState, @parameters={}) ->
|
||||
|
||||
super(@submission, @problemState, @parameters)
|
||||
|
||||
solve: () ->
|
||||
|
||||
@solution = {0: @problemState.value}
|
||||
|
||||
grade: () ->
|
||||
|
||||
if not @solution?
|
||||
@solve()
|
||||
|
||||
allCorrect = true
|
||||
|
||||
for id, value of @solution
|
||||
valueCorrect = if @submission? then (value == @submission[id]) else false
|
||||
@evaluation[id] = valueCorrect
|
||||
if not valueCorrect
|
||||
allCorrect = false
|
||||
|
||||
return allCorrect
|
||||
|
||||
root = exports ? this
|
||||
root.graderClass = TestProblemGrader
|
||||
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* decaffeinate suggestions:
|
||||
* DS001: Remove Babel/TypeScript constructor workaround
|
||||
* DS102: Remove unnecessary code created because of implicit returns
|
||||
* DS207: Consider shorter variations of null checks
|
||||
* DS208: Avoid top-level this
|
||||
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
|
||||
*/
|
||||
class TestProblemGrader extends XProblemGrader {
|
||||
|
||||
constructor(submission, problemState, parameters) {
|
||||
|
||||
{
|
||||
// Hack: trick Babel/TypeScript into allowing this before super.
|
||||
if (false) { super(); }
|
||||
let thisFn = (() => { this; }).toString();
|
||||
let thisName = thisFn.slice(thisFn.indexOf('{') + 1, thisFn.indexOf(';')).trim();
|
||||
eval(`${thisName} = this;`);
|
||||
}
|
||||
this.submission = submission;
|
||||
this.problemState = problemState;
|
||||
if (parameters == null) { parameters = {}; }
|
||||
this.parameters = parameters;
|
||||
super(this.submission, this.problemState, this.parameters);
|
||||
}
|
||||
|
||||
solve() {
|
||||
|
||||
return this.solution = {0: this.problemState.value};
|
||||
}
|
||||
|
||||
grade() {
|
||||
|
||||
if ((this.solution == null)) {
|
||||
this.solve();
|
||||
}
|
||||
|
||||
let allCorrect = true;
|
||||
|
||||
for (let id in this.solution) {
|
||||
const value = this.solution[id];
|
||||
const valueCorrect = (this.submission != null) ? (value === this.submission[id]) : false;
|
||||
this.evaluation[id] = valueCorrect;
|
||||
if (!valueCorrect) {
|
||||
allCorrect = false;
|
||||
}
|
||||
}
|
||||
|
||||
return allCorrect;
|
||||
}
|
||||
}
|
||||
|
||||
const root = typeof exports !== 'undefined' && exports !== null ? exports : this;
|
||||
root.graderClass = TestProblemGrader;
|
||||
@@ -1,47 +0,0 @@
|
||||
class XProblemGenerator
|
||||
|
||||
constructor: (seed, @parameters={}) ->
|
||||
|
||||
@random = new MersenneTwister(seed)
|
||||
|
||||
@problemState = {}
|
||||
|
||||
generate: () ->
|
||||
|
||||
console.error("Abstract method called: XProblemGenerator.generate")
|
||||
|
||||
class XProblemDisplay
|
||||
|
||||
constructor: (@state, @submission, @evaluation, @container, @submissionField, @parameters={}) ->
|
||||
|
||||
render: () ->
|
||||
|
||||
console.error("Abstract method called: XProblemDisplay.render")
|
||||
|
||||
updateSubmission: () ->
|
||||
|
||||
@submissionField.val(JSON.stringify(@getCurrentSubmission()))
|
||||
|
||||
getCurrentSubmission: () ->
|
||||
console.error("Abstract method called: XProblemDisplay.getCurrentSubmission")
|
||||
|
||||
class XProblemGrader
|
||||
|
||||
constructor: (@submission, @problemState, @parameters={}) ->
|
||||
|
||||
@solution = null
|
||||
@evaluation = {}
|
||||
|
||||
solve: () ->
|
||||
|
||||
console.error("Abstract method called: XProblemGrader.solve")
|
||||
|
||||
grade: () ->
|
||||
|
||||
console.error("Abstract method called: XProblemGrader.grade")
|
||||
|
||||
root = exports ? this
|
||||
|
||||
root.XProblemGenerator = XProblemGenerator
|
||||
root.XProblemDisplay = XProblemDisplay
|
||||
root.XProblemGrader = XProblemGrader
|
||||
78
common/lib/capa/capa/tests/test_files/js/xproblem.js
Normal file
78
common/lib/capa/capa/tests/test_files/js/xproblem.js
Normal file
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
* decaffeinate suggestions:
|
||||
* DS207: Consider shorter variations of null checks
|
||||
* DS208: Avoid top-level this
|
||||
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
|
||||
*/
|
||||
class XProblemGenerator {
|
||||
|
||||
constructor(seed, parameters) {
|
||||
|
||||
if (parameters == null) { parameters = {}; }
|
||||
this.parameters = parameters;
|
||||
this.random = new MersenneTwister(seed);
|
||||
|
||||
this.problemState = {};
|
||||
}
|
||||
|
||||
generate() {
|
||||
|
||||
console.error("Abstract method called: XProblemGenerator.generate");
|
||||
}
|
||||
}
|
||||
|
||||
class XProblemDisplay {
|
||||
|
||||
constructor(state, submission, evaluation, container, submissionField, parameters) {
|
||||
this.state = state;
|
||||
this.submission = submission;
|
||||
this.evaluation = evaluation;
|
||||
this.container = container;
|
||||
this.submissionField = submissionField;
|
||||
if (parameters == null) { parameters = {}; }
|
||||
this.parameters = parameters;
|
||||
}
|
||||
|
||||
render() {
|
||||
|
||||
console.error("Abstract method called: XProblemDisplay.render");
|
||||
}
|
||||
|
||||
updateSubmission() {
|
||||
|
||||
this.submissionField.val(JSON.stringify(this.getCurrentSubmission()));
|
||||
}
|
||||
|
||||
getCurrentSubmission() {
|
||||
console.error("Abstract method called: XProblemDisplay.getCurrentSubmission");
|
||||
}
|
||||
}
|
||||
|
||||
class XProblemGrader {
|
||||
|
||||
constructor(submission, problemState, parameters) {
|
||||
|
||||
this.submission = submission;
|
||||
this.problemState = problemState;
|
||||
if (parameters == null) { parameters = {}; }
|
||||
this.parameters = parameters;
|
||||
this.solution = null;
|
||||
this.evaluation = {};
|
||||
}
|
||||
|
||||
solve() {
|
||||
|
||||
console.error("Abstract method called: XProblemGrader.solve");
|
||||
}
|
||||
|
||||
grade() {
|
||||
|
||||
console.error("Abstract method called: XProblemGrader.grade");
|
||||
}
|
||||
}
|
||||
|
||||
const root = typeof exports !== 'undefined' && exports !== null ? exports : this;
|
||||
|
||||
root.XProblemGenerator = XProblemGenerator;
|
||||
root.XProblemDisplay = XProblemDisplay;
|
||||
root.XProblemGrader = XProblemGrader;
|
||||
10
common/lib/xmodule/xmodule/js/spec/.gitignore
vendored
10
common/lib/xmodule/xmodule/js/spec/.gitignore
vendored
@@ -1,10 +0,0 @@
|
||||
*.js
|
||||
|
||||
# Tests for video are written in pure JavaScript.
|
||||
!video/*.js
|
||||
|
||||
# Tests for Time are written in pure JavaScript.
|
||||
!time_spec.js
|
||||
!collapsible_spec.js
|
||||
!xmodule_spec.js
|
||||
!sequence/display_spec.js
|
||||
@@ -1,9 +0,0 @@
|
||||
describe 'Annotatable', ->
|
||||
beforeEach ->
|
||||
loadFixtures 'annotatable.html'
|
||||
describe 'constructor', ->
|
||||
el = $('.xblock-student_view.xmodule_AnnotatableModule')
|
||||
beforeEach ->
|
||||
@annotatable = new Annotatable(el)
|
||||
it 'works', ->
|
||||
expect(1).toBe(1)
|
||||
@@ -0,0 +1,10 @@
|
||||
describe('Annotatable', function() {
|
||||
beforeEach(() => loadFixtures('annotatable.html'));
|
||||
describe('constructor', function() {
|
||||
const el = $('.xblock-student_view.xmodule_AnnotatableModule');
|
||||
beforeEach(function() {
|
||||
this.annotatable = new Annotatable(el);
|
||||
});
|
||||
it('works', () => expect(1).toBe(1));
|
||||
});
|
||||
});
|
||||
@@ -1,846 +0,0 @@
|
||||
describe 'Problem', ->
|
||||
problem_content_default = readFixtures('problem_content.html')
|
||||
|
||||
beforeEach ->
|
||||
# Stub MathJax
|
||||
window.MathJax =
|
||||
Hub: jasmine.createSpyObj('MathJax.Hub', ['getAllJax', 'Queue'])
|
||||
Callback: jasmine.createSpyObj('MathJax.Callback', ['After'])
|
||||
@stubbedJax = root: jasmine.createSpyObj('jax.root', ['toMathML'])
|
||||
MathJax.Hub.getAllJax.and.returnValue [@stubbedJax]
|
||||
window.update_schematics = ->
|
||||
spyOn SR, 'readText'
|
||||
spyOn SR, 'readTexts'
|
||||
|
||||
# Load this function from spec/helper.coffee
|
||||
# Note that if your test fails with a message like:
|
||||
# 'External request attempted for blah, which is not defined.'
|
||||
# this msg is coming from the stubRequests function else clause.
|
||||
jasmine.stubRequests()
|
||||
|
||||
loadFixtures 'problem.html'
|
||||
|
||||
spyOn Logger, 'log'
|
||||
spyOn($.fn, 'load').and.callFake (url, callback) ->
|
||||
$(@).html readFixtures('problem_content.html')
|
||||
callback()
|
||||
|
||||
describe 'constructor', ->
|
||||
|
||||
it 'set the element from html', ->
|
||||
@problem999 = new Problem ("
|
||||
<section class='xblock xblock-student_view xmodule_display xmodule_CapaModule' data-type='Problem'>
|
||||
<section id='problem_999'
|
||||
class='problems-wrapper'
|
||||
data-problem-id='i4x://edX/999/problem/Quiz'
|
||||
data-url='/problem/quiz/'>
|
||||
</section>
|
||||
</section>
|
||||
")
|
||||
expect(@problem999.element_id).toBe 'problem_999'
|
||||
|
||||
it 'set the element from loadFixtures', ->
|
||||
@problem1 = new Problem($('.xblock-student_view'))
|
||||
expect(@problem1.element_id).toBe 'problem_1'
|
||||
|
||||
describe 'bind', ->
|
||||
beforeEach ->
|
||||
spyOn window, 'update_schematics'
|
||||
MathJax.Hub.getAllJax.and.returnValue [@stubbedJax]
|
||||
@problem = new Problem($('.xblock-student_view'))
|
||||
|
||||
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($('div.action button')).toHandleWith 'click', @problem.refreshAnswers
|
||||
|
||||
it 'bind the submit button', ->
|
||||
expect($('.action .submit')).toHandleWith 'click', @problem.submit_fd
|
||||
|
||||
it 'bind the reset button', ->
|
||||
expect($('div.action button.reset')).toHandleWith 'click', @problem.reset
|
||||
|
||||
it 'bind the show button', ->
|
||||
expect($('.action .show')).toHandleWith 'click', @problem.show
|
||||
|
||||
it 'bind the save button', ->
|
||||
expect($('div.action button.save')).toHandleWith 'click', @problem.save
|
||||
|
||||
it 'bind the math input', ->
|
||||
expect($('input.math')).toHandleWith 'keyup', @problem.refreshMath
|
||||
|
||||
describe 'bind_with_custom_input_id', ->
|
||||
beforeEach ->
|
||||
spyOn window, 'update_schematics'
|
||||
MathJax.Hub.getAllJax.and.returnValue [@stubbedJax]
|
||||
@problem = new Problem($('.xblock-student_view'))
|
||||
$(@).html readFixtures('problem_content_1240.html')
|
||||
|
||||
it 'bind the submit button', ->
|
||||
expect($('.action .submit')).toHandleWith 'click', @problem.submit_fd
|
||||
|
||||
it 'bind the show button', ->
|
||||
expect($('div.action button.show')).toHandleWith 'click', @problem.show
|
||||
|
||||
|
||||
describe 'renderProgressState', ->
|
||||
beforeEach ->
|
||||
@problem = new Problem($('.xblock-student_view'))
|
||||
|
||||
testProgessData = (problem, score, total_possible, attempts, graded, expected_progress_after_render) ->
|
||||
problem.el.data('problem-score', score);
|
||||
problem.el.data('problem-total-possible', total_possible);
|
||||
problem.el.data('attempts-used', attempts);
|
||||
problem.el.data('graded', graded)
|
||||
expect(problem.$('.problem-progress').html()).toEqual ""
|
||||
problem.renderProgressState()
|
||||
expect(problem.$('.problem-progress').html()).toEqual expected_progress_after_render
|
||||
|
||||
describe 'with a status of "none"', ->
|
||||
it 'reports the number of points possible and graded', ->
|
||||
testProgessData(@problem, 0, 1, 0, "True", "1 point possible (graded)")
|
||||
|
||||
it 'displays the number of points possible when rendering happens with the content', ->
|
||||
testProgessData(@problem, 0, 2, 0, "True", "2 points possible (graded)")
|
||||
|
||||
it 'reports the number of points possible and ungraded', ->
|
||||
testProgessData(@problem, 0, 1, 0, "False", "1 point possible (ungraded)")
|
||||
|
||||
it 'displays ungraded if number of points possible is 0', ->
|
||||
testProgessData(@problem, 0, 0, 0, "False", "0 points possible (ungraded)")
|
||||
|
||||
it 'displays ungraded if number of points possible is 0, even if graded value is True', ->
|
||||
testProgessData(@problem, 0, 0, 0, "True", "0 points possible (ungraded)")
|
||||
|
||||
it 'reports the correct score with status none and >0 attempts', ->
|
||||
testProgessData(@problem, 0, 1, 1, "True", "0/1 point (graded)")
|
||||
|
||||
it 'reports the correct score with >1 weight, status none, and >0 attempts', ->
|
||||
testProgessData(@problem, 0, 2, 2, "True", "0/2 points (graded)")
|
||||
|
||||
describe 'with any other valid status', ->
|
||||
|
||||
it 'reports the current score', ->
|
||||
testProgessData(@problem, 1, 1, 1, "True", "1/1 point (graded)")
|
||||
|
||||
it 'shows current score when rendering happens with the content', ->
|
||||
testProgessData(@problem, 2, 2, 1, "True", "2/2 points (graded)")
|
||||
|
||||
it 'reports the current score even if problem is ungraded', ->
|
||||
testProgessData(@problem, 1, 1, 1, "False", "1/1 point (ungraded)")
|
||||
|
||||
describe 'with valid status and string containing an integer like "0" for detail', ->
|
||||
# These tests are to address a failure specific to Chrome 51 and 52 +
|
||||
it 'shows 0 points possible for the detail', ->
|
||||
testProgessData(@problem, 0, 0, 1, "False", "0 points possible (ungraded)")
|
||||
|
||||
describe 'with a score of null (show_correctness == false)', ->
|
||||
it 'reports the number of points possible and graded, results hidden', ->
|
||||
testProgessData(@problem, null, 1, 0, "True", "1 point possible (graded, results hidden)")
|
||||
|
||||
it 'reports the number of points possible (plural) and graded, results hidden', ->
|
||||
testProgessData(@problem, null, 2, 0, "True", "2 points possible (graded, results hidden)")
|
||||
|
||||
it 'reports the number of points possible and ungraded, results hidden', ->
|
||||
testProgessData(@problem, null, 1, 0, "False", "1 point possible (ungraded, results hidden)")
|
||||
|
||||
it 'displays ungraded if number of points possible is 0, results hidden', ->
|
||||
testProgessData(@problem, null, 0, 0, "False", "0 points possible (ungraded, results hidden)")
|
||||
|
||||
it 'displays ungraded if number of points possible is 0, even if graded value is True, results hidden', ->
|
||||
testProgessData(@problem, null, 0, 0, "True", "0 points possible (ungraded, results hidden)")
|
||||
|
||||
it 'reports the correct score with status none and >0 attempts, results hidden', ->
|
||||
testProgessData(@problem, null, 1, 1, "True", "1 point possible (graded, results hidden)")
|
||||
|
||||
it 'reports the correct score with >1 weight, status none, and >0 attempts, results hidden', ->
|
||||
testProgessData(@problem, null, 2, 2, "True", "2 points possible (graded, results hidden)")
|
||||
|
||||
describe 'render', ->
|
||||
beforeEach ->
|
||||
@problem = new Problem($('.xblock-student_view'))
|
||||
@bind = @problem.bind
|
||||
spyOn @problem, 'bind'
|
||||
|
||||
describe 'with content given', ->
|
||||
beforeEach ->
|
||||
@problem.render 'Hello World'
|
||||
|
||||
it 'render the content', ->
|
||||
expect(@problem.el.html()).toEqual 'Hello World'
|
||||
|
||||
it 're-bind the content', ->
|
||||
expect(@problem.bind).toHaveBeenCalled()
|
||||
|
||||
describe 'with no content given', ->
|
||||
beforeEach ->
|
||||
spyOn($, 'postWithPrefix').and.callFake (url, callback) ->
|
||||
callback html: "Hello World"
|
||||
@problem.render()
|
||||
|
||||
it 'load the content via ajax', ->
|
||||
expect(@problem.el.html()).toEqual 'Hello World'
|
||||
|
||||
it 're-bind the content', ->
|
||||
expect(@problem.bind).toHaveBeenCalled()
|
||||
|
||||
describe 'submit_fd', ->
|
||||
beforeEach ->
|
||||
# Insert an input of type file outside of the problem.
|
||||
$('.xblock-student_view').after('<input type="file" />')
|
||||
@problem = new Problem($('.xblock-student_view'))
|
||||
spyOn(@problem, 'submit')
|
||||
|
||||
it 'submit method is called if input of type file is not in problem', ->
|
||||
@problem.submit_fd()
|
||||
expect(@problem.submit).toHaveBeenCalled()
|
||||
|
||||
describe 'submit', ->
|
||||
beforeEach ->
|
||||
@problem = new Problem($('.xblock-student_view'))
|
||||
@problem.answers = 'foo=1&bar=2'
|
||||
|
||||
it 'log the problem_check event', ->
|
||||
spyOn($, 'postWithPrefix').and.callFake (url, answers, callback) ->
|
||||
promise =
|
||||
always: (callable) -> callable()
|
||||
done: (callable) -> callable()
|
||||
@problem.submit()
|
||||
expect(Logger.log).toHaveBeenCalledWith 'problem_check', 'foo=1&bar=2'
|
||||
|
||||
it 'log the problem_graded event, after the problem is done grading.', ->
|
||||
spyOn($, 'postWithPrefix').and.callFake (url, answers, callback) ->
|
||||
response =
|
||||
success: 'correct'
|
||||
contents: 'mock grader response'
|
||||
callback(response)
|
||||
promise =
|
||||
always: (callable) -> callable()
|
||||
done: (callable) -> callable()
|
||||
@problem.submit()
|
||||
expect(Logger.log).toHaveBeenCalledWith 'problem_graded', ['foo=1&bar=2', 'mock grader response'], @problem.id
|
||||
|
||||
it 'submit the answer for submit', ->
|
||||
spyOn($, 'postWithPrefix').and.callFake (url, answers, callback) ->
|
||||
promise =
|
||||
always: (callable) -> callable()
|
||||
done: (callable) -> callable()
|
||||
@problem.submit()
|
||||
expect($.postWithPrefix).toHaveBeenCalledWith '/problem/Problem1/problem_check',
|
||||
'foo=1&bar=2', jasmine.any(Function)
|
||||
|
||||
describe 'when the response is correct', ->
|
||||
it 'call render with returned content', ->
|
||||
contents = '<div class="wrapper-problem-response" aria-label="Question 1"><p>Correct<span class="status">excellent</span></p></div>' +
|
||||
'<div class="wrapper-problem-response" aria-label="Question 2"><p>Yep<span class="status">correct</span></p></div>'
|
||||
spyOn($, 'postWithPrefix').and.callFake (url, answers, callback) ->
|
||||
callback(success: 'correct', contents: contents)
|
||||
promise =
|
||||
always: (callable) -> callable()
|
||||
done: (callable) -> callable()
|
||||
@problem.submit()
|
||||
expect(@problem.el).toHaveHtml contents
|
||||
expect(window.SR.readTexts).toHaveBeenCalledWith ['Question 1: excellent', 'Question 2: correct']
|
||||
|
||||
describe 'when the response is incorrect', ->
|
||||
it 'call render with returned content', ->
|
||||
contents = '<p>Incorrect<span class="status">no, try again</span></p>'
|
||||
spyOn($, 'postWithPrefix').and.callFake (url, answers, callback) ->
|
||||
callback(success: 'incorrect', contents: contents)
|
||||
promise =
|
||||
always: (callable) -> callable()
|
||||
done: (callable) -> callable()
|
||||
@problem.submit()
|
||||
expect(@problem.el).toHaveHtml contents
|
||||
expect(window.SR.readTexts).toHaveBeenCalledWith ['no, try again']
|
||||
|
||||
it 'tests if the submit button is disabled while submitting and the text changes on the button', ->
|
||||
self = this
|
||||
curr_html = @problem.el.html()
|
||||
spyOn($, 'postWithPrefix').and.callFake (url, answers, callback) ->
|
||||
# At this point enableButtons should have been called, making the submit button disabled with text 'submitting'
|
||||
expect(self.problem.submitButton).toHaveAttr('disabled');
|
||||
expect(self.problem.submitButtonLabel.text()).toBe('Submitting');
|
||||
callback
|
||||
success: 'incorrect' # does not matter if correct or incorrect here
|
||||
contents: curr_html
|
||||
promise =
|
||||
always: (callable) -> callable()
|
||||
done: (callable) -> callable()
|
||||
# Make sure the submit button is enabled before submitting
|
||||
$('#input_example_1').val('test').trigger('input')
|
||||
expect(@problem.submitButton).not.toHaveAttr('disabled')
|
||||
@problem.submit()
|
||||
# After submit, the button should not be disabled and should have text as 'Submit'
|
||||
expect(@problem.submitButtonLabel.text()).toBe('Submit')
|
||||
expect(@problem.submitButton).not.toHaveAttr('disabled')
|
||||
|
||||
describe 'submit button on problems', ->
|
||||
|
||||
beforeEach ->
|
||||
@problem = new Problem($('.xblock-student_view'))
|
||||
@submitDisabled = (disabled) =>
|
||||
if disabled
|
||||
expect(@problem.submitButton).toHaveAttr('disabled')
|
||||
else
|
||||
expect(@problem.submitButton).not.toHaveAttr('disabled')
|
||||
|
||||
describe 'some basic tests for submit button', ->
|
||||
it 'should become enabled after a value is entered into the text box', ->
|
||||
$('#input_example_1').val('test').trigger('input')
|
||||
@submitDisabled false
|
||||
$('#input_example_1').val('').trigger('input')
|
||||
@submitDisabled true
|
||||
|
||||
describe 'some advanced tests for submit button', ->
|
||||
radioButtonProblemHtml = readFixtures('radiobutton_problem.html')
|
||||
checkboxProblemHtml = readFixtures('checkbox_problem.html')
|
||||
|
||||
it 'should become enabled after a checkbox is checked', ->
|
||||
$('#input_example_1').replaceWith(checkboxProblemHtml)
|
||||
@problem.submitAnswersAndSubmitButton true
|
||||
@submitDisabled true
|
||||
$('#input_1_1_1').click()
|
||||
@submitDisabled false
|
||||
$('#input_1_1_1').click()
|
||||
@submitDisabled true
|
||||
|
||||
it 'should become enabled after a radiobutton is checked', ->
|
||||
$('#input_example_1').replaceWith(radioButtonProblemHtml)
|
||||
@problem.submitAnswersAndSubmitButton true
|
||||
@submitDisabled true
|
||||
$('#input_1_1_1').attr('checked', true).trigger('click')
|
||||
@submitDisabled false
|
||||
$('#input_1_1_1').attr('checked', false).trigger('click')
|
||||
@submitDisabled true
|
||||
|
||||
it 'should become enabled after a value is selected in a selector', ->
|
||||
html = '''
|
||||
<div id="problem_sel">
|
||||
<select>
|
||||
<option value="val0">Select an option</option>
|
||||
<option value="val1">1</option>
|
||||
<option value="val2">2</option>
|
||||
</select>
|
||||
</div>
|
||||
'''
|
||||
$('#input_example_1').replaceWith(html)
|
||||
@problem.submitAnswersAndSubmitButton true
|
||||
@submitDisabled true
|
||||
$("#problem_sel select").val("val2").trigger('change')
|
||||
@submitDisabled false
|
||||
$("#problem_sel select").val("val0").trigger('change')
|
||||
@submitDisabled true
|
||||
|
||||
it 'should become enabled after a radiobutton is checked and a value is entered into the text box', ->
|
||||
$(radioButtonProblemHtml).insertAfter('#input_example_1')
|
||||
@problem.submitAnswersAndSubmitButton true
|
||||
@submitDisabled true
|
||||
$('#input_1_1_1').attr('checked', true).trigger('click')
|
||||
@submitDisabled true
|
||||
$('#input_example_1').val('111').trigger('input')
|
||||
@submitDisabled false
|
||||
$('#input_1_1_1').attr('checked', false).trigger('click')
|
||||
@submitDisabled true
|
||||
|
||||
it 'should become enabled if there are only hidden input fields', ->
|
||||
html = '''
|
||||
<input type="text" name="test" id="test" aria-describedby="answer_test" value="" style="display:none;">
|
||||
'''
|
||||
$('#input_example_1').replaceWith(html)
|
||||
@problem.submitAnswersAndSubmitButton true
|
||||
@submitDisabled false
|
||||
|
||||
describe 'reset', ->
|
||||
beforeEach ->
|
||||
@problem = new Problem($('.xblock-student_view'))
|
||||
|
||||
it 'log the problem_reset event', ->
|
||||
spyOn($, 'postWithPrefix').and.callFake (url, answers, callback) ->
|
||||
promise =
|
||||
always: (callable) -> callable()
|
||||
@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').and.callFake (url, answers, callback) ->
|
||||
promise =
|
||||
always: (callable) -> callable()
|
||||
@problem.reset()
|
||||
expect($.postWithPrefix).toHaveBeenCalledWith '/problem/Problem1/problem_reset',
|
||||
{ id: 'i4x://edX/101/problem/Problem1' }, jasmine.any(Function)
|
||||
|
||||
it 'render the returned content', ->
|
||||
spyOn($, 'postWithPrefix').and.callFake (url, answers, callback) ->
|
||||
callback html: "Reset", success: true
|
||||
promise =
|
||||
always: (callable) -> callable()
|
||||
@problem.reset()
|
||||
expect(@problem.el.html()).toEqual 'Reset'
|
||||
|
||||
it 'sends a message to the window SR element', ->
|
||||
spyOn($, 'postWithPrefix').and.callFake (url, answers, callback) ->
|
||||
callback html: "Reset", success: true
|
||||
promise =
|
||||
always: (callable) -> callable()
|
||||
@problem.reset()
|
||||
expect(window.SR.readText).toHaveBeenCalledWith 'This problem has been reset.'
|
||||
|
||||
it 'shows a notification on error', ->
|
||||
spyOn($, 'postWithPrefix').and.callFake (url, answers, callback) ->
|
||||
callback msg: "Error on reset.", success: false
|
||||
promise =
|
||||
always: (callable) -> callable()
|
||||
@problem.reset()
|
||||
expect($('.notification-gentle-alert .notification-message').text()).toEqual("Error on reset.")
|
||||
|
||||
it 'tests that reset does not enable submit or modify the text while resetting', ->
|
||||
self = this
|
||||
curr_html = @problem.el.html()
|
||||
spyOn($, 'postWithPrefix').and.callFake (url, answers, callback) ->
|
||||
# enableButtons should have been called at this point to set them to all disabled
|
||||
expect(self.problem.submitButton).toHaveAttr('disabled')
|
||||
expect(self.problem.submitButtonLabel.text()).toBe('Submit')
|
||||
callback(success: 'correct', html: curr_html)
|
||||
promise =
|
||||
always: (callable) -> callable()
|
||||
# Submit should be disabled
|
||||
expect(@problem.submitButton).toHaveAttr('disabled')
|
||||
@problem.reset()
|
||||
# Submit should remain disabled
|
||||
expect(self.problem.submitButton).toHaveAttr('disabled')
|
||||
expect(self.problem.submitButtonLabel.text()).toBe('Submit')
|
||||
|
||||
describe 'show', ->
|
||||
beforeEach ->
|
||||
@problem = new Problem($('.xblock-student_view'))
|
||||
@problem.el.prepend '<div id="answer_1_1" /><div id="answer_1_2" />'
|
||||
|
||||
describe 'when the answer has not yet shown', ->
|
||||
beforeEach ->
|
||||
expect(@problem.el.find('.show').attr('disabled')).not.toEqual('disabled')
|
||||
|
||||
it 'log the problem_show event', ->
|
||||
@problem.show()
|
||||
expect(Logger.log).toHaveBeenCalledWith 'problem_show',
|
||||
problem: 'i4x://edX/101/problem/Problem1'
|
||||
|
||||
it 'fetch the answers', ->
|
||||
spyOn $, 'postWithPrefix'
|
||||
@problem.show()
|
||||
expect($.postWithPrefix).toHaveBeenCalledWith '/problem/Problem1/problem_show',
|
||||
jasmine.any(Function)
|
||||
|
||||
it 'show the answers', ->
|
||||
spyOn($, 'postWithPrefix').and.callFake (url, callback) ->
|
||||
callback answers: '1_1': 'One', '1_2': 'Two'
|
||||
@problem.show()
|
||||
expect($('#answer_1_1')).toHaveHtml 'One'
|
||||
expect($('#answer_1_2')).toHaveHtml 'Two'
|
||||
|
||||
it 'disables the show answer button', ->
|
||||
spyOn($, 'postWithPrefix').and.callFake (url, callback) -> callback(answers: {})
|
||||
@problem.show()
|
||||
expect(@problem.el.find('.show').attr('disabled')).toEqual('disabled')
|
||||
|
||||
describe 'radio text question', ->
|
||||
radio_text_xml='''
|
||||
<section class="problem">
|
||||
<div><p></p><span><section id="choicetextinput_1_2_1" class="choicetextinput">
|
||||
|
||||
<form class="choicetextgroup capa_inputtype" id="inputtype_1_2_1">
|
||||
<div class="indicator-container">
|
||||
<span class="unanswered" style="display:inline-block;" id="status_1_2_1"></span>
|
||||
</div>
|
||||
<fieldset>
|
||||
<section id="forinput1_2_1_choiceinput_0bc">
|
||||
<input class="ctinput" type="radio" name="choiceinput_1_2_1" id="1_2_1_choiceinput_0bc" value="choiceinput_0"">
|
||||
<input class="ctinput" type="text" name="choiceinput_0_textinput_0" id="1_2_1_choiceinput_0_textinput_0" value=" ">
|
||||
<p id="answer_1_2_1_choiceinput_0bc" class="answer"></p>
|
||||
</>
|
||||
<section id="forinput1_2_1_choiceinput_1bc">
|
||||
<input class="ctinput" type="radio" name="choiceinput_1_2_1" id="1_2_1_choiceinput_1bc" value="choiceinput_1" >
|
||||
<input class="ctinput" type="text" name="choiceinput_1_textinput_0" id="1_2_1_choiceinput_1_textinput_0" value=" " >
|
||||
<p id="answer_1_2_1_choiceinput_1bc" class="answer"></p>
|
||||
</section>
|
||||
<section id="forinput1_2_1_choiceinput_2bc">
|
||||
<input class="ctinput" type="radio" name="choiceinput_1_2_1" id="1_2_1_choiceinput_2bc" value="choiceinput_2" >
|
||||
<input class="ctinput" type="text" name="choiceinput_2_textinput_0" id="1_2_1_choiceinput_2_textinput_0" value=" " >
|
||||
<p id="answer_1_2_1_choiceinput_2bc" class="answer"></p>
|
||||
</section></fieldset><input class="choicetextvalue" type="hidden" name="input_1_2_1" id="input_1_2_1"></form>
|
||||
</section></span></div>
|
||||
</section>
|
||||
'''
|
||||
beforeEach ->
|
||||
# Append a radiotextresponse problem to the problem, so we can check it's javascript functionality
|
||||
@problem.el.prepend(radio_text_xml)
|
||||
|
||||
it 'sets the correct class on the section for the correct choice', ->
|
||||
spyOn($, 'postWithPrefix').and.callFake (url, callback) ->
|
||||
callback answers: "1_2_1": ["1_2_1_choiceinput_0bc"], "1_2_1_choiceinput_0bc": "3"
|
||||
@problem.show()
|
||||
|
||||
expect($('#forinput1_2_1_choiceinput_0bc').attr('class')).toEqual(
|
||||
'choicetextgroup_show_correct')
|
||||
expect($('#answer_1_2_1_choiceinput_0bc').text()).toEqual('3')
|
||||
expect($('#answer_1_2_1_choiceinput_1bc').text()).toEqual('')
|
||||
expect($('#answer_1_2_1_choiceinput_2bc').text()).toEqual('')
|
||||
|
||||
it 'Should not disable input fields', ->
|
||||
spyOn($, 'postWithPrefix').and.callFake (url, callback) ->
|
||||
callback answers: "1_2_1": ["1_2_1_choiceinput_0bc"], "1_2_1_choiceinput_0bc": "3"
|
||||
@problem.show()
|
||||
expect($('input#1_2_1_choiceinput_0bc').attr('disabled')).not.toEqual('disabled')
|
||||
expect($('input#1_2_1_choiceinput_1bc').attr('disabled')).not.toEqual('disabled')
|
||||
expect($('input#1_2_1_choiceinput_2bc').attr('disabled')).not.toEqual('disabled')
|
||||
expect($('input#1_2_1').attr('disabled')).not.toEqual('disabled')
|
||||
|
||||
describe 'imageinput', ->
|
||||
imageinput_html = readFixtures('imageinput.underscore')
|
||||
|
||||
DEFAULTS =
|
||||
id: '12345'
|
||||
width: '300'
|
||||
height: '400'
|
||||
|
||||
beforeEach ->
|
||||
@problem = new Problem($('.xblock-student_view'))
|
||||
@problem.el.prepend _.template(imageinput_html)(DEFAULTS)
|
||||
|
||||
assertAnswer = (problem, data) =>
|
||||
stubRequest(data)
|
||||
problem.show()
|
||||
|
||||
$.each data['answers'], (id, answer) =>
|
||||
img = getImage(answer)
|
||||
el = $('#inputtype_' + id)
|
||||
expect(img).toImageDiffEqual(el.find('canvas')[0])
|
||||
|
||||
stubRequest = (data) =>
|
||||
spyOn($, 'postWithPrefix').and.callFake (url, callback) ->
|
||||
callback data
|
||||
|
||||
getImage = (coords, c_width, c_height) =>
|
||||
types =
|
||||
rectangle: (coords) =>
|
||||
reg = /^\(([0-9]+),([0-9]+)\)-\(([0-9]+),([0-9]+)\)$/
|
||||
rects = coords.replace(/\s*/g, '').split(/;/)
|
||||
|
||||
$.each rects, (index, rect) =>
|
||||
abs = Math.abs
|
||||
points = reg.exec(rect)
|
||||
if points
|
||||
width = abs(points[3] - points[1])
|
||||
height = abs(points[4] - points[2])
|
||||
|
||||
ctx.rect(points[1], points[2], width, height)
|
||||
|
||||
ctx.stroke()
|
||||
ctx.fill()
|
||||
|
||||
regions: (coords) =>
|
||||
parseCoords = (coords) =>
|
||||
reg = JSON.parse(coords)
|
||||
|
||||
if typeof reg[0][0][0] == "undefined"
|
||||
reg = [reg]
|
||||
|
||||
return reg
|
||||
|
||||
$.each parseCoords(coords), (index, region) =>
|
||||
ctx.beginPath()
|
||||
$.each region, (index, point) =>
|
||||
if index is 0
|
||||
ctx.moveTo(point[0], point[1])
|
||||
else
|
||||
ctx.lineTo(point[0], point[1]);
|
||||
|
||||
ctx.closePath()
|
||||
ctx.stroke()
|
||||
ctx.fill()
|
||||
|
||||
canvas = document.createElement('canvas')
|
||||
canvas.width = c_width or 100
|
||||
canvas.height = c_height or 100
|
||||
|
||||
if canvas.getContext
|
||||
ctx = canvas.getContext('2d')
|
||||
else
|
||||
return console.log 'Canvas is not supported.'
|
||||
|
||||
ctx.fillStyle = 'rgba(255,255,255,.3)';
|
||||
ctx.strokeStyle = "#FF0000";
|
||||
ctx.lineWidth = "2";
|
||||
|
||||
$.each coords, (key, value) =>
|
||||
types[key](value) if types[key]? and value
|
||||
|
||||
return canvas
|
||||
|
||||
it 'rectangle is drawn correctly', ->
|
||||
assertAnswer(@problem, {
|
||||
'answers':
|
||||
'12345':
|
||||
'rectangle': '(10,10)-(30,30)',
|
||||
'regions': null
|
||||
})
|
||||
|
||||
it 'region is drawn correctly', ->
|
||||
assertAnswer(@problem, {
|
||||
'answers':
|
||||
'12345':
|
||||
'rectangle': null,
|
||||
'regions': '[[10,10],[30,30],[70,30],[20,30]]'
|
||||
})
|
||||
|
||||
it 'mixed shapes are drawn correctly', ->
|
||||
assertAnswer(@problem, {
|
||||
'answers':'12345':
|
||||
'rectangle': '(10,10)-(30,30);(5,5)-(20,20)',
|
||||
'regions': '''[
|
||||
[[50,50],[40,40],[70,30],[50,70]],
|
||||
[[90,95],[95,95],[90,70],[70,70]]
|
||||
]'''
|
||||
})
|
||||
|
||||
it 'multiple image inputs draw answers on separate canvases', ->
|
||||
data =
|
||||
id: '67890'
|
||||
width: '400'
|
||||
height: '300'
|
||||
|
||||
@problem.el.prepend _.template(imageinput_html)(data)
|
||||
assertAnswer(@problem, {
|
||||
'answers':
|
||||
'12345':
|
||||
'rectangle': null,
|
||||
'regions': '[[10,10],[30,30],[70,30],[20,30]]'
|
||||
'67890':
|
||||
'rectangle': '(10,10)-(30,30)',
|
||||
'regions': null
|
||||
})
|
||||
|
||||
it 'dictionary with answers doesn\'t contain answer for current id', ->
|
||||
spyOn console, 'log'
|
||||
stubRequest({'answers':{}})
|
||||
@problem.show()
|
||||
el = $('#inputtype_12345')
|
||||
expect(el.find('canvas')).not.toExist()
|
||||
expect(console.log).toHaveBeenCalledWith('Answer is absent for image input with id=12345')
|
||||
|
||||
describe 'save', ->
|
||||
beforeEach ->
|
||||
@problem = new Problem($('.xblock-student_view'))
|
||||
@problem.answers = 'foo=1&bar=2'
|
||||
|
||||
it 'log the problem_save event', ->
|
||||
spyOn($, 'postWithPrefix').and.callFake (url, answers, callback) ->
|
||||
promise =
|
||||
always: (callable) -> callable()
|
||||
@problem.save()
|
||||
expect(Logger.log).toHaveBeenCalledWith 'problem_save', 'foo=1&bar=2'
|
||||
|
||||
it 'POST to save problem', ->
|
||||
spyOn($, 'postWithPrefix').and.callFake (url, answers, callback) ->
|
||||
promise =
|
||||
always: (callable) -> callable()
|
||||
@problem.save()
|
||||
expect($.postWithPrefix).toHaveBeenCalledWith '/problem/Problem1/problem_save',
|
||||
'foo=1&bar=2', jasmine.any(Function)
|
||||
|
||||
it 'tests that save does not enable the submit button or change the text when submit is originally disabled', ->
|
||||
self = this
|
||||
curr_html = @problem.el.html()
|
||||
spyOn($, 'postWithPrefix').and.callFake (url, answers, callback) ->
|
||||
# enableButtons should have been called at this point and the submit button should be unaffected
|
||||
expect(self.problem.submitButton).toHaveAttr('disabled')
|
||||
expect(self.problem.submitButtonLabel.text()).toBe('Submit')
|
||||
callback(success: 'correct', html: curr_html)
|
||||
promise =
|
||||
always: (callable) -> callable()
|
||||
# Expect submit to be disabled and labeled properly at the start
|
||||
expect(@problem.submitButton).toHaveAttr('disabled')
|
||||
expect(@problem.submitButtonLabel.text()).toBe('Submit')
|
||||
@problem.save()
|
||||
# Submit button should have the same state after save has completed
|
||||
expect(@problem.submitButton).toHaveAttr('disabled')
|
||||
expect(@problem.submitButtonLabel.text()).toBe('Submit')
|
||||
|
||||
it 'tests that save does not disable the submit button or change the text when submit is originally enabled', ->
|
||||
self = this
|
||||
curr_html = @problem.el.html()
|
||||
spyOn($, 'postWithPrefix').and.callFake (url, answers, callback) ->
|
||||
# enableButtons should have been called at this point, and the submit button should be disabled while submitting
|
||||
expect(self.problem.submitButton).toHaveAttr('disabled')
|
||||
expect(self.problem.submitButtonLabel.text()).toBe('Submit')
|
||||
callback(success: 'correct', html: curr_html)
|
||||
promise =
|
||||
always: (callable) -> callable()
|
||||
# Expect submit to be enabled and labeled properly at the start after adding an input
|
||||
$('#input_example_1').val('test').trigger('input')
|
||||
expect(@problem.submitButton).not.toHaveAttr('disabled')
|
||||
expect(@problem.submitButtonLabel.text()).toBe('Submit')
|
||||
@problem.save()
|
||||
# Submit button should have the same state after save has completed
|
||||
expect(@problem.submitButton).not.toHaveAttr('disabled')
|
||||
expect(@problem.submitButtonLabel.text()).toBe('Submit')
|
||||
|
||||
describe 'refreshMath', ->
|
||||
beforeEach ->
|
||||
@problem = new Problem($('.xblock-student_view'))
|
||||
$('#input_example_1').val 'E=mc^2'
|
||||
@problem.refreshMath target: $('#input_example_1').get(0)
|
||||
|
||||
it 'should queue the conversion and MathML element update', ->
|
||||
expect(MathJax.Hub.Queue).toHaveBeenCalledWith ['Text', @stubbedJax, 'E=mc^2'],
|
||||
[@problem.updateMathML, @stubbedJax, $('#input_example_1').get(0)]
|
||||
|
||||
describe 'updateMathML', ->
|
||||
beforeEach ->
|
||||
@problem = new Problem($('.xblock-student_view'))
|
||||
@stubbedJax.root.toMathML.and.returnValue '<MathML>'
|
||||
|
||||
describe 'when there is no exception', ->
|
||||
beforeEach ->
|
||||
@problem.updateMathML @stubbedJax, $('#input_example_1').get(0)
|
||||
|
||||
it 'convert jax to MathML', ->
|
||||
expect($('#input_example_1_dynamath')).toHaveValue '<MathML>'
|
||||
|
||||
describe 'when there is an exception', ->
|
||||
beforeEach ->
|
||||
error = new Error()
|
||||
error.restart = true
|
||||
@stubbedJax.root.toMathML.and.throwError error
|
||||
@problem.updateMathML @stubbedJax, $('#input_example_1').get(0)
|
||||
|
||||
it 'should queue up the exception', ->
|
||||
expect(MathJax.Callback.After).toHaveBeenCalledWith [@problem.refreshMath, @stubbedJax], true
|
||||
|
||||
describe 'refreshAnswers', ->
|
||||
beforeEach ->
|
||||
@problem = new Problem($('.xblock-student_view'))
|
||||
@problem.el.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()
|
||||
|
||||
describe 'multiple JsInput in single problem', ->
|
||||
jsinput_html = readFixtures('jsinput_problem.html')
|
||||
|
||||
beforeEach ->
|
||||
@problem = new Problem($('.xblock-student_view'))
|
||||
@problem.render(jsinput_html)
|
||||
|
||||
it 'submit_save_waitfor should return false', ->
|
||||
$(@problem.inputs[0]).data('waitfor', ->)
|
||||
expect(@problem.submit_save_waitfor()).toEqual(false)
|
||||
|
||||
describe 'Submitting an xqueue-graded problem', ->
|
||||
matlabinput_html = readFixtures('matlabinput_problem.html')
|
||||
|
||||
beforeEach ->
|
||||
spyOn($, 'postWithPrefix').and.callFake (url, callback) ->
|
||||
callback html: matlabinput_html
|
||||
jasmine.clock().install()
|
||||
@problem = new Problem($('.xblock-student_view'))
|
||||
spyOn(@problem, 'poll').and.callThrough()
|
||||
@problem.render(matlabinput_html)
|
||||
|
||||
afterEach ->
|
||||
jasmine.clock().uninstall()
|
||||
|
||||
it 'check that we stop polling after a fixed amount of time', ->
|
||||
expect(@problem.poll).not.toHaveBeenCalled()
|
||||
jasmine.clock().tick(1)
|
||||
time_steps = [1000, 2000, 4000, 8000, 16000, 32000]
|
||||
num_calls = 1
|
||||
for time_step in time_steps
|
||||
do (time_step) =>
|
||||
jasmine.clock().tick(time_step)
|
||||
expect(@problem.poll.calls.count()).toEqual(num_calls)
|
||||
num_calls += 1
|
||||
|
||||
# jump the next step and verify that we are not still continuing to poll
|
||||
jasmine.clock().tick(64000)
|
||||
expect(@problem.poll.calls.count()).toEqual(6)
|
||||
|
||||
expect($('.notification-gentle-alert .notification-message').text()).toEqual("The grading process is still running. Refresh the page to see updates.")
|
||||
|
||||
describe 'codeinput problem', ->
|
||||
codeinputProblemHtml = readFixtures('codeinput_problem.html')
|
||||
|
||||
beforeEach ->
|
||||
spyOn($, 'postWithPrefix').and.callFake (url, callback) ->
|
||||
callback html: codeinputProblemHtml
|
||||
@problem = new Problem($('.xblock-student_view'))
|
||||
@problem.render(codeinputProblemHtml)
|
||||
|
||||
it 'has rendered with correct a11y info', ->
|
||||
CodeMirrorTextArea = $('textarea')[1]
|
||||
CodeMirrorTextAreaId = 'cm-textarea-101'
|
||||
|
||||
# verify that question label has correct `for` attribute value
|
||||
expect($('.problem-group-label').attr('for')).toEqual(CodeMirrorTextAreaId)
|
||||
|
||||
# verify that codemirror textarea has correct `id` attribute value
|
||||
expect($(CodeMirrorTextArea).attr('id')).toEqual(CodeMirrorTextAreaId)
|
||||
|
||||
# verify that codemirror textarea has correct `aria-describedby` attribute value
|
||||
expect($(CodeMirrorTextArea).attr('aria-describedby')).toEqual('cm-editor-exit-message-101 status_101')
|
||||
|
||||
|
||||
describe 'show answer button', ->
|
||||
|
||||
radioButtonProblemHtml = readFixtures('radiobutton_problem.html')
|
||||
checkboxProblemHtml = readFixtures('checkbox_problem.html')
|
||||
|
||||
beforeEach ->
|
||||
@problem = new Problem($('.xblock-student_view'))
|
||||
|
||||
@checkAssertionsAfterClickingAnotherOption = =>
|
||||
# verify that 'show answer button is no longer disabled'
|
||||
expect(@problem.el.find('.show').attr('disabled')).not.toEqual('disabled')
|
||||
|
||||
# verify that displayed answer disappears
|
||||
expect(@problem.el.find('div.choicegroup')).not.toHaveClass('choicegroup_correct')
|
||||
|
||||
# verify that radio/checkbox label has no span having class '.status.correct'
|
||||
expect(@problem.el.find('div.choicegroup')).not.toHaveAttr('span.status.correct')
|
||||
|
||||
it 'should become enabled after a radiobutton is selected', ->
|
||||
$('#input_example_1').replaceWith(radioButtonProblemHtml)
|
||||
# assume that 'ShowAnswer' button is clicked,
|
||||
# clicking make it disabled.
|
||||
@problem.el.find('.show').attr('disabled', 'disabled')
|
||||
# bind click event to input fields
|
||||
@problem.submitAnswersAndSubmitButton true
|
||||
# selects option 2
|
||||
$('#input_1_1_2').attr('checked', true).trigger('click')
|
||||
@checkAssertionsAfterClickingAnotherOption()
|
||||
|
||||
it 'should become enabled after a checkbox is selected', ->
|
||||
$('#input_example_1').replaceWith(checkboxProblemHtml)
|
||||
@problem.el.find('.show').attr('disabled', 'disabled')
|
||||
@problem.submitAnswersAndSubmitButton true
|
||||
$('#input_1_1_2').click()
|
||||
@checkAssertionsAfterClickingAnotherOption()
|
||||
1070
common/lib/xmodule/xmodule/js/spec/capa/display_spec.js
Normal file
1070
common/lib/xmodule/xmodule/js/spec/capa/display_spec.js
Normal file
@@ -0,0 +1,1070 @@
|
||||
/*
|
||||
* decaffeinate suggestions:
|
||||
* DS101: Remove unnecessary use of Array.from
|
||||
* DS207: Consider shorter variations of null checks
|
||||
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
|
||||
*/
|
||||
describe('Problem', function() {
|
||||
const problem_content_default = readFixtures('problem_content.html');
|
||||
|
||||
beforeEach(function() {
|
||||
// Stub MathJax
|
||||
window.MathJax = {
|
||||
Hub: jasmine.createSpyObj('MathJax.Hub', ['getAllJax', 'Queue']),
|
||||
Callback: jasmine.createSpyObj('MathJax.Callback', ['After'])
|
||||
};
|
||||
this.stubbedJax = {root: jasmine.createSpyObj('jax.root', ['toMathML'])};
|
||||
MathJax.Hub.getAllJax.and.returnValue([this.stubbedJax]);
|
||||
window.update_schematics = function() {};
|
||||
spyOn(SR, 'readText');
|
||||
spyOn(SR, 'readTexts');
|
||||
|
||||
// Load this function from spec/helper.coffee
|
||||
// Note that if your test fails with a message like:
|
||||
// 'External request attempted for blah, which is not defined.'
|
||||
// this msg is coming from the stubRequests function else clause.
|
||||
jasmine.stubRequests();
|
||||
|
||||
loadFixtures('problem.html');
|
||||
|
||||
spyOn(Logger, 'log');
|
||||
spyOn($.fn, 'load').and.callFake(function(url, callback) {
|
||||
$(this).html(readFixtures('problem_content.html'));
|
||||
return callback();
|
||||
});
|
||||
});
|
||||
|
||||
describe('constructor', function() {
|
||||
|
||||
it('set the element from html', function() {
|
||||
this.problem999 = new Problem((`\
|
||||
<section class='xblock xblock-student_view xmodule_display xmodule_CapaModule' data-type='Problem'> \
|
||||
<section id='problem_999' \
|
||||
class='problems-wrapper' \
|
||||
data-problem-id='i4x://edX/999/problem/Quiz' \
|
||||
data-url='/problem/quiz/'> \
|
||||
</section> \
|
||||
</section>\
|
||||
`)
|
||||
);
|
||||
expect(this.problem999.element_id).toBe('problem_999');
|
||||
});
|
||||
|
||||
it('set the element from loadFixtures', function() {
|
||||
this.problem1 = new Problem($('.xblock-student_view'));
|
||||
expect(this.problem1.element_id).toBe('problem_1');
|
||||
});
|
||||
});
|
||||
|
||||
describe('bind', function() {
|
||||
beforeEach(function() {
|
||||
spyOn(window, 'update_schematics');
|
||||
MathJax.Hub.getAllJax.and.returnValue([this.stubbedJax]);
|
||||
this.problem = new Problem($('.xblock-student_view'));
|
||||
});
|
||||
|
||||
it('set mathjax typeset', () => expect(MathJax.Hub.Queue).toHaveBeenCalled());
|
||||
|
||||
it('update schematics', () => expect(window.update_schematics).toHaveBeenCalled());
|
||||
|
||||
it('bind answer refresh on button click', function() {
|
||||
expect($('div.action button')).toHandleWith('click', this.problem.refreshAnswers);
|
||||
});
|
||||
|
||||
it('bind the submit button', function() {
|
||||
expect($('.action .submit')).toHandleWith('click', this.problem.submit_fd);
|
||||
});
|
||||
|
||||
it('bind the reset button', function() {
|
||||
expect($('div.action button.reset')).toHandleWith('click', this.problem.reset);
|
||||
});
|
||||
|
||||
it('bind the show button', function() {
|
||||
expect($('.action .show')).toHandleWith('click', this.problem.show);
|
||||
});
|
||||
|
||||
it('bind the save button', function() {
|
||||
expect($('div.action button.save')).toHandleWith('click', this.problem.save);
|
||||
});
|
||||
|
||||
it('bind the math input', function() {
|
||||
expect($('input.math')).toHandleWith('keyup', this.problem.refreshMath);
|
||||
});
|
||||
});
|
||||
|
||||
describe('bind_with_custom_input_id', function() {
|
||||
beforeEach(function() {
|
||||
spyOn(window, 'update_schematics');
|
||||
MathJax.Hub.getAllJax.and.returnValue([this.stubbedJax]);
|
||||
this.problem = new Problem($('.xblock-student_view'));
|
||||
return $(this).html(readFixtures('problem_content_1240.html'));
|
||||
});
|
||||
|
||||
it('bind the submit button', function() {
|
||||
expect($('.action .submit')).toHandleWith('click', this.problem.submit_fd);
|
||||
});
|
||||
|
||||
it('bind the show button', function() {
|
||||
expect($('div.action button.show')).toHandleWith('click', this.problem.show);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
describe('renderProgressState', function() {
|
||||
beforeEach(function() {
|
||||
this.problem = new Problem($('.xblock-student_view'));
|
||||
});
|
||||
|
||||
const testProgessData = function(problem, score, total_possible, attempts, graded, expected_progress_after_render) {
|
||||
problem.el.data('problem-score', score);
|
||||
problem.el.data('problem-total-possible', total_possible);
|
||||
problem.el.data('attempts-used', attempts);
|
||||
problem.el.data('graded', graded);
|
||||
expect(problem.$('.problem-progress').html()).toEqual("");
|
||||
problem.renderProgressState();
|
||||
expect(problem.$('.problem-progress').html()).toEqual(expected_progress_after_render);
|
||||
};
|
||||
|
||||
describe('with a status of "none"', function() {
|
||||
it('reports the number of points possible and graded', function() {
|
||||
testProgessData(this.problem, 0, 1, 0, "True", "1 point possible (graded)");
|
||||
});
|
||||
|
||||
it('displays the number of points possible when rendering happens with the content', function() {
|
||||
testProgessData(this.problem, 0, 2, 0, "True", "2 points possible (graded)");
|
||||
});
|
||||
|
||||
it('reports the number of points possible and ungraded', function() {
|
||||
testProgessData(this.problem, 0, 1, 0, "False", "1 point possible (ungraded)");
|
||||
});
|
||||
|
||||
it('displays ungraded if number of points possible is 0', function() {
|
||||
testProgessData(this.problem, 0, 0, 0, "False", "0 points possible (ungraded)");
|
||||
});
|
||||
|
||||
it('displays ungraded if number of points possible is 0, even if graded value is True', function() {
|
||||
testProgessData(this.problem, 0, 0, 0, "True", "0 points possible (ungraded)");
|
||||
});
|
||||
|
||||
it('reports the correct score with status none and >0 attempts', function() {
|
||||
testProgessData(this.problem, 0, 1, 1, "True", "0/1 point (graded)");
|
||||
});
|
||||
|
||||
it('reports the correct score with >1 weight, status none, and >0 attempts', function() {
|
||||
testProgessData(this.problem, 0, 2, 2, "True", "0/2 points (graded)");
|
||||
});
|
||||
});
|
||||
|
||||
describe('with any other valid status', function() {
|
||||
|
||||
it('reports the current score', function() {
|
||||
testProgessData(this.problem, 1, 1, 1, "True", "1/1 point (graded)");
|
||||
});
|
||||
|
||||
it('shows current score when rendering happens with the content', function() {
|
||||
testProgessData(this.problem, 2, 2, 1, "True", "2/2 points (graded)");
|
||||
});
|
||||
|
||||
it('reports the current score even if problem is ungraded', function() {
|
||||
testProgessData(this.problem, 1, 1, 1, "False", "1/1 point (ungraded)");
|
||||
});
|
||||
});
|
||||
|
||||
describe('with valid status and string containing an integer like "0" for detail', () =>
|
||||
// These tests are to address a failure specific to Chrome 51 and 52 +
|
||||
it('shows 0 points possible for the detail', function() {
|
||||
testProgessData(this.problem, 0, 0, 1, "False", "0 points possible (ungraded)");
|
||||
})
|
||||
);
|
||||
|
||||
describe('with a score of null (show_correctness == false)', function() {
|
||||
it('reports the number of points possible and graded, results hidden', function() {
|
||||
testProgessData(this.problem, null, 1, 0, "True", "1 point possible (graded, results hidden)");
|
||||
});
|
||||
|
||||
it('reports the number of points possible (plural) and graded, results hidden', function() {
|
||||
testProgessData(this.problem, null, 2, 0, "True", "2 points possible (graded, results hidden)");
|
||||
});
|
||||
|
||||
it('reports the number of points possible and ungraded, results hidden', function() {
|
||||
testProgessData(this.problem, null, 1, 0, "False", "1 point possible (ungraded, results hidden)");
|
||||
});
|
||||
|
||||
it('displays ungraded if number of points possible is 0, results hidden', function() {
|
||||
testProgessData(this.problem, null, 0, 0, "False", "0 points possible (ungraded, results hidden)");
|
||||
});
|
||||
|
||||
it('displays ungraded if number of points possible is 0, even if graded value is True, results hidden', function() {
|
||||
testProgessData(this.problem, null, 0, 0, "True", "0 points possible (ungraded, results hidden)");
|
||||
});
|
||||
|
||||
it('reports the correct score with status none and >0 attempts, results hidden', function() {
|
||||
testProgessData(this.problem, null, 1, 1, "True", "1 point possible (graded, results hidden)");
|
||||
});
|
||||
|
||||
it('reports the correct score with >1 weight, status none, and >0 attempts, results hidden', function() {
|
||||
testProgessData(this.problem, null, 2, 2, "True", "2 points possible (graded, results hidden)");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('render', function() {
|
||||
beforeEach(function() {
|
||||
this.problem = new Problem($('.xblock-student_view'));
|
||||
this.bind = this.problem.bind;
|
||||
spyOn(this.problem, 'bind');
|
||||
});
|
||||
|
||||
describe('with content given', function() {
|
||||
beforeEach(function() {
|
||||
this.problem.render('Hello World');
|
||||
});
|
||||
|
||||
it('render the content', function() {
|
||||
expect(this.problem.el.html()).toEqual('Hello World');
|
||||
});
|
||||
|
||||
it('re-bind the content', function() {
|
||||
expect(this.problem.bind).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('with no content given', function() {
|
||||
beforeEach(function() {
|
||||
spyOn($, 'postWithPrefix').and.callFake((url, callback) => callback({html: "Hello World"}));
|
||||
this.problem.render();
|
||||
});
|
||||
|
||||
it('load the content via ajax', function() {
|
||||
expect(this.problem.el.html()).toEqual('Hello World');
|
||||
});
|
||||
|
||||
it('re-bind the content', function() {
|
||||
expect(this.problem.bind).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('submit_fd', function() {
|
||||
beforeEach(function() {
|
||||
// Insert an input of type file outside of the problem.
|
||||
$('.xblock-student_view').after('<input type="file" />');
|
||||
this.problem = new Problem($('.xblock-student_view'));
|
||||
spyOn(this.problem, 'submit');
|
||||
});
|
||||
|
||||
it('submit method is called if input of type file is not in problem', function() {
|
||||
this.problem.submit_fd();
|
||||
expect(this.problem.submit).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('submit', function() {
|
||||
beforeEach(function() {
|
||||
this.problem = new Problem($('.xblock-student_view'));
|
||||
this.problem.answers = 'foo=1&bar=2';
|
||||
});
|
||||
|
||||
it('log the problem_check event', function() {
|
||||
spyOn($, 'postWithPrefix').and.callFake(function(url, answers, callback) {
|
||||
let promise;
|
||||
promise = {
|
||||
always(callable) { return callable(); },
|
||||
done(callable) { return callable(); }
|
||||
};
|
||||
return promise;
|
||||
});
|
||||
this.problem.submit();
|
||||
expect(Logger.log).toHaveBeenCalledWith('problem_check', 'foo=1&bar=2');
|
||||
});
|
||||
|
||||
it('log the problem_graded event, after the problem is done grading.', function() {
|
||||
spyOn($, 'postWithPrefix').and.callFake(function(url, answers, callback) {
|
||||
let promise;
|
||||
const response = {
|
||||
success: 'correct',
|
||||
contents: 'mock grader response'
|
||||
};
|
||||
callback(response);
|
||||
promise = {
|
||||
always(callable) { return callable(); },
|
||||
done(callable) { return callable(); }
|
||||
};
|
||||
return promise;
|
||||
});
|
||||
this.problem.submit();
|
||||
expect(Logger.log).toHaveBeenCalledWith('problem_graded', ['foo=1&bar=2', 'mock grader response'], this.problem.id);
|
||||
});
|
||||
|
||||
it('submit the answer for submit', function() {
|
||||
spyOn($, 'postWithPrefix').and.callFake(function(url, answers, callback) {
|
||||
let promise;
|
||||
promise = {
|
||||
always(callable) { return callable(); },
|
||||
done(callable) { return callable(); }
|
||||
};
|
||||
return promise;
|
||||
});
|
||||
this.problem.submit();
|
||||
expect($.postWithPrefix).toHaveBeenCalledWith('/problem/Problem1/problem_check',
|
||||
'foo=1&bar=2', jasmine.any(Function));
|
||||
});
|
||||
|
||||
describe('when the response is correct', () =>
|
||||
it('call render with returned content', function() {
|
||||
const contents = '<div class="wrapper-problem-response" aria-label="Question 1"><p>Correct<span class="status">excellent</span></p></div>' +
|
||||
'<div class="wrapper-problem-response" aria-label="Question 2"><p>Yep<span class="status">correct</span></p></div>';
|
||||
spyOn($, 'postWithPrefix').and.callFake(function(url, answers, callback) {
|
||||
let promise;
|
||||
callback({success: 'correct', contents});
|
||||
promise = {
|
||||
always(callable) { return callable(); },
|
||||
done(callable) { return callable(); }
|
||||
};
|
||||
return promise;
|
||||
});
|
||||
this.problem.submit();
|
||||
expect(this.problem.el).toHaveHtml(contents);
|
||||
expect(window.SR.readTexts).toHaveBeenCalledWith(['Question 1: excellent', 'Question 2: correct']);
|
||||
})
|
||||
);
|
||||
|
||||
describe('when the response is incorrect', () =>
|
||||
it('call render with returned content', function() {
|
||||
const contents = '<p>Incorrect<span class="status">no, try again</span></p>';
|
||||
spyOn($, 'postWithPrefix').and.callFake(function(url, answers, callback) {
|
||||
let promise;
|
||||
callback({success: 'incorrect', contents});
|
||||
promise = {
|
||||
always(callable) { return callable(); },
|
||||
done(callable) { return callable(); }
|
||||
};
|
||||
return promise;
|
||||
});
|
||||
this.problem.submit();
|
||||
expect(this.problem.el).toHaveHtml(contents);
|
||||
expect(window.SR.readTexts).toHaveBeenCalledWith(['no, try again']);
|
||||
})
|
||||
);
|
||||
|
||||
it('tests if the submit button is disabled while submitting and the text changes on the button', function() {
|
||||
const self = this;
|
||||
const curr_html = this.problem.el.html();
|
||||
spyOn($, 'postWithPrefix').and.callFake(function(url, answers, callback) {
|
||||
// At this point enableButtons should have been called, making the submit button disabled with text 'submitting'
|
||||
let promise;
|
||||
expect(self.problem.submitButton).toHaveAttr('disabled');
|
||||
expect(self.problem.submitButtonLabel.text()).toBe('Submitting');
|
||||
callback({
|
||||
success: 'incorrect', // does not matter if correct or incorrect here
|
||||
contents: curr_html
|
||||
});
|
||||
promise = {
|
||||
always(callable) { return callable(); },
|
||||
done(callable) { return callable(); }
|
||||
};
|
||||
return promise;
|
||||
});
|
||||
// Make sure the submit button is enabled before submitting
|
||||
$('#input_example_1').val('test').trigger('input');
|
||||
expect(this.problem.submitButton).not.toHaveAttr('disabled');
|
||||
this.problem.submit();
|
||||
// After submit, the button should not be disabled and should have text as 'Submit'
|
||||
expect(this.problem.submitButtonLabel.text()).toBe('Submit');
|
||||
expect(this.problem.submitButton).not.toHaveAttr('disabled');
|
||||
});
|
||||
});
|
||||
|
||||
describe('submit button on problems', function() {
|
||||
|
||||
beforeEach(function() {
|
||||
this.problem = new Problem($('.xblock-student_view'));
|
||||
this.submitDisabled = disabled => {
|
||||
if (disabled) {
|
||||
expect(this.problem.submitButton).toHaveAttr('disabled');
|
||||
} else {
|
||||
expect(this.problem.submitButton).not.toHaveAttr('disabled');
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
describe('some basic tests for submit button', () =>
|
||||
it('should become enabled after a value is entered into the text box', function() {
|
||||
$('#input_example_1').val('test').trigger('input');
|
||||
this.submitDisabled(false);
|
||||
$('#input_example_1').val('').trigger('input');
|
||||
this.submitDisabled(true);
|
||||
})
|
||||
);
|
||||
|
||||
describe('some advanced tests for submit button', function() {
|
||||
const radioButtonProblemHtml = readFixtures('radiobutton_problem.html');
|
||||
const checkboxProblemHtml = readFixtures('checkbox_problem.html');
|
||||
|
||||
it('should become enabled after a checkbox is checked', function() {
|
||||
$('#input_example_1').replaceWith(checkboxProblemHtml);
|
||||
this.problem.submitAnswersAndSubmitButton(true);
|
||||
this.submitDisabled(true);
|
||||
$('#input_1_1_1').click();
|
||||
this.submitDisabled(false);
|
||||
$('#input_1_1_1').click();
|
||||
this.submitDisabled(true);
|
||||
});
|
||||
|
||||
it('should become enabled after a radiobutton is checked', function() {
|
||||
$('#input_example_1').replaceWith(radioButtonProblemHtml);
|
||||
this.problem.submitAnswersAndSubmitButton(true);
|
||||
this.submitDisabled(true);
|
||||
$('#input_1_1_1').attr('checked', true).trigger('click');
|
||||
this.submitDisabled(false);
|
||||
$('#input_1_1_1').attr('checked', false).trigger('click');
|
||||
this.submitDisabled(true);
|
||||
});
|
||||
|
||||
it('should become enabled after a value is selected in a selector', function() {
|
||||
const html = `\
|
||||
<div id="problem_sel">
|
||||
<select>
|
||||
<option value="val0">Select an option</option>
|
||||
<option value="val1">1</option>
|
||||
<option value="val2">2</option>
|
||||
</select>
|
||||
</div>\
|
||||
`;
|
||||
$('#input_example_1').replaceWith(html);
|
||||
this.problem.submitAnswersAndSubmitButton(true);
|
||||
this.submitDisabled(true);
|
||||
$("#problem_sel select").val("val2").trigger('change');
|
||||
this.submitDisabled(false);
|
||||
$("#problem_sel select").val("val0").trigger('change');
|
||||
this.submitDisabled(true);
|
||||
});
|
||||
|
||||
it('should become enabled after a radiobutton is checked and a value is entered into the text box', function() {
|
||||
$(radioButtonProblemHtml).insertAfter('#input_example_1');
|
||||
this.problem.submitAnswersAndSubmitButton(true);
|
||||
this.submitDisabled(true);
|
||||
$('#input_1_1_1').attr('checked', true).trigger('click');
|
||||
this.submitDisabled(true);
|
||||
$('#input_example_1').val('111').trigger('input');
|
||||
this.submitDisabled(false);
|
||||
$('#input_1_1_1').attr('checked', false).trigger('click');
|
||||
this.submitDisabled(true);
|
||||
});
|
||||
|
||||
it('should become enabled if there are only hidden input fields', function() {
|
||||
const html = `\
|
||||
<input type="text" name="test" id="test" aria-describedby="answer_test" value="" style="display:none;">\
|
||||
`;
|
||||
$('#input_example_1').replaceWith(html);
|
||||
this.problem.submitAnswersAndSubmitButton(true);
|
||||
this.submitDisabled(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('reset', function() {
|
||||
beforeEach(function() {
|
||||
this.problem = new Problem($('.xblock-student_view'));
|
||||
});
|
||||
|
||||
it('log the problem_reset event', function() {
|
||||
spyOn($, 'postWithPrefix').and.callFake(function(url, answers, callback) {
|
||||
let promise;
|
||||
promise =
|
||||
{always(callable) { return callable(); }};
|
||||
return promise;
|
||||
});
|
||||
this.problem.answers = 'foo=1&bar=2';
|
||||
this.problem.reset();
|
||||
expect(Logger.log).toHaveBeenCalledWith('problem_reset', 'foo=1&bar=2');
|
||||
});
|
||||
|
||||
it('POST to the problem reset page', function() {
|
||||
spyOn($, 'postWithPrefix').and.callFake(function(url, answers, callback) {
|
||||
let promise;
|
||||
promise =
|
||||
{always(callable) { return callable(); }};
|
||||
return promise;
|
||||
});
|
||||
this.problem.reset();
|
||||
expect($.postWithPrefix).toHaveBeenCalledWith('/problem/Problem1/problem_reset',
|
||||
{ id: 'i4x://edX/101/problem/Problem1' }, jasmine.any(Function));
|
||||
});
|
||||
|
||||
it('render the returned content', function() {
|
||||
spyOn($, 'postWithPrefix').and.callFake(function(url, answers, callback) {
|
||||
let promise;
|
||||
callback({html: "Reset", success: true});
|
||||
promise =
|
||||
{always(callable) { return callable(); }};
|
||||
return promise;
|
||||
});
|
||||
this.problem.reset();
|
||||
expect(this.problem.el.html()).toEqual('Reset');
|
||||
});
|
||||
|
||||
it('sends a message to the window SR element', function() {
|
||||
spyOn($, 'postWithPrefix').and.callFake(function(url, answers, callback) {
|
||||
let promise;
|
||||
callback({html: "Reset", success: true});
|
||||
promise =
|
||||
{always(callable) { return callable(); }};
|
||||
return promise;
|
||||
});
|
||||
this.problem.reset();
|
||||
expect(window.SR.readText).toHaveBeenCalledWith('This problem has been reset.');
|
||||
});
|
||||
|
||||
it('shows a notification on error', function() {
|
||||
spyOn($, 'postWithPrefix').and.callFake(function(url, answers, callback) {
|
||||
let promise;
|
||||
callback({msg: "Error on reset.", success: false});
|
||||
promise =
|
||||
{always(callable) { return callable(); }};
|
||||
return promise;
|
||||
});
|
||||
this.problem.reset();
|
||||
expect($('.notification-gentle-alert .notification-message').text()).toEqual("Error on reset.");
|
||||
});
|
||||
|
||||
it('tests that reset does not enable submit or modify the text while resetting', function() {
|
||||
const self = this;
|
||||
const curr_html = this.problem.el.html();
|
||||
spyOn($, 'postWithPrefix').and.callFake(function(url, answers, callback) {
|
||||
// enableButtons should have been called at this point to set them to all disabled
|
||||
let promise;
|
||||
expect(self.problem.submitButton).toHaveAttr('disabled');
|
||||
expect(self.problem.submitButtonLabel.text()).toBe('Submit');
|
||||
callback({success: 'correct', html: curr_html});
|
||||
promise =
|
||||
{always(callable) { return callable(); }};
|
||||
return promise;
|
||||
});
|
||||
// Submit should be disabled
|
||||
expect(this.problem.submitButton).toHaveAttr('disabled');
|
||||
this.problem.reset();
|
||||
// Submit should remain disabled
|
||||
expect(self.problem.submitButton).toHaveAttr('disabled');
|
||||
expect(self.problem.submitButtonLabel.text()).toBe('Submit');
|
||||
});
|
||||
});
|
||||
|
||||
describe('show', function() {
|
||||
beforeEach(function() {
|
||||
this.problem = new Problem($('.xblock-student_view'));
|
||||
this.problem.el.prepend('<div id="answer_1_1" /><div id="answer_1_2" />');
|
||||
});
|
||||
|
||||
describe('when the answer has not yet shown', function() {
|
||||
beforeEach(function() {
|
||||
expect(this.problem.el.find('.show').attr('disabled')).not.toEqual('disabled');
|
||||
});
|
||||
|
||||
it('log the problem_show event', function() {
|
||||
this.problem.show();
|
||||
expect(Logger.log).toHaveBeenCalledWith('problem_show',
|
||||
{problem: 'i4x://edX/101/problem/Problem1'});
|
||||
});
|
||||
|
||||
it('fetch the answers', function() {
|
||||
spyOn($, 'postWithPrefix');
|
||||
this.problem.show();
|
||||
expect($.postWithPrefix).toHaveBeenCalledWith('/problem/Problem1/problem_show',
|
||||
jasmine.any(Function));
|
||||
});
|
||||
|
||||
it('show the answers', function() {
|
||||
spyOn($, 'postWithPrefix').and.callFake((url, callback) => callback({answers: {'1_1': 'One', '1_2': 'Two'}}));
|
||||
this.problem.show();
|
||||
expect($('#answer_1_1')).toHaveHtml('One');
|
||||
expect($('#answer_1_2')).toHaveHtml('Two');
|
||||
});
|
||||
|
||||
it('disables the show answer button', function() {
|
||||
spyOn($, 'postWithPrefix').and.callFake((url, callback) => callback({answers: {}}));
|
||||
this.problem.show();
|
||||
expect(this.problem.el.find('.show').attr('disabled')).toEqual('disabled');
|
||||
});
|
||||
|
||||
describe('radio text question', function() {
|
||||
const radio_text_xml=`\
|
||||
<section class="problem">
|
||||
<div><p></p><span><section id="choicetextinput_1_2_1" class="choicetextinput">
|
||||
|
||||
<form class="choicetextgroup capa_inputtype" id="inputtype_1_2_1">
|
||||
<div class="indicator-container">
|
||||
<span class="unanswered" style="display:inline-block;" id="status_1_2_1"></span>
|
||||
</div>
|
||||
<fieldset>
|
||||
<section id="forinput1_2_1_choiceinput_0bc">
|
||||
<input class="ctinput" type="radio" name="choiceinput_1_2_1" id="1_2_1_choiceinput_0bc" value="choiceinput_0"">
|
||||
<input class="ctinput" type="text" name="choiceinput_0_textinput_0" id="1_2_1_choiceinput_0_textinput_0" value=" ">
|
||||
<p id="answer_1_2_1_choiceinput_0bc" class="answer"></p>
|
||||
</>
|
||||
<section id="forinput1_2_1_choiceinput_1bc">
|
||||
<input class="ctinput" type="radio" name="choiceinput_1_2_1" id="1_2_1_choiceinput_1bc" value="choiceinput_1" >
|
||||
<input class="ctinput" type="text" name="choiceinput_1_textinput_0" id="1_2_1_choiceinput_1_textinput_0" value=" " >
|
||||
<p id="answer_1_2_1_choiceinput_1bc" class="answer"></p>
|
||||
</section>
|
||||
<section id="forinput1_2_1_choiceinput_2bc">
|
||||
<input class="ctinput" type="radio" name="choiceinput_1_2_1" id="1_2_1_choiceinput_2bc" value="choiceinput_2" >
|
||||
<input class="ctinput" type="text" name="choiceinput_2_textinput_0" id="1_2_1_choiceinput_2_textinput_0" value=" " >
|
||||
<p id="answer_1_2_1_choiceinput_2bc" class="answer"></p>
|
||||
</section></fieldset><input class="choicetextvalue" type="hidden" name="input_1_2_1" id="input_1_2_1"></form>
|
||||
</section></span></div>
|
||||
</section>\
|
||||
`;
|
||||
beforeEach(function() {
|
||||
// Append a radiotextresponse problem to the problem, so we can check it's javascript functionality
|
||||
this.problem.el.prepend(radio_text_xml);
|
||||
});
|
||||
|
||||
it('sets the correct class on the section for the correct choice', function() {
|
||||
spyOn($, 'postWithPrefix').and.callFake((url, callback) => callback({answers: {"1_2_1": ["1_2_1_choiceinput_0bc"], "1_2_1_choiceinput_0bc": "3"}}));
|
||||
this.problem.show();
|
||||
|
||||
expect($('#forinput1_2_1_choiceinput_0bc').attr('class')).toEqual(
|
||||
'choicetextgroup_show_correct');
|
||||
expect($('#answer_1_2_1_choiceinput_0bc').text()).toEqual('3');
|
||||
expect($('#answer_1_2_1_choiceinput_1bc').text()).toEqual('');
|
||||
expect($('#answer_1_2_1_choiceinput_2bc').text()).toEqual('');
|
||||
});
|
||||
|
||||
it('Should not disable input fields', function() {
|
||||
spyOn($, 'postWithPrefix').and.callFake((url, callback) => callback({answers: {"1_2_1": ["1_2_1_choiceinput_0bc"], "1_2_1_choiceinput_0bc": "3"}}));
|
||||
this.problem.show();
|
||||
expect($('input#1_2_1_choiceinput_0bc').attr('disabled')).not.toEqual('disabled');
|
||||
expect($('input#1_2_1_choiceinput_1bc').attr('disabled')).not.toEqual('disabled');
|
||||
expect($('input#1_2_1_choiceinput_2bc').attr('disabled')).not.toEqual('disabled');
|
||||
expect($('input#1_2_1').attr('disabled')).not.toEqual('disabled');
|
||||
});
|
||||
});
|
||||
|
||||
describe('imageinput', function() {
|
||||
let el, height, width;
|
||||
const imageinput_html = readFixtures('imageinput.underscore');
|
||||
|
||||
const DEFAULTS = {
|
||||
id: '12345',
|
||||
width: '300',
|
||||
height: '400'
|
||||
};
|
||||
|
||||
beforeEach(function() {
|
||||
this.problem = new Problem($('.xblock-student_view'));
|
||||
this.problem.el.prepend(_.template(imageinput_html)(DEFAULTS));
|
||||
});
|
||||
|
||||
const assertAnswer = (problem, data) => {
|
||||
stubRequest(data);
|
||||
problem.show();
|
||||
|
||||
$.each(data['answers'], (id, answer) => {
|
||||
const img = getImage(answer);
|
||||
el = $(`#inputtype_${id}`);
|
||||
expect(img).toImageDiffEqual(el.find('canvas')[0]);
|
||||
});
|
||||
};
|
||||
|
||||
var stubRequest = data => {
|
||||
spyOn($, 'postWithPrefix').and.callFake((url, callback) => callback(data));
|
||||
};
|
||||
|
||||
var getImage = (coords, c_width, c_height) => {
|
||||
let ctx, reg;
|
||||
const types = {
|
||||
rectangle: coords => {
|
||||
reg = /^\(([0-9]+),([0-9]+)\)-\(([0-9]+),([0-9]+)\)$/;
|
||||
const rects = coords.replace(/\s*/g, '').split(/;/);
|
||||
|
||||
$.each(rects, (index, rect) => {
|
||||
const { abs } = Math;
|
||||
const points = reg.exec(rect);
|
||||
if (points) {
|
||||
width = abs(points[3] - points[1]);
|
||||
height = abs(points[4] - points[2]);
|
||||
|
||||
return ctx.rect(points[1], points[2], width, height);
|
||||
}
|
||||
});
|
||||
|
||||
ctx.stroke();
|
||||
ctx.fill();
|
||||
},
|
||||
|
||||
regions: coords => {
|
||||
const parseCoords = coords => {
|
||||
reg = JSON.parse(coords);
|
||||
|
||||
if (typeof reg[0][0][0] === "undefined") {
|
||||
reg = [reg];
|
||||
}
|
||||
|
||||
return reg;
|
||||
};
|
||||
|
||||
return $.each(parseCoords(coords), (index, region) => {
|
||||
ctx.beginPath();
|
||||
$.each(region, (index, point) => {
|
||||
if (index === 0) {
|
||||
return ctx.moveTo(point[0], point[1]);
|
||||
} else {
|
||||
return ctx.lineTo(point[0], point[1]);
|
||||
}
|
||||
});
|
||||
|
||||
ctx.closePath();
|
||||
ctx.stroke();
|
||||
ctx.fill();
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const canvas = document.createElement('canvas');
|
||||
canvas.width = c_width || 100;
|
||||
canvas.height = c_height || 100;
|
||||
|
||||
if (canvas.getContext) {
|
||||
ctx = canvas.getContext('2d');
|
||||
} else {
|
||||
console.log('Canvas is not supported.');
|
||||
}
|
||||
|
||||
ctx.fillStyle = 'rgba(255,255,255,.3)';
|
||||
ctx.strokeStyle = "#FF0000";
|
||||
ctx.lineWidth = "2";
|
||||
|
||||
$.each(coords, (key, value) => {
|
||||
if ((types[key] != null) && value) { return types[key](value); }
|
||||
});
|
||||
|
||||
return canvas;
|
||||
};
|
||||
|
||||
it('rectangle is drawn correctly', function() {
|
||||
assertAnswer(this.problem, {
|
||||
'answers': {
|
||||
'12345': {
|
||||
'rectangle': '(10,10)-(30,30)',
|
||||
'regions': null
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it('region is drawn correctly', function() {
|
||||
assertAnswer(this.problem, {
|
||||
'answers': {
|
||||
'12345': {
|
||||
'rectangle': null,
|
||||
'regions': '[[10,10],[30,30],[70,30],[20,30]]'
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it('mixed shapes are drawn correctly', function() {
|
||||
assertAnswer(this.problem, {
|
||||
'answers': {'12345': {
|
||||
'rectangle': '(10,10)-(30,30);(5,5)-(20,20)',
|
||||
'regions': `[
|
||||
[[50,50],[40,40],[70,30],[50,70]],
|
||||
[[90,95],[95,95],[90,70],[70,70]]
|
||||
]`
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it('multiple image inputs draw answers on separate canvases', function() {
|
||||
const data = {
|
||||
id: '67890',
|
||||
width: '400',
|
||||
height: '300'
|
||||
};
|
||||
|
||||
this.problem.el.prepend(_.template(imageinput_html)(data));
|
||||
assertAnswer(this.problem, {
|
||||
'answers': {
|
||||
'12345': {
|
||||
'rectangle': null,
|
||||
'regions': '[[10,10],[30,30],[70,30],[20,30]]'
|
||||
},
|
||||
'67890': {
|
||||
'rectangle': '(10,10)-(30,30)',
|
||||
'regions': null
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it('dictionary with answers doesn\'t contain answer for current id', function() {
|
||||
spyOn(console, 'log');
|
||||
stubRequest({'answers':{}});
|
||||
this.problem.show();
|
||||
el = $('#inputtype_12345');
|
||||
expect(el.find('canvas')).not.toExist();
|
||||
expect(console.log).toHaveBeenCalledWith('Answer is absent for image input with id=12345');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('save', function() {
|
||||
beforeEach(function() {
|
||||
this.problem = new Problem($('.xblock-student_view'));
|
||||
this.problem.answers = 'foo=1&bar=2';
|
||||
});
|
||||
|
||||
it('log the problem_save event', function() {
|
||||
spyOn($, 'postWithPrefix').and.callFake(function(url, answers, callback) {
|
||||
let promise;
|
||||
promise =
|
||||
{always(callable) { return callable(); }};
|
||||
return promise;
|
||||
});
|
||||
this.problem.save();
|
||||
expect(Logger.log).toHaveBeenCalledWith('problem_save', 'foo=1&bar=2');
|
||||
});
|
||||
|
||||
it('POST to save problem', function() {
|
||||
spyOn($, 'postWithPrefix').and.callFake(function(url, answers, callback) {
|
||||
let promise;
|
||||
promise =
|
||||
{always(callable) { return callable(); }};
|
||||
return promise;
|
||||
});
|
||||
this.problem.save();
|
||||
expect($.postWithPrefix).toHaveBeenCalledWith('/problem/Problem1/problem_save',
|
||||
'foo=1&bar=2', jasmine.any(Function));
|
||||
});
|
||||
|
||||
it('tests that save does not enable the submit button or change the text when submit is originally disabled', function() {
|
||||
const self = this;
|
||||
const curr_html = this.problem.el.html();
|
||||
spyOn($, 'postWithPrefix').and.callFake(function(url, answers, callback) {
|
||||
// enableButtons should have been called at this point and the submit button should be unaffected
|
||||
let promise;
|
||||
expect(self.problem.submitButton).toHaveAttr('disabled');
|
||||
expect(self.problem.submitButtonLabel.text()).toBe('Submit');
|
||||
callback({success: 'correct', html: curr_html});
|
||||
promise =
|
||||
{always(callable) { return callable(); }};
|
||||
return promise;
|
||||
});
|
||||
// Expect submit to be disabled and labeled properly at the start
|
||||
expect(this.problem.submitButton).toHaveAttr('disabled');
|
||||
expect(this.problem.submitButtonLabel.text()).toBe('Submit');
|
||||
this.problem.save();
|
||||
// Submit button should have the same state after save has completed
|
||||
expect(this.problem.submitButton).toHaveAttr('disabled');
|
||||
expect(this.problem.submitButtonLabel.text()).toBe('Submit');
|
||||
});
|
||||
|
||||
it('tests that save does not disable the submit button or change the text when submit is originally enabled', function() {
|
||||
const self = this;
|
||||
const curr_html = this.problem.el.html();
|
||||
spyOn($, 'postWithPrefix').and.callFake(function(url, answers, callback) {
|
||||
// enableButtons should have been called at this point, and the submit button should be disabled while submitting
|
||||
let promise;
|
||||
expect(self.problem.submitButton).toHaveAttr('disabled');
|
||||
expect(self.problem.submitButtonLabel.text()).toBe('Submit');
|
||||
callback({success: 'correct', html: curr_html});
|
||||
promise =
|
||||
{always(callable) { return callable(); }};
|
||||
return promise;
|
||||
});
|
||||
// Expect submit to be enabled and labeled properly at the start after adding an input
|
||||
$('#input_example_1').val('test').trigger('input');
|
||||
expect(this.problem.submitButton).not.toHaveAttr('disabled');
|
||||
expect(this.problem.submitButtonLabel.text()).toBe('Submit');
|
||||
this.problem.save();
|
||||
// Submit button should have the same state after save has completed
|
||||
expect(this.problem.submitButton).not.toHaveAttr('disabled');
|
||||
expect(this.problem.submitButtonLabel.text()).toBe('Submit');
|
||||
});
|
||||
});
|
||||
|
||||
describe('refreshMath', function() {
|
||||
beforeEach(function() {
|
||||
this.problem = new Problem($('.xblock-student_view'));
|
||||
$('#input_example_1').val('E=mc^2');
|
||||
this.problem.refreshMath({target: $('#input_example_1').get(0)});
|
||||
});
|
||||
|
||||
it('should queue the conversion and MathML element update', function() {
|
||||
expect(MathJax.Hub.Queue).toHaveBeenCalledWith(['Text', this.stubbedJax, 'E=mc^2'],
|
||||
[this.problem.updateMathML, this.stubbedJax, $('#input_example_1').get(0)]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('updateMathML', function() {
|
||||
beforeEach(function() {
|
||||
this.problem = new Problem($('.xblock-student_view'));
|
||||
this.stubbedJax.root.toMathML.and.returnValue('<MathML>');
|
||||
});
|
||||
|
||||
describe('when there is no exception', function() {
|
||||
beforeEach(function() {
|
||||
this.problem.updateMathML(this.stubbedJax, $('#input_example_1').get(0));
|
||||
});
|
||||
|
||||
it('convert jax to MathML', () => expect($('#input_example_1_dynamath')).toHaveValue('<MathML>'));
|
||||
});
|
||||
|
||||
describe('when there is an exception', function() {
|
||||
beforeEach(function() {
|
||||
const error = new Error();
|
||||
error.restart = true;
|
||||
this.stubbedJax.root.toMathML.and.throwError(error);
|
||||
this.problem.updateMathML(this.stubbedJax, $('#input_example_1').get(0));
|
||||
});
|
||||
|
||||
it('should queue up the exception', function() {
|
||||
expect(MathJax.Callback.After).toHaveBeenCalledWith([this.problem.refreshMath, this.stubbedJax], true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('refreshAnswers', function() {
|
||||
beforeEach(function() {
|
||||
this.problem = new Problem($('.xblock-student_view'));
|
||||
this.problem.el.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" />\
|
||||
`
|
||||
);
|
||||
this.stubSchematic = { update_value: jasmine.createSpy('schematic') };
|
||||
this.stubCodeMirror = { save: jasmine.createSpy('CodeMirror') };
|
||||
$('input.schematic').get(0).schematic = this.stubSchematic;
|
||||
$('textarea.CodeMirror').get(0).CodeMirror = this.stubCodeMirror;
|
||||
});
|
||||
|
||||
it('update each schematic', function() {
|
||||
this.problem.refreshAnswers();
|
||||
expect(this.stubSchematic.update_value).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('update each code block', function() {
|
||||
this.problem.refreshAnswers();
|
||||
expect(this.stubCodeMirror.save).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('multiple JsInput in single problem', function() {
|
||||
const jsinput_html = readFixtures('jsinput_problem.html');
|
||||
|
||||
beforeEach(function() {
|
||||
this.problem = new Problem($('.xblock-student_view'));
|
||||
this.problem.render(jsinput_html);
|
||||
});
|
||||
|
||||
it('submit_save_waitfor should return false', function() {
|
||||
$(this.problem.inputs[0]).data('waitfor', function() {});
|
||||
expect(this.problem.submit_save_waitfor()).toEqual(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Submitting an xqueue-graded problem', function() {
|
||||
const matlabinput_html = readFixtures('matlabinput_problem.html');
|
||||
|
||||
beforeEach(function() {
|
||||
spyOn($, 'postWithPrefix').and.callFake((url, callback) => callback({html: matlabinput_html}));
|
||||
jasmine.clock().install();
|
||||
this.problem = new Problem($('.xblock-student_view'));
|
||||
spyOn(this.problem, 'poll').and.callThrough();
|
||||
this.problem.render(matlabinput_html);
|
||||
});
|
||||
|
||||
afterEach(() => jasmine.clock().uninstall());
|
||||
|
||||
it('check that we stop polling after a fixed amount of time', function() {
|
||||
expect(this.problem.poll).not.toHaveBeenCalled();
|
||||
jasmine.clock().tick(1);
|
||||
const time_steps = [1000, 2000, 4000, 8000, 16000, 32000];
|
||||
let num_calls = 1;
|
||||
for (let time_step of Array.from(time_steps)) {
|
||||
(time_step => {
|
||||
jasmine.clock().tick(time_step);
|
||||
expect(this.problem.poll.calls.count()).toEqual(num_calls);
|
||||
num_calls += 1;
|
||||
})(time_step);
|
||||
}
|
||||
|
||||
// jump the next step and verify that we are not still continuing to poll
|
||||
jasmine.clock().tick(64000);
|
||||
expect(this.problem.poll.calls.count()).toEqual(6);
|
||||
|
||||
expect($('.notification-gentle-alert .notification-message').text()).toEqual("The grading process is still running. Refresh the page to see updates.");
|
||||
});
|
||||
});
|
||||
|
||||
describe('codeinput problem', function() {
|
||||
const codeinputProblemHtml = readFixtures('codeinput_problem.html');
|
||||
|
||||
beforeEach(function() {
|
||||
spyOn($, 'postWithPrefix').and.callFake((url, callback) => callback({html: codeinputProblemHtml}));
|
||||
this.problem = new Problem($('.xblock-student_view'));
|
||||
this.problem.render(codeinputProblemHtml);
|
||||
});
|
||||
|
||||
it('has rendered with correct a11y info', function() {
|
||||
const CodeMirrorTextArea = $('textarea')[1];
|
||||
const CodeMirrorTextAreaId = 'cm-textarea-101';
|
||||
|
||||
// verify that question label has correct `for` attribute value
|
||||
expect($('.problem-group-label').attr('for')).toEqual(CodeMirrorTextAreaId);
|
||||
|
||||
// verify that codemirror textarea has correct `id` attribute value
|
||||
expect($(CodeMirrorTextArea).attr('id')).toEqual(CodeMirrorTextAreaId);
|
||||
|
||||
// verify that codemirror textarea has correct `aria-describedby` attribute value
|
||||
expect($(CodeMirrorTextArea).attr('aria-describedby')).toEqual('cm-editor-exit-message-101 status_101');
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
describe('show answer button', function() {
|
||||
|
||||
const radioButtonProblemHtml = readFixtures('radiobutton_problem.html');
|
||||
const checkboxProblemHtml = readFixtures('checkbox_problem.html');
|
||||
|
||||
beforeEach(function() {
|
||||
this.problem = new Problem($('.xblock-student_view'));
|
||||
|
||||
this.checkAssertionsAfterClickingAnotherOption = () => {
|
||||
// verify that 'show answer button is no longer disabled'
|
||||
expect(this.problem.el.find('.show').attr('disabled')).not.toEqual('disabled');
|
||||
|
||||
// verify that displayed answer disappears
|
||||
expect(this.problem.el.find('div.choicegroup')).not.toHaveClass('choicegroup_correct');
|
||||
|
||||
// verify that radio/checkbox label has no span having class '.status.correct'
|
||||
expect(this.problem.el.find('div.choicegroup')).not.toHaveAttr('span.status.correct');
|
||||
};
|
||||
});
|
||||
|
||||
it('should become enabled after a radiobutton is selected', function() {
|
||||
$('#input_example_1').replaceWith(radioButtonProblemHtml);
|
||||
// assume that 'ShowAnswer' button is clicked,
|
||||
// clicking make it disabled.
|
||||
this.problem.el.find('.show').attr('disabled', 'disabled');
|
||||
// bind click event to input fields
|
||||
this.problem.submitAnswersAndSubmitButton(true);
|
||||
// selects option 2
|
||||
$('#input_1_1_2').attr('checked', true).trigger('click');
|
||||
this.checkAssertionsAfterClickingAnotherOption();
|
||||
});
|
||||
|
||||
it('should become enabled after a checkbox is selected', function() {
|
||||
$('#input_example_1').replaceWith(checkboxProblemHtml);
|
||||
this.problem.el.find('.show').attr('disabled', 'disabled');
|
||||
this.problem.submitAnswersAndSubmitButton(true);
|
||||
$('#input_1_1_2').click();
|
||||
this.checkAssertionsAfterClickingAnotherOption();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,49 +0,0 @@
|
||||
describe 'HTMLEditingDescriptor', ->
|
||||
beforeEach ->
|
||||
window.baseUrl = "/static/deadbeef"
|
||||
afterEach ->
|
||||
delete window.baseUrl
|
||||
describe 'Visual HTML Editor', ->
|
||||
beforeEach ->
|
||||
loadFixtures 'html-edit-visual.html'
|
||||
@descriptor = new HTMLEditingDescriptor($('.test-component'))
|
||||
it 'Returns data from Visual Editor if text has changed', ->
|
||||
visualEditorStub =
|
||||
getContent: () -> 'from visual editor'
|
||||
spyOn(@descriptor, 'getVisualEditor').and.callFake () ->
|
||||
visualEditorStub
|
||||
data = @descriptor.save().data
|
||||
expect(data).toEqual('from visual editor')
|
||||
it 'Returns data from Raw Editor if text has not changed', ->
|
||||
visualEditorStub =
|
||||
getContent: () -> '<p>original visual text</p>'
|
||||
spyOn(@descriptor, 'getVisualEditor').and.callFake () ->
|
||||
visualEditorStub
|
||||
data = @descriptor.save().data
|
||||
expect(data).toEqual('raw text')
|
||||
it 'Performs link rewriting for static assets when saving', ->
|
||||
visualEditorStub =
|
||||
getContent: () -> 'from visual editor with /c4x/foo/bar/asset/image.jpg'
|
||||
spyOn(@descriptor, 'getVisualEditor').and.callFake () ->
|
||||
visualEditorStub
|
||||
data = @descriptor.save().data
|
||||
expect(data).toEqual('from visual editor with /static/image.jpg')
|
||||
it 'When showing visual editor links are rewritten to c4x format', ->
|
||||
visualEditorStub =
|
||||
content: 'text /static/image.jpg'
|
||||
startContent: 'text /static/image.jpg'
|
||||
focus: ->
|
||||
setContent: (x) -> @content = x
|
||||
getContent: -> @content
|
||||
|
||||
@descriptor.initInstanceCallback(visualEditorStub)
|
||||
expect(visualEditorStub.getContent()).toEqual('text /c4x/foo/bar/asset/image.jpg')
|
||||
it 'Enables spellcheck', ->
|
||||
expect($('.html-editor iframe')[0].contentDocument.body.spellcheck).toBe(true)
|
||||
describe 'Raw HTML Editor', ->
|
||||
beforeEach ->
|
||||
loadFixtures 'html-editor-raw.html'
|
||||
@descriptor = new HTMLEditingDescriptor($('.test-component'))
|
||||
it 'Returns data from raw editor', ->
|
||||
data = @descriptor.save().data
|
||||
expect(data).toEqual('raw text')
|
||||
54
common/lib/xmodule/xmodule/js/spec/html/edit_spec.js
Normal file
54
common/lib/xmodule/xmodule/js/spec/html/edit_spec.js
Normal file
@@ -0,0 +1,54 @@
|
||||
describe('HTMLEditingDescriptor', function() {
|
||||
beforeEach(() => window.baseUrl = "/static/deadbeef");
|
||||
afterEach(() => delete window.baseUrl);
|
||||
describe('Visual HTML Editor', function() {
|
||||
beforeEach(function() {
|
||||
loadFixtures('html-edit-visual.html');
|
||||
this.descriptor = new HTMLEditingDescriptor($('.test-component'));
|
||||
});
|
||||
it('Returns data from Visual Editor if text has changed', function() {
|
||||
const visualEditorStub =
|
||||
{getContent() { return 'from visual editor'; }};
|
||||
spyOn(this.descriptor, 'getVisualEditor').and.callFake(() => visualEditorStub);
|
||||
const { data } = this.descriptor.save();
|
||||
expect(data).toEqual('from visual editor');
|
||||
});
|
||||
it('Returns data from Raw Editor if text has not changed', function() {
|
||||
const visualEditorStub =
|
||||
{getContent() { return '<p>original visual text</p>'; }};
|
||||
spyOn(this.descriptor, 'getVisualEditor').and.callFake(() => visualEditorStub);
|
||||
const { data } = this.descriptor.save();
|
||||
expect(data).toEqual('raw text');
|
||||
});
|
||||
it('Performs link rewriting for static assets when saving', function() {
|
||||
const visualEditorStub =
|
||||
{getContent() { return 'from visual editor with /c4x/foo/bar/asset/image.jpg'; }};
|
||||
spyOn(this.descriptor, 'getVisualEditor').and.callFake(() => visualEditorStub);
|
||||
const { data } = this.descriptor.save();
|
||||
expect(data).toEqual('from visual editor with /static/image.jpg');
|
||||
});
|
||||
it('When showing visual editor links are rewritten to c4x format', function() {
|
||||
const visualEditorStub = {
|
||||
content: 'text /static/image.jpg',
|
||||
startContent: 'text /static/image.jpg',
|
||||
focus() {},
|
||||
setContent(x) { this.content = x; },
|
||||
getContent() { return this.content; }
|
||||
};
|
||||
|
||||
this.descriptor.initInstanceCallback(visualEditorStub);
|
||||
expect(visualEditorStub.getContent()).toEqual('text /c4x/foo/bar/asset/image.jpg');
|
||||
});
|
||||
it('Enables spellcheck', () => expect($('.html-editor iframe')[0].contentDocument.body.spellcheck).toBe(true));
|
||||
});
|
||||
describe('Raw HTML Editor', function() {
|
||||
beforeEach(function() {
|
||||
loadFixtures('html-editor-raw.html');
|
||||
this.descriptor = new HTMLEditingDescriptor($('.test-component'));
|
||||
});
|
||||
it('Returns data from raw editor', function() {
|
||||
const { data } = this.descriptor.save();
|
||||
expect(data).toEqual('raw text');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,985 +0,0 @@
|
||||
describe 'MarkdownEditingDescriptor', ->
|
||||
describe 'save stores the correct data', ->
|
||||
it 'saves markdown from markdown editor', ->
|
||||
loadFixtures 'problem-with-markdown.html'
|
||||
@descriptor = new MarkdownEditingDescriptor($('.problem-editor'))
|
||||
saveResult = @descriptor.save()
|
||||
expect(saveResult.metadata.markdown).toEqual('markdown')
|
||||
expect(saveResult.data).toXMLEqual('<problem>\n <p>markdown</p>\n</problem>')
|
||||
it 'clears markdown when xml editor is selected', ->
|
||||
loadFixtures 'problem-with-markdown.html'
|
||||
@descriptor = new MarkdownEditingDescriptor($('.problem-editor'))
|
||||
@descriptor.createXMLEditor('replace with markdown')
|
||||
saveResult = @descriptor.save()
|
||||
expect(saveResult.nullout).toEqual(['markdown'])
|
||||
expect(saveResult.data).toEqual('replace with markdown')
|
||||
it 'saves xml from the xml editor', ->
|
||||
loadFixtures 'problem-without-markdown.html'
|
||||
@descriptor = new MarkdownEditingDescriptor($('.problem-editor'))
|
||||
saveResult = @descriptor.save()
|
||||
expect(saveResult.nullout).toEqual(['markdown'])
|
||||
expect(saveResult.data).toEqual('xml only')
|
||||
|
||||
describe 'advanced editor opens correctly', ->
|
||||
it 'click on advanced editor should work', ->
|
||||
loadFixtures 'problem-with-markdown.html'
|
||||
@descriptor = new MarkdownEditingDescriptor($('.problem-editor'))
|
||||
spyOn(@descriptor, 'confirmConversionToXml').and.returnValue(true)
|
||||
expect(@descriptor.confirmConversionToXml).not.toHaveBeenCalled()
|
||||
e = jasmine.createSpyObj('e', [ 'preventDefault' ])
|
||||
@descriptor.onShowXMLButton(e)
|
||||
expect(e.preventDefault).toHaveBeenCalled()
|
||||
expect(@descriptor.confirmConversionToXml).toHaveBeenCalled()
|
||||
expect($('.editor-bar').length).toEqual(0)
|
||||
|
||||
describe 'insertMultipleChoice', ->
|
||||
it 'inserts the template if selection is empty', ->
|
||||
revisedSelection = MarkdownEditingDescriptor.insertMultipleChoice('')
|
||||
expect(revisedSelection).toEqual(MarkdownEditingDescriptor.multipleChoiceTemplate)
|
||||
it 'wraps existing text', ->
|
||||
revisedSelection = MarkdownEditingDescriptor.insertMultipleChoice('foo\nbar')
|
||||
expect(revisedSelection).toEqual('( ) foo\n( ) bar\n')
|
||||
it 'recognizes x as a selection if there is non-whitespace after x', ->
|
||||
revisedSelection = MarkdownEditingDescriptor.insertMultipleChoice('a\nx b\nc\nx \nd\n x e')
|
||||
expect(revisedSelection).toEqual('( ) a\n(x) b\n( ) c\n( ) x \n( ) d\n(x) e\n')
|
||||
it 'recognizes x as a selection if it is first non whitespace and has whitespace with other non-whitespace', ->
|
||||
revisedSelection = MarkdownEditingDescriptor.insertMultipleChoice(' x correct\n x \nex post facto\nb x c\nx c\nxxp')
|
||||
expect(revisedSelection).toEqual('(x) correct\n( ) x \n( ) ex post facto\n( ) b x c\n(x) c\n( ) xxp\n')
|
||||
it 'removes multiple newlines but not last one', ->
|
||||
revisedSelection = MarkdownEditingDescriptor.insertMultipleChoice('a\nx b\n\n\nc\n')
|
||||
expect(revisedSelection).toEqual('( ) a\n(x) b\n( ) c\n')
|
||||
|
||||
describe 'insertCheckboxChoice', ->
|
||||
# Note, shares code with insertMultipleChoice. Therefore only doing smoke test.
|
||||
it 'inserts the template if selection is empty', ->
|
||||
revisedSelection = MarkdownEditingDescriptor.insertCheckboxChoice('')
|
||||
expect(revisedSelection).toEqual(MarkdownEditingDescriptor.checkboxChoiceTemplate)
|
||||
it 'wraps existing text', ->
|
||||
revisedSelection = MarkdownEditingDescriptor.insertCheckboxChoice('foo\nbar')
|
||||
expect(revisedSelection).toEqual('[ ] foo\n[ ] bar\n')
|
||||
|
||||
describe 'insertStringInput', ->
|
||||
it 'inserts the template if selection is empty', ->
|
||||
revisedSelection = MarkdownEditingDescriptor.insertStringInput('')
|
||||
expect(revisedSelection).toEqual(MarkdownEditingDescriptor.stringInputTemplate)
|
||||
it 'wraps existing text', ->
|
||||
revisedSelection = MarkdownEditingDescriptor.insertStringInput('my text')
|
||||
expect(revisedSelection).toEqual('= my text')
|
||||
|
||||
describe 'insertNumberInput', ->
|
||||
it 'inserts the template if selection is empty', ->
|
||||
revisedSelection = MarkdownEditingDescriptor.insertNumberInput('')
|
||||
expect(revisedSelection).toEqual(MarkdownEditingDescriptor.numberInputTemplate)
|
||||
it 'wraps existing text', ->
|
||||
revisedSelection = MarkdownEditingDescriptor.insertNumberInput('my text')
|
||||
expect(revisedSelection).toEqual('= my text')
|
||||
|
||||
describe 'insertSelect', ->
|
||||
it 'inserts the template if selection is empty', ->
|
||||
revisedSelection = MarkdownEditingDescriptor.insertSelect('')
|
||||
expect(revisedSelection).toEqual(MarkdownEditingDescriptor.selectTemplate)
|
||||
it 'wraps existing text', ->
|
||||
revisedSelection = MarkdownEditingDescriptor.insertSelect('my text')
|
||||
expect(revisedSelection).toEqual('[[my text]]')
|
||||
|
||||
describe 'insertHeader', ->
|
||||
it 'inserts the template if selection is empty', ->
|
||||
revisedSelection = MarkdownEditingDescriptor.insertHeader('')
|
||||
expect(revisedSelection).toEqual(MarkdownEditingDescriptor.headerTemplate)
|
||||
it 'wraps existing text', ->
|
||||
revisedSelection = MarkdownEditingDescriptor.insertHeader('my text')
|
||||
expect(revisedSelection).toEqual('my text\n====\n')
|
||||
|
||||
describe 'insertExplanation', ->
|
||||
it 'inserts the template if selection is empty', ->
|
||||
revisedSelection = MarkdownEditingDescriptor.insertExplanation('')
|
||||
expect(revisedSelection).toEqual(MarkdownEditingDescriptor.explanationTemplate)
|
||||
it 'wraps existing text', ->
|
||||
revisedSelection = MarkdownEditingDescriptor.insertExplanation('my text')
|
||||
expect(revisedSelection).toEqual('[explanation]\nmy text\n[explanation]')
|
||||
|
||||
describe 'markdownToXml', ->
|
||||
it 'converts raw text to paragraph', ->
|
||||
data = MarkdownEditingDescriptor.markdownToXml('foo')
|
||||
expect(data).toXMLEqual('<problem>\n <p>foo</p>\n</problem>')
|
||||
# test default templates
|
||||
it 'converts numerical response to xml', ->
|
||||
data = MarkdownEditingDescriptor.markdownToXml("""A numerical response problem accepts a line of text input from the student, and evaluates the input for correctness based on its numerical value.
|
||||
|
||||
The answer is correct if it is within a specified numerical tolerance of the expected answer.
|
||||
|
||||
Enter the numerical value of Pi:
|
||||
= 3.14159 +- .02
|
||||
|
||||
Enter the approximate value of 502*9:
|
||||
= 502*9 +- 15%
|
||||
|
||||
Enter the number of fingers on a human hand:
|
||||
= 5
|
||||
|
||||
Range tolerance case
|
||||
= [6, 7]
|
||||
= (1, 2)
|
||||
|
||||
If first and last symbols are not brackets, or they are not closed, stringresponse will appear.
|
||||
= (7), 7
|
||||
= (1+2
|
||||
|
||||
[Explanation]
|
||||
Pi, or the the ratio between a circle's circumference to its diameter, is an irrational number known to extreme precision. It is value is approximately equal to 3.14.
|
||||
|
||||
Although you can get an exact value by typing 502*9 into a calculator, the result will be close to 500*10, or 5,000. The grader accepts any response within 15% of the true value, 4518, so that you can use any estimation technique that you like.
|
||||
|
||||
If you look at your hand, you can count that you have five fingers.
|
||||
[Explanation]
|
||||
""")
|
||||
expect(data).toXMLEqual("""<problem>
|
||||
<p>A numerical response problem accepts a line of text input from the student, and evaluates the input for correctness based on its numerical value.</p>
|
||||
<p>The answer is correct if it is within a specified numerical tolerance of the expected answer.</p>
|
||||
<p>Enter the numerical value of Pi:</p>
|
||||
<numericalresponse answer="3.14159">
|
||||
<responseparam type="tolerance" default=".02" />
|
||||
<formulaequationinput />
|
||||
</numericalresponse>
|
||||
<p>Enter the approximate value of 502*9:</p>
|
||||
<numericalresponse answer="502*9">
|
||||
<responseparam type="tolerance" default="15%" />
|
||||
<formulaequationinput />
|
||||
</numericalresponse>
|
||||
<p>Enter the number of fingers on a human hand:</p>
|
||||
<numericalresponse answer="5">
|
||||
<formulaequationinput />
|
||||
</numericalresponse>
|
||||
<p>Range tolerance case</p>
|
||||
<numericalresponse answer="[6, 7]">
|
||||
<formulaequationinput />
|
||||
</numericalresponse>
|
||||
<numericalresponse answer="(1, 2)">
|
||||
<formulaequationinput />
|
||||
</numericalresponse>
|
||||
<p>If first and last symbols are not brackets, or they are not closed, stringresponse will appear.</p>
|
||||
<stringresponse answer="(7), 7" type="ci" >
|
||||
<textline size="20"/>
|
||||
</stringresponse>
|
||||
<stringresponse answer="(1+2" type="ci" >
|
||||
<textline size="20"/>
|
||||
</stringresponse>
|
||||
<solution>
|
||||
<div class="detailed-solution">
|
||||
<p>Explanation</p>
|
||||
<p>Pi, or the the ratio between a circle's circumference to its diameter, is an irrational number known to extreme precision. It is value is approximately equal to 3.14.</p>
|
||||
<p>Although you can get an exact value by typing 502*9 into a calculator, the result will be close to 500*10, or 5,000. The grader accepts any response within 15% of the true value, 4518, so that you can use any estimation technique that you like.</p>
|
||||
<p>If you look at your hand, you can count that you have five fingers.</p>
|
||||
</div>
|
||||
</solution>
|
||||
</problem>""")
|
||||
it 'will convert 0 as a numerical response (instead of string response)', ->
|
||||
data = MarkdownEditingDescriptor.markdownToXml("""
|
||||
Enter 0 with a tolerance:
|
||||
= 0 +- .02
|
||||
""")
|
||||
expect(data).toXMLEqual("""<problem>
|
||||
<numericalresponse answer="0">
|
||||
<p>Enter 0 with a tolerance:</p>
|
||||
<responseparam type="tolerance" default=".02"/>
|
||||
<formulaequationinput/>
|
||||
</numericalresponse>
|
||||
|
||||
|
||||
</problem>""")
|
||||
it 'markup with additional answer does not break numerical response', ->
|
||||
data = MarkdownEditingDescriptor.markdownToXml("""
|
||||
Enter 1 with a tolerance:
|
||||
= 1 +- .02
|
||||
or= 2
|
||||
""")
|
||||
expect(data).toXMLEqual("""<problem>
|
||||
<numericalresponse answer="1">
|
||||
<p>Enter 1 with a tolerance:</p>
|
||||
<responseparam type="tolerance" default=".02"/>
|
||||
<additional_answer answer="2"/>
|
||||
<formulaequationinput/>
|
||||
</numericalresponse>
|
||||
|
||||
</problem>"""
|
||||
)
|
||||
it 'markup for numerical with multiple additional answers renders correctly', ->
|
||||
data = MarkdownEditingDescriptor.markdownToXml("""
|
||||
Enter 1 with a tolerance:
|
||||
= 1 +- .02
|
||||
or= 2
|
||||
or= 3
|
||||
""")
|
||||
expect(data).toXMLEqual("""<problem>
|
||||
<numericalresponse answer="1">
|
||||
<p>Enter 1 with a tolerance:</p>
|
||||
<responseparam type="tolerance" default=".02"/>
|
||||
<additional_answer answer="2"/>
|
||||
<additional_answer answer="3"/>
|
||||
<formulaequationinput/>
|
||||
</numericalresponse>
|
||||
|
||||
</problem>"""
|
||||
)
|
||||
it 'Do not render ranged/tolerance/alphabetical additional answers for numerical response', ->
|
||||
data = MarkdownEditingDescriptor.markdownToXml("""
|
||||
Enter 1 with a tolerance:
|
||||
= 1 +- .02
|
||||
or= 2
|
||||
or= 3 +- 0.1
|
||||
or= [4,6]
|
||||
or= ABC
|
||||
or= 7
|
||||
""")
|
||||
expect(data).toXMLEqual("""<problem>
|
||||
<numericalresponse answer="1">
|
||||
<p>Enter 1 with a tolerance:</p>
|
||||
<responseparam type="tolerance" default=".02"/>
|
||||
<additional_answer answer="2"/>
|
||||
<additional_answer answer="7"/>
|
||||
<formulaequationinput/>
|
||||
</numericalresponse>
|
||||
|
||||
</problem>"""
|
||||
)
|
||||
it 'markup with feedback renders correctly in additional answer for numerical response', ->
|
||||
data = MarkdownEditingDescriptor.markdownToXml("""
|
||||
Enter 1 with a tolerance:
|
||||
= 100 +- .02 {{ main feedback }}
|
||||
or= 10 {{ additional feedback }}
|
||||
""")
|
||||
expect(data).toXMLEqual("""<problem>
|
||||
<numericalresponse answer="100">
|
||||
<p>Enter 1 with a tolerance:</p>
|
||||
<responseparam type="tolerance" default=".02"/>
|
||||
<additional_answer answer="10">
|
||||
<correcthint>additional feedback</correcthint>
|
||||
</additional_answer>
|
||||
<formulaequationinput/>
|
||||
<correcthint>main feedback</correcthint>
|
||||
</numericalresponse>
|
||||
|
||||
</problem>"""
|
||||
)
|
||||
it 'converts multiple choice to xml', ->
|
||||
data = MarkdownEditingDescriptor.markdownToXml("""A multiple choice problem presents radio buttons for student input. Students can only select a single option presented. Multiple Choice questions have been the subject of many areas of research due to the early invention and adoption of bubble sheets.
|
||||
|
||||
One of the main elements that goes into a good multiple choice question is the existence of good distractors. That is, each of the alternate responses presented to the student should be the result of a plausible mistake that a student might make.
|
||||
|
||||
>>What Apple device competed with the portable CD player?<<
|
||||
( ) The iPad
|
||||
( ) Napster
|
||||
(x) The iPod
|
||||
( ) The vegetable peeler
|
||||
( ) Android
|
||||
( ) The Beatles
|
||||
|
||||
[Explanation]
|
||||
The release of the iPod allowed consumers to carry their entire music library with them in a format that did not rely on fragile and energy-intensive spinning disks.
|
||||
[Explanation]
|
||||
""")
|
||||
expect(data).toXMLEqual("""<problem>
|
||||
<multiplechoiceresponse>
|
||||
<p>A multiple choice problem presents radio buttons for student input. Students can only select a single option presented. Multiple Choice questions have been the subject of many areas of research due to the early invention and adoption of bubble sheets.</p>
|
||||
<p>One of the main elements that goes into a good multiple choice question is the existence of good distractors. That is, each of the alternate responses presented to the student should be the result of a plausible mistake that a student might make.</p>
|
||||
<label>What Apple device competed with the portable CD player?</label>
|
||||
<choicegroup type="MultipleChoice">
|
||||
<choice correct="false">The iPad</choice>
|
||||
<choice correct="false">Napster</choice>
|
||||
<choice correct="true">The iPod</choice>
|
||||
<choice correct="false">The vegetable peeler</choice>
|
||||
<choice correct="false">Android</choice>
|
||||
<choice correct="false">The Beatles</choice>
|
||||
</choicegroup>
|
||||
<solution>
|
||||
<div class="detailed-solution">
|
||||
<p>Explanation</p>
|
||||
<p>The release of the iPod allowed consumers to carry their entire music library with them in a format that did not rely on fragile and energy-intensive spinning disks.</p>
|
||||
</div>
|
||||
</solution>
|
||||
</multiplechoiceresponse>
|
||||
</problem>""")
|
||||
it 'converts multiple choice shuffle to xml', ->
|
||||
data = MarkdownEditingDescriptor.markdownToXml("""A multiple choice problem presents radio buttons for student input. Students can only select a single option presented. Multiple Choice questions have been the subject of many areas of research due to the early invention and adoption of bubble sheets.
|
||||
|
||||
One of the main elements that goes into a good multiple choice question is the existence of good distractors. That is, each of the alternate responses presented to the student should be the result of a plausible mistake that a student might make.
|
||||
|
||||
What Apple device competed with the portable CD player?
|
||||
(!x@) The iPad
|
||||
(@) Napster
|
||||
() The iPod
|
||||
( ) The vegetable peeler
|
||||
( ) Android
|
||||
(@) The Beatles
|
||||
|
||||
[Explanation]
|
||||
The release of the iPod allowed consumers to carry their entire music library with them in a format that did not rely on fragile and energy-intensive spinning disks.
|
||||
[Explanation]
|
||||
""")
|
||||
expect(data).toXMLEqual("""
|
||||
<problem>
|
||||
<multiplechoiceresponse>
|
||||
<p>A multiple choice problem presents radio buttons for student input. Students can only select a single option presented. Multiple Choice questions have been the subject of many areas of research due to the early invention and adoption of bubble sheets.</p>
|
||||
<p>One of the main elements that goes into a good multiple choice question is the existence of good distractors. That is, each of the alternate responses presented to the student should be the result of a plausible mistake that a student might make.</p>
|
||||
<p>What Apple device competed with the portable CD player?</p>
|
||||
<choicegroup type="MultipleChoice" shuffle="true">
|
||||
<choice correct="true" fixed="true">The iPad</choice>
|
||||
<choice correct="false" fixed="true">Napster</choice>
|
||||
<choice correct="false">The iPod</choice>
|
||||
<choice correct="false">The vegetable peeler</choice>
|
||||
<choice correct="false">Android</choice>
|
||||
<choice correct="false" fixed="true">The Beatles</choice>
|
||||
</choicegroup>
|
||||
<solution>
|
||||
<div class="detailed-solution">
|
||||
<p>Explanation</p>
|
||||
<p>The release of the iPod allowed consumers to carry their entire music library with them in a format that did not rely on fragile and energy-intensive spinning disks.</p>
|
||||
</div>
|
||||
</solution>
|
||||
</multiplechoiceresponse>
|
||||
</problem>""")
|
||||
|
||||
it 'converts a series of multiplechoice to xml', ->
|
||||
data = MarkdownEditingDescriptor.markdownToXml("""bleh
|
||||
(!x) a
|
||||
() b
|
||||
() c
|
||||
yatta
|
||||
( ) x
|
||||
( ) y
|
||||
(x) z
|
||||
testa
|
||||
(!) i
|
||||
( ) ii
|
||||
(x) iii
|
||||
[Explanation]
|
||||
When the student is ready, the explanation appears.
|
||||
[Explanation]
|
||||
""")
|
||||
expect(data).toXMLEqual("""
|
||||
<problem>
|
||||
<p>bleh</p>
|
||||
<multiplechoiceresponse>
|
||||
<choicegroup type="MultipleChoice" shuffle="true">
|
||||
<choice correct="true">a</choice>
|
||||
<choice correct="false">b</choice>
|
||||
<choice correct="false">c</choice>
|
||||
</choicegroup>
|
||||
</multiplechoiceresponse>
|
||||
<p>yatta</p>
|
||||
<multiplechoiceresponse>
|
||||
<choicegroup type="MultipleChoice">
|
||||
<choice correct="false">x</choice>
|
||||
<choice correct="false">y</choice>
|
||||
<choice correct="true">z</choice>
|
||||
</choicegroup>
|
||||
</multiplechoiceresponse>
|
||||
<p>testa</p>
|
||||
<multiplechoiceresponse>
|
||||
<choicegroup type="MultipleChoice" shuffle="true">
|
||||
<choice correct="false">i</choice>
|
||||
<choice correct="false">ii</choice>
|
||||
<choice correct="true">iii</choice>
|
||||
</choicegroup>
|
||||
</multiplechoiceresponse>
|
||||
<solution>
|
||||
<div class="detailed-solution">
|
||||
<p>Explanation</p>
|
||||
<p>When the student is ready, the explanation appears.</p>
|
||||
</div>
|
||||
</solution>
|
||||
</problem>""")
|
||||
|
||||
it 'converts OptionResponse to xml', ->
|
||||
data = MarkdownEditingDescriptor.markdownToXml("""OptionResponse gives a limited set of options for students to respond with, and presents those options in a format that encourages them to search for a specific answer rather than being immediately presented with options from which to recognize the correct answer.
|
||||
|
||||
The answer options and the identification of the correct answer is defined in the <b>optioninput</b> tag.
|
||||
|
||||
Translation between Option Response and __________ is extremely straightforward:
|
||||
[[(Multiple Choice), String Response, Numerical Response, External Response, Image Response]]
|
||||
|
||||
[Explanation]
|
||||
Multiple Choice also allows students to select from a variety of pre-written responses, although the format makes it easier for students to read very long response options. Optionresponse also differs slightly because students are more likely to think of an answer and then search for it rather than relying purely on recognition to answer the question.
|
||||
[Explanation]
|
||||
""")
|
||||
expect(data).toXMLEqual("""
|
||||
<problem>
|
||||
<optionresponse>
|
||||
<p>OptionResponse gives a limited set of options for students to respond with, and presents those options in a format that encourages them to search for a specific answer rather than being immediately presented with options from which to recognize the correct answer.</p>
|
||||
<p>The answer options and the identification of the correct answer is defined in the <b>optioninput</b> tag.</p>
|
||||
<p>Translation between Option Response and __________ is extremely straightforward:</p>
|
||||
<optioninput options="('Multiple Choice','String Response','Numerical Response','External Response','Image Response')" correct="Multiple Choice"/>
|
||||
<solution>
|
||||
<div class="detailed-solution">
|
||||
<p>Explanation</p>
|
||||
<p>Multiple Choice also allows students to select from a variety of pre-written responses, although the format makes it easier for students to read very long response options. Optionresponse also differs slightly because students are more likely to think of an answer and then search for it rather than relying purely on recognition to answer the question.</p>
|
||||
</div>
|
||||
</solution>
|
||||
</optionresponse>
|
||||
</problem>""")
|
||||
it 'converts StringResponse to xml', ->
|
||||
data = MarkdownEditingDescriptor.markdownToXml("""A string response problem accepts a line of text input from the student, and evaluates the input for correctness based on an expected answer within each input box.
|
||||
|
||||
The answer is correct if it matches every character of the expected answer. This can be a problem with international spelling, dates, or anything where the format of the answer is not clear.
|
||||
|
||||
Which US state has Lansing as its capital?
|
||||
= Michigan
|
||||
|
||||
[Explanation]
|
||||
Lansing is the capital of Michigan, although it is not Michgan's largest city, or even the seat of the county in which it resides.
|
||||
[Explanation]
|
||||
""")
|
||||
expect(data).toXMLEqual("""
|
||||
<problem>
|
||||
<stringresponse answer="Michigan" type="ci">
|
||||
<p>A string response problem accepts a line of text input from the student, and evaluates the input for correctness based on an expected answer within each input box.</p>
|
||||
<p>The answer is correct if it matches every character of the expected answer. This can be a problem with international spelling, dates, or anything where the format of the answer is not clear.</p>
|
||||
<p>Which US state has Lansing as its capital?</p>
|
||||
<textline size="20"/>
|
||||
<solution>
|
||||
<div class="detailed-solution">
|
||||
<p>Explanation</p>
|
||||
<p>Lansing is the capital of Michigan, although it is not Michgan's largest city, or even the seat of the county in which it resides.</p>
|
||||
</div>
|
||||
</solution>
|
||||
</stringresponse>
|
||||
</problem>""")
|
||||
it 'converts StringResponse with regular expression to xml', ->
|
||||
data = MarkdownEditingDescriptor.markdownToXml("""Who lead the civil right movement in the United States of America?
|
||||
= | \w*\.?\s*Luther King\s*.*
|
||||
|
||||
[Explanation]
|
||||
Test Explanation.
|
||||
[Explanation]
|
||||
""")
|
||||
expect(data).toXMLEqual("""
|
||||
<problem>
|
||||
<stringresponse answer="w*.?s*Luther Kings*.*" type="ci regexp">
|
||||
<p>Who lead the civil right movement in the United States of America?</p>
|
||||
<textline size="20"/>
|
||||
<solution>
|
||||
<div class="detailed-solution">
|
||||
<p>Explanation</p>
|
||||
<p>Test Explanation.</p>
|
||||
</div>
|
||||
</solution>
|
||||
</stringresponse>
|
||||
</problem>""")
|
||||
it 'converts StringResponse with multiple answers to xml', ->
|
||||
data = MarkdownEditingDescriptor.markdownToXml("""Who lead the civil right movement in the United States of America?
|
||||
= Dr. Martin Luther King Jr.
|
||||
or= Doctor Martin Luther King Junior
|
||||
or= Martin Luther King
|
||||
or= Martin Luther King Junior
|
||||
|
||||
[Explanation]
|
||||
Test Explanation.
|
||||
[Explanation]
|
||||
""")
|
||||
expect(data).toXMLEqual("""
|
||||
<problem>
|
||||
<stringresponse answer="Dr. Martin Luther King Jr." type="ci">
|
||||
<p>Who lead the civil right movement in the United States of America?</p>
|
||||
<additional_answer answer="Doctor Martin Luther King Junior"/>
|
||||
<additional_answer answer="Martin Luther King"/>
|
||||
<additional_answer answer="Martin Luther King Junior"/>
|
||||
<textline size="20"/>
|
||||
<solution>
|
||||
<div class="detailed-solution">
|
||||
<p>Explanation</p>
|
||||
<p>Test Explanation.</p>
|
||||
</div>
|
||||
</solution>
|
||||
</stringresponse>
|
||||
</problem>""")
|
||||
it 'converts StringResponse with multiple answers and regular expressions to xml', ->
|
||||
data = MarkdownEditingDescriptor.markdownToXml("""Write a number from 1 to 4.
|
||||
=| ^One$
|
||||
or= two
|
||||
or= ^thre+
|
||||
or= ^4|Four$
|
||||
|
||||
[Explanation]
|
||||
Test Explanation.
|
||||
[Explanation]
|
||||
""")
|
||||
expect(data).toXMLEqual("""
|
||||
<problem>
|
||||
<stringresponse answer="^One$" type="ci regexp">
|
||||
<p>Write a number from 1 to 4.</p>
|
||||
<additional_answer answer="two"/>
|
||||
<additional_answer answer="^thre+"/>
|
||||
<additional_answer answer="^4|Four$"/>
|
||||
<textline size="20"/>
|
||||
<solution>
|
||||
<div class="detailed-solution">
|
||||
<p>Explanation</p>
|
||||
<p>Test Explanation.</p>
|
||||
</div>
|
||||
</solution>
|
||||
</stringresponse>
|
||||
</problem>""")
|
||||
# test labels
|
||||
it 'converts markdown labels to label attributes', ->
|
||||
data = MarkdownEditingDescriptor.markdownToXml(""">>Who lead the civil right movement in the United States of America?<<
|
||||
= | \w*\.?\s*Luther King\s*.*
|
||||
|
||||
[Explanation]
|
||||
Test Explanation.
|
||||
[Explanation]
|
||||
""")
|
||||
expect(data).toXMLEqual("""
|
||||
<problem>
|
||||
<stringresponse answer="w*.?s*Luther Kings*.*" type="ci regexp">
|
||||
<label>Who lead the civil right movement in the United States of America?</label>
|
||||
<textline size="20"/>
|
||||
<solution>
|
||||
<div class="detailed-solution">
|
||||
<p>Explanation</p>
|
||||
<p>Test Explanation.</p>
|
||||
</div>
|
||||
</solution>
|
||||
</stringresponse>
|
||||
</problem>""")
|
||||
it 'handles multiple questions with labels', ->
|
||||
data = MarkdownEditingDescriptor.markdownToXml("""
|
||||
France is a country in Europe.
|
||||
|
||||
>>What is the capital of France?<<
|
||||
= Paris
|
||||
|
||||
Germany is a country in Europe, too.
|
||||
|
||||
>>What is the capital of Germany?<<
|
||||
( ) Bonn
|
||||
( ) Hamburg
|
||||
(x) Berlin
|
||||
( ) Donut
|
||||
""")
|
||||
expect(data).toXMLEqual("""
|
||||
<problem>
|
||||
<p>France is a country in Europe.</p>
|
||||
|
||||
<label>What is the capital of France?</label>
|
||||
<stringresponse answer="Paris" type="ci" >
|
||||
<textline size="20"/>
|
||||
</stringresponse>
|
||||
|
||||
<p>Germany is a country in Europe, too.</p>
|
||||
|
||||
<label>What is the capital of Germany?</label>
|
||||
<multiplechoiceresponse>
|
||||
<choicegroup type="MultipleChoice">
|
||||
<choice correct="false">Bonn</choice>
|
||||
<choice correct="false">Hamburg</choice>
|
||||
<choice correct="true">Berlin</choice>
|
||||
<choice correct="false">Donut</choice>
|
||||
</choicegroup>
|
||||
</multiplechoiceresponse>
|
||||
</problem>""")
|
||||
it 'tests multiple questions with only one label', ->
|
||||
data = MarkdownEditingDescriptor.markdownToXml("""
|
||||
France is a country in Europe.
|
||||
|
||||
>>What is the capital of France?<<
|
||||
= Paris
|
||||
|
||||
Germany is a country in Europe, too.
|
||||
|
||||
What is the capital of Germany?
|
||||
( ) Bonn
|
||||
( ) Hamburg
|
||||
(x) Berlin
|
||||
( ) Donut
|
||||
""")
|
||||
expect(data).toXMLEqual("""
|
||||
<problem>
|
||||
<p>France is a country in Europe.</p>
|
||||
|
||||
<label>What is the capital of France?</label>
|
||||
<stringresponse answer="Paris" type="ci" >
|
||||
<textline size="20"/>
|
||||
</stringresponse>
|
||||
|
||||
<p>Germany is a country in Europe, too.</p>
|
||||
|
||||
<p>What is the capital of Germany?</p>
|
||||
<multiplechoiceresponse>
|
||||
<choicegroup type="MultipleChoice">
|
||||
<choice correct="false">Bonn</choice>
|
||||
<choice correct="false">Hamburg</choice>
|
||||
<choice correct="true">Berlin</choice>
|
||||
<choice correct="false">Donut</choice>
|
||||
</choicegroup>
|
||||
</multiplechoiceresponse>
|
||||
</problem>""")
|
||||
|
||||
it 'adds labels to formulae', ->
|
||||
data = MarkdownEditingDescriptor.markdownToXml("""
|
||||
>>Enter the numerical value of Pi:<<
|
||||
= 3.14159 +- .02
|
||||
""")
|
||||
expect(data).toXMLEqual("""<problem>
|
||||
<numericalresponse answer="3.14159">
|
||||
<label>Enter the numerical value of Pi:</label>
|
||||
<responseparam type="tolerance" default=".02"/>
|
||||
<formulaequationinput/>
|
||||
</numericalresponse>
|
||||
|
||||
|
||||
</problem>""")
|
||||
|
||||
# test oddities
|
||||
it 'converts headers and oddities to xml', ->
|
||||
data = MarkdownEditingDescriptor.markdownToXml("""Not a header
|
||||
A header
|
||||
==============
|
||||
|
||||
Multiple choice w/ parentheticals
|
||||
( ) option (with parens)
|
||||
( ) xd option (x)
|
||||
()) parentheses inside
|
||||
() no space b4 close paren
|
||||
|
||||
Choice checks
|
||||
[ ] option1 [x]
|
||||
[x] correct
|
||||
[x] redundant
|
||||
[(] distractor
|
||||
[] no space
|
||||
|
||||
Option with multiple correct ones
|
||||
[[one option, (correct one), (should not be correct)]]
|
||||
|
||||
Option with embedded parens
|
||||
[[My (heart), another, (correct)]]
|
||||
|
||||
What happens w/ empty correct options?
|
||||
[[()]]
|
||||
|
||||
[Explanation]see[/expLanation]
|
||||
|
||||
[explanation]
|
||||
orphaned start
|
||||
|
||||
No p tags in the below
|
||||
<script type='javascript'>
|
||||
var two = 2;
|
||||
|
||||
console.log(two * 2);
|
||||
</script>
|
||||
|
||||
But in this there should be
|
||||
<div>
|
||||
Great ideas require offsetting.
|
||||
|
||||
bad tests require drivel
|
||||
</div>
|
||||
|
||||
[code]
|
||||
Code should be nicely monospaced.
|
||||
[/code]
|
||||
""")
|
||||
expect(data).toXMLEqual("""
|
||||
<problem>
|
||||
<p>Not a header</p>
|
||||
<h3 class="hd hd-2 problem-header">A header</h3>
|
||||
<p>Multiple choice w/ parentheticals</p>
|
||||
<multiplechoiceresponse>
|
||||
<choicegroup type="MultipleChoice">
|
||||
<choice correct="false">option (with parens)</choice>
|
||||
<choice correct="false">xd option (x)</choice>
|
||||
<choice correct="false">parentheses inside</choice>
|
||||
<choice correct="false">no space b4 close paren</choice>
|
||||
</choicegroup>
|
||||
</multiplechoiceresponse>
|
||||
<p>Choice checks</p>
|
||||
<choiceresponse>
|
||||
<checkboxgroup>
|
||||
<choice correct="false">option1 [x]</choice>
|
||||
<choice correct="true">correct</choice>
|
||||
<choice correct="true">redundant</choice>
|
||||
<choice correct="false">distractor</choice>
|
||||
<choice correct="false">no space</choice>
|
||||
</checkboxgroup>
|
||||
</choiceresponse>
|
||||
<p>Option with multiple correct ones</p>
|
||||
<optionresponse>
|
||||
<optioninput options="('one option','correct one','should not be correct')" correct="correct one"></optioninput>
|
||||
</optionresponse>
|
||||
<p>Option with embedded parens</p>
|
||||
<optionresponse>
|
||||
<optioninput options="('My (heart)','another','correct')" correct="correct"></optioninput>
|
||||
</optionresponse>
|
||||
<p>What happens w/ empty correct options?</p>
|
||||
<optionresponse>
|
||||
<optioninput options="('')" correct=""></optioninput>
|
||||
</optionresponse>
|
||||
<solution>
|
||||
<div class="detailed-solution">
|
||||
<p>Explanation</p>
|
||||
<p>see</p>
|
||||
</div>
|
||||
</solution>
|
||||
<p>[explanation]</p>
|
||||
<p>orphaned start</p>
|
||||
<p>No p tags in the below</p>
|
||||
<script type='javascript'>
|
||||
var two = 2;
|
||||
|
||||
console.log(two * 2);
|
||||
</script>
|
||||
<p>But in this there should be</p>
|
||||
<div>
|
||||
<p>Great ideas require offsetting.</p>
|
||||
<p>bad tests require drivel</p>
|
||||
</div>
|
||||
<pre>
|
||||
<code>Code should be nicely monospaced.
|
||||
</code>
|
||||
</pre>
|
||||
</problem>""")
|
||||
|
||||
it 'can separate responsetypes based on ---', ->
|
||||
data = MarkdownEditingDescriptor.markdownToXml("""
|
||||
Multiple choice problems allow learners to select only one option. Learners can see all the options along with the problem text.
|
||||
|
||||
>>Which of the following countries has the largest population?<<
|
||||
( ) Brazil {{ timely feedback -- explain why an almost correct answer is wrong }}
|
||||
( ) Germany
|
||||
(x) Indonesia
|
||||
( ) Russia
|
||||
|
||||
[explanation]
|
||||
According to September 2014 estimates:
|
||||
The population of Indonesia is approximately 250 million.
|
||||
The population of Brazil is approximately 200 million.
|
||||
The population of Russia is approximately 146 million.
|
||||
The population of Germany is approximately 81 million.
|
||||
[explanation]
|
||||
|
||||
---
|
||||
|
||||
Checkbox problems allow learners to select multiple options. Learners can see all the options along with the problem text.
|
||||
|
||||
>>The following languages are in the Indo-European family:<<
|
||||
[x] Urdu
|
||||
[ ] Finnish
|
||||
[x] Marathi
|
||||
[x] French
|
||||
[ ] Hungarian
|
||||
|
||||
Note: Make sure you select all of the correct options—there may be more than one!
|
||||
|
||||
[explanation]
|
||||
Urdu, Marathi, and French are all Indo-European languages, while Finnish and Hungarian are in the Uralic family.
|
||||
[explanation]
|
||||
|
||||
""")
|
||||
expect(data).toXMLEqual("""
|
||||
<problem>
|
||||
<multiplechoiceresponse>
|
||||
<p>Multiple choice problems allow learners to select only one option. Learners can see all the options along with the problem text.</p>
|
||||
<label>Which of the following countries has the largest population?</label>
|
||||
<choicegroup type="MultipleChoice">
|
||||
<choice correct="false">Brazil <choicehint>timely feedback -- explain why an almost correct answer is wrong</choicehint>
|
||||
</choice>
|
||||
<choice correct="false">Germany</choice>
|
||||
<choice correct="true">Indonesia</choice>
|
||||
<choice correct="false">Russia</choice>
|
||||
</choicegroup>
|
||||
<solution>
|
||||
<div class="detailed-solution">
|
||||
<p>Explanation</p>
|
||||
<p>According to September 2014 estimates:</p>
|
||||
<p>The population of Indonesia is approximately 250 million.</p>
|
||||
<p>The population of Brazil is approximately 200 million.</p>
|
||||
<p>The population of Russia is approximately 146 million.</p>
|
||||
<p>The population of Germany is approximately 81 million.</p>
|
||||
</div>
|
||||
</solution>
|
||||
</multiplechoiceresponse>
|
||||
|
||||
<choiceresponse>
|
||||
<p>Checkbox problems allow learners to select multiple options. Learners can see all the options along with the problem text.</p>
|
||||
<label>The following languages are in the Indo-European family:</label>
|
||||
<checkboxgroup>
|
||||
<choice correct="true">Urdu</choice>
|
||||
<choice correct="false">Finnish</choice>
|
||||
<choice correct="true">Marathi</choice>
|
||||
<choice correct="true">French</choice>
|
||||
<choice correct="false">Hungarian</choice>
|
||||
</checkboxgroup>
|
||||
<p>Note: Make sure you select all of the correct options—there may be more than one!</p>
|
||||
<solution>
|
||||
<div class="detailed-solution">
|
||||
<p>Explanation</p>
|
||||
<p>Urdu, Marathi, and French are all Indo-European languages, while Finnish and Hungarian are in the Uralic family.</p>
|
||||
</div>
|
||||
</solution>
|
||||
</choiceresponse>
|
||||
</problem>
|
||||
""")
|
||||
|
||||
it 'can separate other things based on ---', ->
|
||||
data = MarkdownEditingDescriptor.markdownToXml("""
|
||||
Multiple choice problems allow learners to select only one option. Learners can see all the options along with the problem text.
|
||||
|
||||
---
|
||||
|
||||
>>Which of the following countries has the largest population?<<
|
||||
( ) Brazil {{ timely feedback -- explain why an almost correct answer is wrong }}
|
||||
( ) Germany
|
||||
(x) Indonesia
|
||||
( ) Russia
|
||||
|
||||
[explanation]
|
||||
According to September 2014 estimates:
|
||||
The population of Indonesia is approximately 250 million.
|
||||
The population of Brazil is approximately 200 million.
|
||||
The population of Russia is approximately 146 million.
|
||||
The population of Germany is approximately 81 million.
|
||||
[explanation]
|
||||
""")
|
||||
expect(data).toXMLEqual("""
|
||||
<problem>
|
||||
<p>Multiple choice problems allow learners to select only one option. Learners can see all the options along with the problem text.</p>
|
||||
|
||||
<multiplechoiceresponse>
|
||||
<label>Which of the following countries has the largest population?</label>
|
||||
<choicegroup type="MultipleChoice">
|
||||
<choice correct="false">Brazil <choicehint>timely feedback -- explain why an almost correct answer is wrong</choicehint>
|
||||
</choice>
|
||||
<choice correct="false">Germany</choice>
|
||||
<choice correct="true">Indonesia</choice>
|
||||
<choice correct="false">Russia</choice>
|
||||
</choicegroup>
|
||||
<solution>
|
||||
<div class="detailed-solution">
|
||||
<p>Explanation</p>
|
||||
<p>According to September 2014 estimates:</p>
|
||||
<p>The population of Indonesia is approximately 250 million.</p>
|
||||
<p>The population of Brazil is approximately 200 million.</p>
|
||||
<p>The population of Russia is approximately 146 million.</p>
|
||||
<p>The population of Germany is approximately 81 million.</p>
|
||||
</div>
|
||||
</solution>
|
||||
</multiplechoiceresponse>
|
||||
</problem>
|
||||
""")
|
||||
|
||||
it 'can do separation if spaces are present around ---', ->
|
||||
data = MarkdownEditingDescriptor.markdownToXml("""
|
||||
>>The following languages are in the Indo-European family:||There are three correct choices.<<
|
||||
[x] Urdu
|
||||
[ ] Finnish
|
||||
[x] Marathi
|
||||
[x] French
|
||||
[ ] Hungarian
|
||||
|
||||
---
|
||||
|
||||
>>Which of the following countries has the largest population?||You have only choice.<<
|
||||
( ) Brazil {{ timely feedback -- explain why an almost correct answer is wrong }}
|
||||
( ) Germany
|
||||
(x) Indonesia
|
||||
( ) Russia
|
||||
""")
|
||||
expect(data).toXMLEqual("""
|
||||
<problem>
|
||||
<choiceresponse>
|
||||
<label>The following languages are in the Indo-European family:</label>
|
||||
<description>There are three correct choices.</description>
|
||||
<checkboxgroup>
|
||||
<choice correct="true">Urdu</choice>
|
||||
<choice correct="false">Finnish</choice>
|
||||
<choice correct="true">Marathi</choice>
|
||||
<choice correct="true">French</choice>
|
||||
<choice correct="false">Hungarian</choice>
|
||||
</checkboxgroup>
|
||||
</choiceresponse>
|
||||
|
||||
<multiplechoiceresponse>
|
||||
<label>Which of the following countries has the largest population?</label>
|
||||
<description>You have only choice.</description>
|
||||
<choicegroup type="MultipleChoice">
|
||||
<choice correct="false">Brazil
|
||||
<choicehint>timely feedback -- explain why an almost correct answer is wrong</choicehint>
|
||||
</choice>
|
||||
<choice correct="false">Germany</choice>
|
||||
<choice correct="true">Indonesia</choice>
|
||||
<choice correct="false">Russia</choice>
|
||||
</choicegroup>
|
||||
</multiplechoiceresponse>
|
||||
</problem>
|
||||
""")
|
||||
|
||||
it 'can extract question description', ->
|
||||
data = MarkdownEditingDescriptor.markdownToXml("""
|
||||
>>The following languages are in the Indo-European family:||Choose wisely.<<
|
||||
[x] Urdu
|
||||
[ ] Finnish
|
||||
[x] Marathi
|
||||
[x] French
|
||||
[ ] Hungarian
|
||||
""")
|
||||
expect(data).toXMLEqual("""
|
||||
<problem>
|
||||
<choiceresponse>
|
||||
<label>The following languages are in the Indo-European family:</label>
|
||||
<description>Choose wisely.</description>
|
||||
<checkboxgroup>
|
||||
<choice correct="true">Urdu</choice>
|
||||
<choice correct="false">Finnish</choice>
|
||||
<choice correct="true">Marathi</choice>
|
||||
<choice correct="true">French</choice>
|
||||
<choice correct="false">Hungarian</choice>
|
||||
</checkboxgroup>
|
||||
</choiceresponse>
|
||||
</problem>
|
||||
""")
|
||||
|
||||
it 'can handle question and description spanned across multiple lines', ->
|
||||
data = MarkdownEditingDescriptor.markdownToXml("""
|
||||
>>The following languages
|
||||
are in the
|
||||
Indo-European family:
|
||||
||
|
||||
first second
|
||||
third
|
||||
<<
|
||||
[x] Urdu
|
||||
[ ] Finnish
|
||||
[x] Marathi
|
||||
""")
|
||||
expect(data).toXMLEqual("""
|
||||
<problem>
|
||||
<choiceresponse>
|
||||
<label>The following languages are in the Indo-European family:</label>
|
||||
<description>first second third</description>
|
||||
<checkboxgroup>
|
||||
<choice correct="true">Urdu</choice>
|
||||
<choice correct="false">Finnish</choice>
|
||||
<choice correct="true">Marathi</choice>
|
||||
</checkboxgroup>
|
||||
</choiceresponse>
|
||||
</problem>
|
||||
""")
|
||||
|
||||
it 'will not add empty description', ->
|
||||
data = MarkdownEditingDescriptor.markdownToXml("""
|
||||
>>The following languages are in the Indo-European family:||<<
|
||||
[x] Urdu
|
||||
[ ] Finnish
|
||||
""")
|
||||
expect(data).toXMLEqual("""
|
||||
<problem>
|
||||
<choiceresponse>
|
||||
<label>The following languages are in the Indo-European family:</label>
|
||||
<checkboxgroup>
|
||||
<choice correct="true">Urdu</choice>
|
||||
<choice correct="false">Finnish</choice>
|
||||
</checkboxgroup>
|
||||
</choiceresponse>
|
||||
</problem>
|
||||
""")
|
||||
1043
common/lib/xmodule/xmodule/js/spec/problem/edit_spec.js
Normal file
1043
common/lib/xmodule/xmodule/js/spec/problem/edit_spec.js
Normal file
@@ -0,0 +1,1043 @@
|
||||
describe('MarkdownEditingDescriptor', function() {
|
||||
describe('save stores the correct data', function() {
|
||||
it('saves markdown from markdown editor', function() {
|
||||
loadFixtures('problem-with-markdown.html');
|
||||
this.descriptor = new MarkdownEditingDescriptor($('.problem-editor'));
|
||||
const saveResult = this.descriptor.save();
|
||||
expect(saveResult.metadata.markdown).toEqual('markdown');
|
||||
expect(saveResult.data).toXMLEqual('<problem>\n <p>markdown</p>\n</problem>');
|
||||
});
|
||||
it('clears markdown when xml editor is selected', function() {
|
||||
loadFixtures('problem-with-markdown.html');
|
||||
this.descriptor = new MarkdownEditingDescriptor($('.problem-editor'));
|
||||
this.descriptor.createXMLEditor('replace with markdown');
|
||||
const saveResult = this.descriptor.save();
|
||||
expect(saveResult.nullout).toEqual(['markdown']);
|
||||
expect(saveResult.data).toEqual('replace with markdown');
|
||||
});
|
||||
it('saves xml from the xml editor', function() {
|
||||
loadFixtures('problem-without-markdown.html');
|
||||
this.descriptor = new MarkdownEditingDescriptor($('.problem-editor'));
|
||||
const saveResult = this.descriptor.save();
|
||||
expect(saveResult.nullout).toEqual(['markdown']);
|
||||
expect(saveResult.data).toEqual('xml only');
|
||||
});
|
||||
});
|
||||
|
||||
describe('advanced editor opens correctly', () =>
|
||||
it('click on advanced editor should work', function() {
|
||||
loadFixtures('problem-with-markdown.html');
|
||||
this.descriptor = new MarkdownEditingDescriptor($('.problem-editor'));
|
||||
spyOn(this.descriptor, 'confirmConversionToXml').and.returnValue(true);
|
||||
expect(this.descriptor.confirmConversionToXml).not.toHaveBeenCalled();
|
||||
const e = jasmine.createSpyObj('e', [ 'preventDefault' ]);
|
||||
this.descriptor.onShowXMLButton(e);
|
||||
expect(e.preventDefault).toHaveBeenCalled();
|
||||
expect(this.descriptor.confirmConversionToXml).toHaveBeenCalled();
|
||||
expect($('.editor-bar').length).toEqual(0);
|
||||
})
|
||||
);
|
||||
|
||||
describe('insertMultipleChoice', function() {
|
||||
it('inserts the template if selection is empty', function() {
|
||||
const revisedSelection = MarkdownEditingDescriptor.insertMultipleChoice('');
|
||||
expect(revisedSelection).toEqual(MarkdownEditingDescriptor.multipleChoiceTemplate);
|
||||
});
|
||||
it('wraps existing text', function() {
|
||||
const revisedSelection = MarkdownEditingDescriptor.insertMultipleChoice('foo\nbar');
|
||||
expect(revisedSelection).toEqual('( ) foo\n( ) bar\n');
|
||||
});
|
||||
it('recognizes x as a selection if there is non-whitespace after x', function() {
|
||||
const revisedSelection = MarkdownEditingDescriptor.insertMultipleChoice('a\nx b\nc\nx \nd\n x e');
|
||||
expect(revisedSelection).toEqual('( ) a\n(x) b\n( ) c\n( ) x \n( ) d\n(x) e\n');
|
||||
});
|
||||
it('recognizes x as a selection if it is first non whitespace and has whitespace with other non-whitespace', function() {
|
||||
const revisedSelection = MarkdownEditingDescriptor.insertMultipleChoice(' x correct\n x \nex post facto\nb x c\nx c\nxxp');
|
||||
expect(revisedSelection).toEqual('(x) correct\n( ) x \n( ) ex post facto\n( ) b x c\n(x) c\n( ) xxp\n');
|
||||
});
|
||||
it('removes multiple newlines but not last one', function() {
|
||||
const revisedSelection = MarkdownEditingDescriptor.insertMultipleChoice('a\nx b\n\n\nc\n');
|
||||
expect(revisedSelection).toEqual('( ) a\n(x) b\n( ) c\n');
|
||||
});
|
||||
});
|
||||
|
||||
describe('insertCheckboxChoice', function() {
|
||||
// Note, shares code with insertMultipleChoice. Therefore only doing smoke test.
|
||||
it('inserts the template if selection is empty', function() {
|
||||
const revisedSelection = MarkdownEditingDescriptor.insertCheckboxChoice('');
|
||||
expect(revisedSelection).toEqual(MarkdownEditingDescriptor.checkboxChoiceTemplate);
|
||||
});
|
||||
it('wraps existing text', function() {
|
||||
const revisedSelection = MarkdownEditingDescriptor.insertCheckboxChoice('foo\nbar');
|
||||
expect(revisedSelection).toEqual('[ ] foo\n[ ] bar\n');
|
||||
});
|
||||
});
|
||||
|
||||
describe('insertStringInput', function() {
|
||||
it('inserts the template if selection is empty', function() {
|
||||
const revisedSelection = MarkdownEditingDescriptor.insertStringInput('');
|
||||
expect(revisedSelection).toEqual(MarkdownEditingDescriptor.stringInputTemplate);
|
||||
});
|
||||
it('wraps existing text', function() {
|
||||
const revisedSelection = MarkdownEditingDescriptor.insertStringInput('my text');
|
||||
expect(revisedSelection).toEqual('= my text');
|
||||
});
|
||||
});
|
||||
|
||||
describe('insertNumberInput', function() {
|
||||
it('inserts the template if selection is empty', function() {
|
||||
const revisedSelection = MarkdownEditingDescriptor.insertNumberInput('');
|
||||
expect(revisedSelection).toEqual(MarkdownEditingDescriptor.numberInputTemplate);
|
||||
});
|
||||
it('wraps existing text', function() {
|
||||
const revisedSelection = MarkdownEditingDescriptor.insertNumberInput('my text');
|
||||
expect(revisedSelection).toEqual('= my text');
|
||||
});
|
||||
});
|
||||
|
||||
describe('insertSelect', function() {
|
||||
it('inserts the template if selection is empty', function() {
|
||||
const revisedSelection = MarkdownEditingDescriptor.insertSelect('');
|
||||
expect(revisedSelection).toEqual(MarkdownEditingDescriptor.selectTemplate);
|
||||
});
|
||||
it('wraps existing text', function() {
|
||||
const revisedSelection = MarkdownEditingDescriptor.insertSelect('my text');
|
||||
expect(revisedSelection).toEqual('[[my text]]');
|
||||
});
|
||||
});
|
||||
|
||||
describe('insertHeader', function() {
|
||||
it('inserts the template if selection is empty', function() {
|
||||
const revisedSelection = MarkdownEditingDescriptor.insertHeader('');
|
||||
expect(revisedSelection).toEqual(MarkdownEditingDescriptor.headerTemplate);
|
||||
});
|
||||
it('wraps existing text', function() {
|
||||
const revisedSelection = MarkdownEditingDescriptor.insertHeader('my text');
|
||||
expect(revisedSelection).toEqual('my text\n====\n');
|
||||
});
|
||||
});
|
||||
|
||||
describe('insertExplanation', function() {
|
||||
it('inserts the template if selection is empty', function() {
|
||||
const revisedSelection = MarkdownEditingDescriptor.insertExplanation('');
|
||||
expect(revisedSelection).toEqual(MarkdownEditingDescriptor.explanationTemplate);
|
||||
});
|
||||
it('wraps existing text', function() {
|
||||
const revisedSelection = MarkdownEditingDescriptor.insertExplanation('my text');
|
||||
expect(revisedSelection).toEqual('[explanation]\nmy text\n[explanation]');
|
||||
});
|
||||
});
|
||||
|
||||
describe('markdownToXml', function() {
|
||||
it('converts raw text to paragraph', function() {
|
||||
const data = MarkdownEditingDescriptor.markdownToXml('foo');
|
||||
expect(data).toXMLEqual('<problem>\n <p>foo</p>\n</problem>');
|
||||
});
|
||||
// test default templates
|
||||
it('converts numerical response to xml', function() {
|
||||
const data = MarkdownEditingDescriptor.markdownToXml(`A numerical response problem accepts a line of text input from the student, and evaluates the input for correctness based on its numerical value.
|
||||
|
||||
The answer is correct if it is within a specified numerical tolerance of the expected answer.
|
||||
|
||||
Enter the numerical value of Pi:
|
||||
= 3.14159 +- .02
|
||||
|
||||
Enter the approximate value of 502*9:
|
||||
= 502*9 +- 15%
|
||||
|
||||
Enter the number of fingers on a human hand:
|
||||
= 5
|
||||
|
||||
Range tolerance case
|
||||
= [6, 7]
|
||||
= (1, 2)
|
||||
|
||||
If first and last symbols are not brackets, or they are not closed, stringresponse will appear.
|
||||
= (7), 7
|
||||
= (1+2
|
||||
|
||||
[Explanation]
|
||||
Pi, or the the ratio between a circle's circumference to its diameter, is an irrational number known to extreme precision. It is value is approximately equal to 3.14.
|
||||
|
||||
Although you can get an exact value by typing 502*9 into a calculator, the result will be close to 500*10, or 5,000. The grader accepts any response within 15% of the true value, 4518, so that you can use any estimation technique that you like.
|
||||
|
||||
If you look at your hand, you can count that you have five fingers.
|
||||
[Explanation]\
|
||||
`);
|
||||
expect(data).toXMLEqual(`<problem>
|
||||
<p>A numerical response problem accepts a line of text input from the student, and evaluates the input for correctness based on its numerical value.</p>
|
||||
<p>The answer is correct if it is within a specified numerical tolerance of the expected answer.</p>
|
||||
<p>Enter the numerical value of Pi:</p>
|
||||
<numericalresponse answer="3.14159">
|
||||
<responseparam type="tolerance" default=".02" />
|
||||
<formulaequationinput />
|
||||
</numericalresponse>
|
||||
<p>Enter the approximate value of 502*9:</p>
|
||||
<numericalresponse answer="502*9">
|
||||
<responseparam type="tolerance" default="15%" />
|
||||
<formulaequationinput />
|
||||
</numericalresponse>
|
||||
<p>Enter the number of fingers on a human hand:</p>
|
||||
<numericalresponse answer="5">
|
||||
<formulaequationinput />
|
||||
</numericalresponse>
|
||||
<p>Range tolerance case</p>
|
||||
<numericalresponse answer="[6, 7]">
|
||||
<formulaequationinput />
|
||||
</numericalresponse>
|
||||
<numericalresponse answer="(1, 2)">
|
||||
<formulaequationinput />
|
||||
</numericalresponse>
|
||||
<p>If first and last symbols are not brackets, or they are not closed, stringresponse will appear.</p>
|
||||
<stringresponse answer="(7), 7" type="ci" >
|
||||
<textline size="20"/>
|
||||
</stringresponse>
|
||||
<stringresponse answer="(1+2" type="ci" >
|
||||
<textline size="20"/>
|
||||
</stringresponse>
|
||||
<solution>
|
||||
<div class="detailed-solution">
|
||||
<p>Explanation</p>
|
||||
<p>Pi, or the the ratio between a circle's circumference to its diameter, is an irrational number known to extreme precision. It is value is approximately equal to 3.14.</p>
|
||||
<p>Although you can get an exact value by typing 502*9 into a calculator, the result will be close to 500*10, or 5,000. The grader accepts any response within 15% of the true value, 4518, so that you can use any estimation technique that you like.</p>
|
||||
<p>If you look at your hand, you can count that you have five fingers.</p>
|
||||
</div>
|
||||
</solution>
|
||||
</problem>`);
|
||||
});
|
||||
it('will convert 0 as a numerical response (instead of string response)', function() {
|
||||
const data = MarkdownEditingDescriptor.markdownToXml(`\
|
||||
Enter 0 with a tolerance:
|
||||
= 0 +- .02\
|
||||
`);
|
||||
expect(data).toXMLEqual(`<problem>
|
||||
<numericalresponse answer="0">
|
||||
<p>Enter 0 with a tolerance:</p>
|
||||
<responseparam type="tolerance" default=".02"/>
|
||||
<formulaequationinput/>
|
||||
</numericalresponse>
|
||||
|
||||
|
||||
</problem>`);
|
||||
});
|
||||
it('markup with additional answer does not break numerical response', function() {
|
||||
const data = MarkdownEditingDescriptor.markdownToXml(`\
|
||||
Enter 1 with a tolerance:
|
||||
= 1 +- .02
|
||||
or= 2\
|
||||
`);
|
||||
expect(data).toXMLEqual(`<problem>
|
||||
<numericalresponse answer="1">
|
||||
<p>Enter 1 with a tolerance:</p>
|
||||
<responseparam type="tolerance" default=".02"/>
|
||||
<additional_answer answer="2"/>
|
||||
<formulaequationinput/>
|
||||
</numericalresponse>
|
||||
|
||||
</problem>`
|
||||
);
|
||||
});
|
||||
it('markup for numerical with multiple additional answers renders correctly', function() {
|
||||
const data = MarkdownEditingDescriptor.markdownToXml(`\
|
||||
Enter 1 with a tolerance:
|
||||
= 1 +- .02
|
||||
or= 2
|
||||
or= 3\
|
||||
`);
|
||||
expect(data).toXMLEqual(`<problem>
|
||||
<numericalresponse answer="1">
|
||||
<p>Enter 1 with a tolerance:</p>
|
||||
<responseparam type="tolerance" default=".02"/>
|
||||
<additional_answer answer="2"/>
|
||||
<additional_answer answer="3"/>
|
||||
<formulaequationinput/>
|
||||
</numericalresponse>
|
||||
|
||||
</problem>`
|
||||
);
|
||||
});
|
||||
it('Do not render ranged/tolerance/alphabetical additional answers for numerical response', function() {
|
||||
const data = MarkdownEditingDescriptor.markdownToXml(`\
|
||||
Enter 1 with a tolerance:
|
||||
= 1 +- .02
|
||||
or= 2
|
||||
or= 3 +- 0.1
|
||||
or= [4,6]
|
||||
or= ABC
|
||||
or= 7\
|
||||
`);
|
||||
expect(data).toXMLEqual(`<problem>
|
||||
<numericalresponse answer="1">
|
||||
<p>Enter 1 with a tolerance:</p>
|
||||
<responseparam type="tolerance" default=".02"/>
|
||||
<additional_answer answer="2"/>
|
||||
<additional_answer answer="7"/>
|
||||
<formulaequationinput/>
|
||||
</numericalresponse>
|
||||
|
||||
</problem>`
|
||||
);
|
||||
});
|
||||
it('markup with feedback renders correctly in additional answer for numerical response', function() {
|
||||
const data = MarkdownEditingDescriptor.markdownToXml(`\
|
||||
Enter 1 with a tolerance:
|
||||
= 100 +- .02 {{ main feedback }}
|
||||
or= 10 {{ additional feedback }}\
|
||||
`);
|
||||
expect(data).toXMLEqual(`<problem>
|
||||
<numericalresponse answer="100">
|
||||
<p>Enter 1 with a tolerance:</p>
|
||||
<responseparam type="tolerance" default=".02"/>
|
||||
<additional_answer answer="10">
|
||||
<correcthint>additional feedback</correcthint>
|
||||
</additional_answer>
|
||||
<formulaequationinput/>
|
||||
<correcthint>main feedback</correcthint>
|
||||
</numericalresponse>
|
||||
|
||||
</problem>`
|
||||
);
|
||||
});
|
||||
it('converts multiple choice to xml', function() {
|
||||
const data = MarkdownEditingDescriptor.markdownToXml(`A multiple choice problem presents radio buttons for student input. Students can only select a single option presented. Multiple Choice questions have been the subject of many areas of research due to the early invention and adoption of bubble sheets.
|
||||
|
||||
One of the main elements that goes into a good multiple choice question is the existence of good distractors. That is, each of the alternate responses presented to the student should be the result of a plausible mistake that a student might make.
|
||||
|
||||
>>What Apple device competed with the portable CD player?<<
|
||||
( ) The iPad
|
||||
( ) Napster
|
||||
(x) The iPod
|
||||
( ) The vegetable peeler
|
||||
( ) Android
|
||||
( ) The Beatles
|
||||
|
||||
[Explanation]
|
||||
The release of the iPod allowed consumers to carry their entire music library with them in a format that did not rely on fragile and energy-intensive spinning disks.
|
||||
[Explanation]\
|
||||
`);
|
||||
expect(data).toXMLEqual(`<problem>
|
||||
<multiplechoiceresponse>
|
||||
<p>A multiple choice problem presents radio buttons for student input. Students can only select a single option presented. Multiple Choice questions have been the subject of many areas of research due to the early invention and adoption of bubble sheets.</p>
|
||||
<p>One of the main elements that goes into a good multiple choice question is the existence of good distractors. That is, each of the alternate responses presented to the student should be the result of a plausible mistake that a student might make.</p>
|
||||
<label>What Apple device competed with the portable CD player?</label>
|
||||
<choicegroup type="MultipleChoice">
|
||||
<choice correct="false">The iPad</choice>
|
||||
<choice correct="false">Napster</choice>
|
||||
<choice correct="true">The iPod</choice>
|
||||
<choice correct="false">The vegetable peeler</choice>
|
||||
<choice correct="false">Android</choice>
|
||||
<choice correct="false">The Beatles</choice>
|
||||
</choicegroup>
|
||||
<solution>
|
||||
<div class="detailed-solution">
|
||||
<p>Explanation</p>
|
||||
<p>The release of the iPod allowed consumers to carry their entire music library with them in a format that did not rely on fragile and energy-intensive spinning disks.</p>
|
||||
</div>
|
||||
</solution>
|
||||
</multiplechoiceresponse>
|
||||
</problem>`);
|
||||
});
|
||||
it('converts multiple choice shuffle to xml', function() {
|
||||
const data = MarkdownEditingDescriptor.markdownToXml(`A multiple choice problem presents radio buttons for student input. Students can only select a single option presented. Multiple Choice questions have been the subject of many areas of research due to the early invention and adoption of bubble sheets.
|
||||
|
||||
One of the main elements that goes into a good multiple choice question is the existence of good distractors. That is, each of the alternate responses presented to the student should be the result of a plausible mistake that a student might make.
|
||||
|
||||
What Apple device competed with the portable CD player?
|
||||
(!x@) The iPad
|
||||
(@) Napster
|
||||
() The iPod
|
||||
( ) The vegetable peeler
|
||||
( ) Android
|
||||
(@) The Beatles
|
||||
|
||||
[Explanation]
|
||||
The release of the iPod allowed consumers to carry their entire music library with them in a format that did not rely on fragile and energy-intensive spinning disks.
|
||||
[Explanation]\
|
||||
`);
|
||||
expect(data).toXMLEqual(`\
|
||||
<problem>
|
||||
<multiplechoiceresponse>
|
||||
<p>A multiple choice problem presents radio buttons for student input. Students can only select a single option presented. Multiple Choice questions have been the subject of many areas of research due to the early invention and adoption of bubble sheets.</p>
|
||||
<p>One of the main elements that goes into a good multiple choice question is the existence of good distractors. That is, each of the alternate responses presented to the student should be the result of a plausible mistake that a student might make.</p>
|
||||
<p>What Apple device competed with the portable CD player?</p>
|
||||
<choicegroup type="MultipleChoice" shuffle="true">
|
||||
<choice correct="true" fixed="true">The iPad</choice>
|
||||
<choice correct="false" fixed="true">Napster</choice>
|
||||
<choice correct="false">The iPod</choice>
|
||||
<choice correct="false">The vegetable peeler</choice>
|
||||
<choice correct="false">Android</choice>
|
||||
<choice correct="false" fixed="true">The Beatles</choice>
|
||||
</choicegroup>
|
||||
<solution>
|
||||
<div class="detailed-solution">
|
||||
<p>Explanation</p>
|
||||
<p>The release of the iPod allowed consumers to carry their entire music library with them in a format that did not rely on fragile and energy-intensive spinning disks.</p>
|
||||
</div>
|
||||
</solution>
|
||||
</multiplechoiceresponse>
|
||||
</problem>`);
|
||||
});
|
||||
|
||||
it('converts a series of multiplechoice to xml', function() {
|
||||
const data = MarkdownEditingDescriptor.markdownToXml(`bleh
|
||||
(!x) a
|
||||
() b
|
||||
() c
|
||||
yatta
|
||||
( ) x
|
||||
( ) y
|
||||
(x) z
|
||||
testa
|
||||
(!) i
|
||||
( ) ii
|
||||
(x) iii
|
||||
[Explanation]
|
||||
When the student is ready, the explanation appears.
|
||||
[Explanation]\
|
||||
`);
|
||||
expect(data).toXMLEqual(`\
|
||||
<problem>
|
||||
<p>bleh</p>
|
||||
<multiplechoiceresponse>
|
||||
<choicegroup type="MultipleChoice" shuffle="true">
|
||||
<choice correct="true">a</choice>
|
||||
<choice correct="false">b</choice>
|
||||
<choice correct="false">c</choice>
|
||||
</choicegroup>
|
||||
</multiplechoiceresponse>
|
||||
<p>yatta</p>
|
||||
<multiplechoiceresponse>
|
||||
<choicegroup type="MultipleChoice">
|
||||
<choice correct="false">x</choice>
|
||||
<choice correct="false">y</choice>
|
||||
<choice correct="true">z</choice>
|
||||
</choicegroup>
|
||||
</multiplechoiceresponse>
|
||||
<p>testa</p>
|
||||
<multiplechoiceresponse>
|
||||
<choicegroup type="MultipleChoice" shuffle="true">
|
||||
<choice correct="false">i</choice>
|
||||
<choice correct="false">ii</choice>
|
||||
<choice correct="true">iii</choice>
|
||||
</choicegroup>
|
||||
</multiplechoiceresponse>
|
||||
<solution>
|
||||
<div class="detailed-solution">
|
||||
<p>Explanation</p>
|
||||
<p>When the student is ready, the explanation appears.</p>
|
||||
</div>
|
||||
</solution>
|
||||
</problem>`);
|
||||
});
|
||||
|
||||
it('converts OptionResponse to xml', function() {
|
||||
const data = MarkdownEditingDescriptor.markdownToXml(`OptionResponse gives a limited set of options for students to respond with, and presents those options in a format that encourages them to search for a specific answer rather than being immediately presented with options from which to recognize the correct answer.
|
||||
|
||||
The answer options and the identification of the correct answer is defined in the <b>optioninput</b> tag.
|
||||
|
||||
Translation between Option Response and __________ is extremely straightforward:
|
||||
[[(Multiple Choice), String Response, Numerical Response, External Response, Image Response]]
|
||||
|
||||
[Explanation]
|
||||
Multiple Choice also allows students to select from a variety of pre-written responses, although the format makes it easier for students to read very long response options. Optionresponse also differs slightly because students are more likely to think of an answer and then search for it rather than relying purely on recognition to answer the question.
|
||||
[Explanation]\
|
||||
`);
|
||||
expect(data).toXMLEqual(`\
|
||||
<problem>
|
||||
<optionresponse>
|
||||
<p>OptionResponse gives a limited set of options for students to respond with, and presents those options in a format that encourages them to search for a specific answer rather than being immediately presented with options from which to recognize the correct answer.</p>
|
||||
<p>The answer options and the identification of the correct answer is defined in the <b>optioninput</b> tag.</p>
|
||||
<p>Translation between Option Response and __________ is extremely straightforward:</p>
|
||||
<optioninput options="('Multiple Choice','String Response','Numerical Response','External Response','Image Response')" correct="Multiple Choice"/>
|
||||
<solution>
|
||||
<div class="detailed-solution">
|
||||
<p>Explanation</p>
|
||||
<p>Multiple Choice also allows students to select from a variety of pre-written responses, although the format makes it easier for students to read very long response options. Optionresponse also differs slightly because students are more likely to think of an answer and then search for it rather than relying purely on recognition to answer the question.</p>
|
||||
</div>
|
||||
</solution>
|
||||
</optionresponse>
|
||||
</problem>`);
|
||||
});
|
||||
it('converts StringResponse to xml', function() {
|
||||
const data = MarkdownEditingDescriptor.markdownToXml(`A string response problem accepts a line of text input from the student, and evaluates the input for correctness based on an expected answer within each input box.
|
||||
|
||||
The answer is correct if it matches every character of the expected answer. This can be a problem with international spelling, dates, or anything where the format of the answer is not clear.
|
||||
|
||||
Which US state has Lansing as its capital?
|
||||
= Michigan
|
||||
|
||||
[Explanation]
|
||||
Lansing is the capital of Michigan, although it is not Michgan's largest city, or even the seat of the county in which it resides.
|
||||
[Explanation]\
|
||||
`);
|
||||
expect(data).toXMLEqual(`\
|
||||
<problem>
|
||||
<stringresponse answer="Michigan" type="ci">
|
||||
<p>A string response problem accepts a line of text input from the student, and evaluates the input for correctness based on an expected answer within each input box.</p>
|
||||
<p>The answer is correct if it matches every character of the expected answer. This can be a problem with international spelling, dates, or anything where the format of the answer is not clear.</p>
|
||||
<p>Which US state has Lansing as its capital?</p>
|
||||
<textline size="20"/>
|
||||
<solution>
|
||||
<div class="detailed-solution">
|
||||
<p>Explanation</p>
|
||||
<p>Lansing is the capital of Michigan, although it is not Michgan's largest city, or even the seat of the county in which it resides.</p>
|
||||
</div>
|
||||
</solution>
|
||||
</stringresponse>
|
||||
</problem>`);
|
||||
});
|
||||
it('converts StringResponse with regular expression to xml', function() {
|
||||
const data = MarkdownEditingDescriptor.markdownToXml(`Who lead the civil right movement in the United States of America?
|
||||
= | \w*\.?\s*Luther King\s*.*
|
||||
|
||||
[Explanation]
|
||||
Test Explanation.
|
||||
[Explanation]\
|
||||
`);
|
||||
expect(data).toXMLEqual(`\
|
||||
<problem>
|
||||
<stringresponse answer="w*.?s*Luther Kings*.*" type="ci regexp">
|
||||
<p>Who lead the civil right movement in the United States of America?</p>
|
||||
<textline size="20"/>
|
||||
<solution>
|
||||
<div class="detailed-solution">
|
||||
<p>Explanation</p>
|
||||
<p>Test Explanation.</p>
|
||||
</div>
|
||||
</solution>
|
||||
</stringresponse>
|
||||
</problem>`);
|
||||
});
|
||||
it('converts StringResponse with multiple answers to xml', function() {
|
||||
const data = MarkdownEditingDescriptor.markdownToXml(`Who lead the civil right movement in the United States of America?
|
||||
= Dr. Martin Luther King Jr.
|
||||
or= Doctor Martin Luther King Junior
|
||||
or= Martin Luther King
|
||||
or= Martin Luther King Junior
|
||||
|
||||
[Explanation]
|
||||
Test Explanation.
|
||||
[Explanation]\
|
||||
`);
|
||||
expect(data).toXMLEqual(`\
|
||||
<problem>
|
||||
<stringresponse answer="Dr. Martin Luther King Jr." type="ci">
|
||||
<p>Who lead the civil right movement in the United States of America?</p>
|
||||
<additional_answer answer="Doctor Martin Luther King Junior"/>
|
||||
<additional_answer answer="Martin Luther King"/>
|
||||
<additional_answer answer="Martin Luther King Junior"/>
|
||||
<textline size="20"/>
|
||||
<solution>
|
||||
<div class="detailed-solution">
|
||||
<p>Explanation</p>
|
||||
<p>Test Explanation.</p>
|
||||
</div>
|
||||
</solution>
|
||||
</stringresponse>
|
||||
</problem>`);
|
||||
});
|
||||
it('converts StringResponse with multiple answers and regular expressions to xml', function() {
|
||||
const data = MarkdownEditingDescriptor.markdownToXml(`Write a number from 1 to 4.
|
||||
=| ^One$
|
||||
or= two
|
||||
or= ^thre+
|
||||
or= ^4|Four$
|
||||
|
||||
[Explanation]
|
||||
Test Explanation.
|
||||
[Explanation]\
|
||||
`);
|
||||
expect(data).toXMLEqual(`\
|
||||
<problem>
|
||||
<stringresponse answer="^One$" type="ci regexp">
|
||||
<p>Write a number from 1 to 4.</p>
|
||||
<additional_answer answer="two"/>
|
||||
<additional_answer answer="^thre+"/>
|
||||
<additional_answer answer="^4|Four$"/>
|
||||
<textline size="20"/>
|
||||
<solution>
|
||||
<div class="detailed-solution">
|
||||
<p>Explanation</p>
|
||||
<p>Test Explanation.</p>
|
||||
</div>
|
||||
</solution>
|
||||
</stringresponse>
|
||||
</problem>`);
|
||||
});
|
||||
// test labels
|
||||
it('converts markdown labels to label attributes', function() {
|
||||
const data = MarkdownEditingDescriptor.markdownToXml(`>>Who lead the civil right movement in the United States of America?<<
|
||||
= | \w*\.?\s*Luther King\s*.*
|
||||
|
||||
[Explanation]
|
||||
Test Explanation.
|
||||
[Explanation]\
|
||||
`);
|
||||
expect(data).toXMLEqual(`\
|
||||
<problem>
|
||||
<stringresponse answer="w*.?s*Luther Kings*.*" type="ci regexp">
|
||||
<label>Who lead the civil right movement in the United States of America?</label>
|
||||
<textline size="20"/>
|
||||
<solution>
|
||||
<div class="detailed-solution">
|
||||
<p>Explanation</p>
|
||||
<p>Test Explanation.</p>
|
||||
</div>
|
||||
</solution>
|
||||
</stringresponse>
|
||||
</problem>`);
|
||||
});
|
||||
it('handles multiple questions with labels', function() {
|
||||
const data = MarkdownEditingDescriptor.markdownToXml(`\
|
||||
France is a country in Europe.
|
||||
|
||||
>>What is the capital of France?<<
|
||||
= Paris
|
||||
|
||||
Germany is a country in Europe, too.
|
||||
|
||||
>>What is the capital of Germany?<<
|
||||
( ) Bonn
|
||||
( ) Hamburg
|
||||
(x) Berlin
|
||||
( ) Donut\
|
||||
`);
|
||||
expect(data).toXMLEqual(`\
|
||||
<problem>
|
||||
<p>France is a country in Europe.</p>
|
||||
|
||||
<label>What is the capital of France?</label>
|
||||
<stringresponse answer="Paris" type="ci" >
|
||||
<textline size="20"/>
|
||||
</stringresponse>
|
||||
|
||||
<p>Germany is a country in Europe, too.</p>
|
||||
|
||||
<label>What is the capital of Germany?</label>
|
||||
<multiplechoiceresponse>
|
||||
<choicegroup type="MultipleChoice">
|
||||
<choice correct="false">Bonn</choice>
|
||||
<choice correct="false">Hamburg</choice>
|
||||
<choice correct="true">Berlin</choice>
|
||||
<choice correct="false">Donut</choice>
|
||||
</choicegroup>
|
||||
</multiplechoiceresponse>
|
||||
</problem>`);
|
||||
});
|
||||
it('tests multiple questions with only one label', function() {
|
||||
const data = MarkdownEditingDescriptor.markdownToXml(`\
|
||||
France is a country in Europe.
|
||||
|
||||
>>What is the capital of France?<<
|
||||
= Paris
|
||||
|
||||
Germany is a country in Europe, too.
|
||||
|
||||
What is the capital of Germany?
|
||||
( ) Bonn
|
||||
( ) Hamburg
|
||||
(x) Berlin
|
||||
( ) Donut\
|
||||
`);
|
||||
expect(data).toXMLEqual(`\
|
||||
<problem>
|
||||
<p>France is a country in Europe.</p>
|
||||
|
||||
<label>What is the capital of France?</label>
|
||||
<stringresponse answer="Paris" type="ci" >
|
||||
<textline size="20"/>
|
||||
</stringresponse>
|
||||
|
||||
<p>Germany is a country in Europe, too.</p>
|
||||
|
||||
<p>What is the capital of Germany?</p>
|
||||
<multiplechoiceresponse>
|
||||
<choicegroup type="MultipleChoice">
|
||||
<choice correct="false">Bonn</choice>
|
||||
<choice correct="false">Hamburg</choice>
|
||||
<choice correct="true">Berlin</choice>
|
||||
<choice correct="false">Donut</choice>
|
||||
</choicegroup>
|
||||
</multiplechoiceresponse>
|
||||
</problem>`);
|
||||
});
|
||||
|
||||
it('adds labels to formulae', function() {
|
||||
const data = MarkdownEditingDescriptor.markdownToXml(`\
|
||||
>>Enter the numerical value of Pi:<<
|
||||
= 3.14159 +- .02\
|
||||
`);
|
||||
expect(data).toXMLEqual(`<problem>
|
||||
<numericalresponse answer="3.14159">
|
||||
<label>Enter the numerical value of Pi:</label>
|
||||
<responseparam type="tolerance" default=".02"/>
|
||||
<formulaequationinput/>
|
||||
</numericalresponse>
|
||||
|
||||
|
||||
</problem>`);
|
||||
});
|
||||
|
||||
// test oddities
|
||||
it('converts headers and oddities to xml', function() {
|
||||
const data = MarkdownEditingDescriptor.markdownToXml(`Not a header
|
||||
A header
|
||||
==============
|
||||
|
||||
Multiple choice w/ parentheticals
|
||||
( ) option (with parens)
|
||||
( ) xd option (x)
|
||||
()) parentheses inside
|
||||
() no space b4 close paren
|
||||
|
||||
Choice checks
|
||||
[ ] option1 [x]
|
||||
[x] correct
|
||||
[x] redundant
|
||||
[(] distractor
|
||||
[] no space
|
||||
|
||||
Option with multiple correct ones
|
||||
[[one option, (correct one), (should not be correct)]]
|
||||
|
||||
Option with embedded parens
|
||||
[[My (heart), another, (correct)]]
|
||||
|
||||
What happens w/ empty correct options?
|
||||
[[()]]
|
||||
|
||||
[Explanation]see[/expLanation]
|
||||
|
||||
[explanation]
|
||||
orphaned start
|
||||
|
||||
No p tags in the below
|
||||
<script type='javascript'>
|
||||
var two = 2;
|
||||
|
||||
console.log(two * 2);
|
||||
</script>
|
||||
|
||||
But in this there should be
|
||||
<div>
|
||||
Great ideas require offsetting.
|
||||
|
||||
bad tests require drivel
|
||||
</div>
|
||||
|
||||
[code]
|
||||
Code should be nicely monospaced.
|
||||
[/code]\
|
||||
`);
|
||||
expect(data).toXMLEqual(`\
|
||||
<problem>
|
||||
<p>Not a header</p>
|
||||
<h3 class="hd hd-2 problem-header">A header</h3>
|
||||
<p>Multiple choice w/ parentheticals</p>
|
||||
<multiplechoiceresponse>
|
||||
<choicegroup type="MultipleChoice">
|
||||
<choice correct="false">option (with parens)</choice>
|
||||
<choice correct="false">xd option (x)</choice>
|
||||
<choice correct="false">parentheses inside</choice>
|
||||
<choice correct="false">no space b4 close paren</choice>
|
||||
</choicegroup>
|
||||
</multiplechoiceresponse>
|
||||
<p>Choice checks</p>
|
||||
<choiceresponse>
|
||||
<checkboxgroup>
|
||||
<choice correct="false">option1 [x]</choice>
|
||||
<choice correct="true">correct</choice>
|
||||
<choice correct="true">redundant</choice>
|
||||
<choice correct="false">distractor</choice>
|
||||
<choice correct="false">no space</choice>
|
||||
</checkboxgroup>
|
||||
</choiceresponse>
|
||||
<p>Option with multiple correct ones</p>
|
||||
<optionresponse>
|
||||
<optioninput options="('one option','correct one','should not be correct')" correct="correct one"></optioninput>
|
||||
</optionresponse>
|
||||
<p>Option with embedded parens</p>
|
||||
<optionresponse>
|
||||
<optioninput options="('My (heart)','another','correct')" correct="correct"></optioninput>
|
||||
</optionresponse>
|
||||
<p>What happens w/ empty correct options?</p>
|
||||
<optionresponse>
|
||||
<optioninput options="('')" correct=""></optioninput>
|
||||
</optionresponse>
|
||||
<solution>
|
||||
<div class="detailed-solution">
|
||||
<p>Explanation</p>
|
||||
<p>see</p>
|
||||
</div>
|
||||
</solution>
|
||||
<p>[explanation]</p>
|
||||
<p>orphaned start</p>
|
||||
<p>No p tags in the below</p>
|
||||
<script type='javascript'>
|
||||
var two = 2;
|
||||
|
||||
console.log(two * 2);
|
||||
</script>
|
||||
<p>But in this there should be</p>
|
||||
<div>
|
||||
<p>Great ideas require offsetting.</p>
|
||||
<p>bad tests require drivel</p>
|
||||
</div>
|
||||
<pre>
|
||||
<code>Code should be nicely monospaced.
|
||||
</code>
|
||||
</pre>
|
||||
</problem>`);
|
||||
});
|
||||
|
||||
it('can separate responsetypes based on ---', function() {
|
||||
const data = MarkdownEditingDescriptor.markdownToXml(`\
|
||||
Multiple choice problems allow learners to select only one option. Learners can see all the options along with the problem text.
|
||||
|
||||
>>Which of the following countries has the largest population?<<
|
||||
( ) Brazil {{ timely feedback -- explain why an almost correct answer is wrong }}
|
||||
( ) Germany
|
||||
(x) Indonesia
|
||||
( ) Russia
|
||||
|
||||
[explanation]
|
||||
According to September 2014 estimates:
|
||||
The population of Indonesia is approximately 250 million.
|
||||
The population of Brazil is approximately 200 million.
|
||||
The population of Russia is approximately 146 million.
|
||||
The population of Germany is approximately 81 million.
|
||||
[explanation]
|
||||
|
||||
---
|
||||
|
||||
Checkbox problems allow learners to select multiple options. Learners can see all the options along with the problem text.
|
||||
|
||||
>>The following languages are in the Indo-European family:<<
|
||||
[x] Urdu
|
||||
[ ] Finnish
|
||||
[x] Marathi
|
||||
[x] French
|
||||
[ ] Hungarian
|
||||
|
||||
Note: Make sure you select all of the correct options—there may be more than one!
|
||||
|
||||
[explanation]
|
||||
Urdu, Marathi, and French are all Indo-European languages, while Finnish and Hungarian are in the Uralic family.
|
||||
[explanation]
|
||||
\
|
||||
`);
|
||||
expect(data).toXMLEqual(`\
|
||||
<problem>
|
||||
<multiplechoiceresponse>
|
||||
<p>Multiple choice problems allow learners to select only one option. Learners can see all the options along with the problem text.</p>
|
||||
<label>Which of the following countries has the largest population?</label>
|
||||
<choicegroup type="MultipleChoice">
|
||||
<choice correct="false">Brazil <choicehint>timely feedback -- explain why an almost correct answer is wrong</choicehint>
|
||||
</choice>
|
||||
<choice correct="false">Germany</choice>
|
||||
<choice correct="true">Indonesia</choice>
|
||||
<choice correct="false">Russia</choice>
|
||||
</choicegroup>
|
||||
<solution>
|
||||
<div class="detailed-solution">
|
||||
<p>Explanation</p>
|
||||
<p>According to September 2014 estimates:</p>
|
||||
<p>The population of Indonesia is approximately 250 million.</p>
|
||||
<p>The population of Brazil is approximately 200 million.</p>
|
||||
<p>The population of Russia is approximately 146 million.</p>
|
||||
<p>The population of Germany is approximately 81 million.</p>
|
||||
</div>
|
||||
</solution>
|
||||
</multiplechoiceresponse>
|
||||
|
||||
<choiceresponse>
|
||||
<p>Checkbox problems allow learners to select multiple options. Learners can see all the options along with the problem text.</p>
|
||||
<label>The following languages are in the Indo-European family:</label>
|
||||
<checkboxgroup>
|
||||
<choice correct="true">Urdu</choice>
|
||||
<choice correct="false">Finnish</choice>
|
||||
<choice correct="true">Marathi</choice>
|
||||
<choice correct="true">French</choice>
|
||||
<choice correct="false">Hungarian</choice>
|
||||
</checkboxgroup>
|
||||
<p>Note: Make sure you select all of the correct options—there may be more than one!</p>
|
||||
<solution>
|
||||
<div class="detailed-solution">
|
||||
<p>Explanation</p>
|
||||
<p>Urdu, Marathi, and French are all Indo-European languages, while Finnish and Hungarian are in the Uralic family.</p>
|
||||
</div>
|
||||
</solution>
|
||||
</choiceresponse>
|
||||
</problem>\
|
||||
`);
|
||||
});
|
||||
|
||||
it('can separate other things based on ---', function() {
|
||||
const data = MarkdownEditingDescriptor.markdownToXml(`\
|
||||
Multiple choice problems allow learners to select only one option. Learners can see all the options along with the problem text.
|
||||
|
||||
---
|
||||
|
||||
>>Which of the following countries has the largest population?<<
|
||||
( ) Brazil {{ timely feedback -- explain why an almost correct answer is wrong }}
|
||||
( ) Germany
|
||||
(x) Indonesia
|
||||
( ) Russia
|
||||
|
||||
[explanation]
|
||||
According to September 2014 estimates:
|
||||
The population of Indonesia is approximately 250 million.
|
||||
The population of Brazil is approximately 200 million.
|
||||
The population of Russia is approximately 146 million.
|
||||
The population of Germany is approximately 81 million.
|
||||
[explanation]\
|
||||
`);
|
||||
expect(data).toXMLEqual(`\
|
||||
<problem>
|
||||
<p>Multiple choice problems allow learners to select only one option. Learners can see all the options along with the problem text.</p>
|
||||
|
||||
<multiplechoiceresponse>
|
||||
<label>Which of the following countries has the largest population?</label>
|
||||
<choicegroup type="MultipleChoice">
|
||||
<choice correct="false">Brazil <choicehint>timely feedback -- explain why an almost correct answer is wrong</choicehint>
|
||||
</choice>
|
||||
<choice correct="false">Germany</choice>
|
||||
<choice correct="true">Indonesia</choice>
|
||||
<choice correct="false">Russia</choice>
|
||||
</choicegroup>
|
||||
<solution>
|
||||
<div class="detailed-solution">
|
||||
<p>Explanation</p>
|
||||
<p>According to September 2014 estimates:</p>
|
||||
<p>The population of Indonesia is approximately 250 million.</p>
|
||||
<p>The population of Brazil is approximately 200 million.</p>
|
||||
<p>The population of Russia is approximately 146 million.</p>
|
||||
<p>The population of Germany is approximately 81 million.</p>
|
||||
</div>
|
||||
</solution>
|
||||
</multiplechoiceresponse>
|
||||
</problem>\
|
||||
`);
|
||||
});
|
||||
|
||||
it('can do separation if spaces are present around ---', function() {
|
||||
const data = MarkdownEditingDescriptor.markdownToXml(`\
|
||||
>>The following languages are in the Indo-European family:||There are three correct choices.<<
|
||||
[x] Urdu
|
||||
[ ] Finnish
|
||||
[x] Marathi
|
||||
[x] French
|
||||
[ ] Hungarian
|
||||
|
||||
---
|
||||
|
||||
>>Which of the following countries has the largest population?||You have only choice.<<
|
||||
( ) Brazil {{ timely feedback -- explain why an almost correct answer is wrong }}
|
||||
( ) Germany
|
||||
(x) Indonesia
|
||||
( ) Russia\
|
||||
`);
|
||||
expect(data).toXMLEqual(`\
|
||||
<problem>
|
||||
<choiceresponse>
|
||||
<label>The following languages are in the Indo-European family:</label>
|
||||
<description>There are three correct choices.</description>
|
||||
<checkboxgroup>
|
||||
<choice correct="true">Urdu</choice>
|
||||
<choice correct="false">Finnish</choice>
|
||||
<choice correct="true">Marathi</choice>
|
||||
<choice correct="true">French</choice>
|
||||
<choice correct="false">Hungarian</choice>
|
||||
</checkboxgroup>
|
||||
</choiceresponse>
|
||||
|
||||
<multiplechoiceresponse>
|
||||
<label>Which of the following countries has the largest population?</label>
|
||||
<description>You have only choice.</description>
|
||||
<choicegroup type="MultipleChoice">
|
||||
<choice correct="false">Brazil
|
||||
<choicehint>timely feedback -- explain why an almost correct answer is wrong</choicehint>
|
||||
</choice>
|
||||
<choice correct="false">Germany</choice>
|
||||
<choice correct="true">Indonesia</choice>
|
||||
<choice correct="false">Russia</choice>
|
||||
</choicegroup>
|
||||
</multiplechoiceresponse>
|
||||
</problem>\
|
||||
`);
|
||||
});
|
||||
|
||||
it('can extract question description', function() {
|
||||
const data = MarkdownEditingDescriptor.markdownToXml(`\
|
||||
>>The following languages are in the Indo-European family:||Choose wisely.<<
|
||||
[x] Urdu
|
||||
[ ] Finnish
|
||||
[x] Marathi
|
||||
[x] French
|
||||
[ ] Hungarian\
|
||||
`);
|
||||
expect(data).toXMLEqual(`\
|
||||
<problem>
|
||||
<choiceresponse>
|
||||
<label>The following languages are in the Indo-European family:</label>
|
||||
<description>Choose wisely.</description>
|
||||
<checkboxgroup>
|
||||
<choice correct="true">Urdu</choice>
|
||||
<choice correct="false">Finnish</choice>
|
||||
<choice correct="true">Marathi</choice>
|
||||
<choice correct="true">French</choice>
|
||||
<choice correct="false">Hungarian</choice>
|
||||
</checkboxgroup>
|
||||
</choiceresponse>
|
||||
</problem>\
|
||||
`);
|
||||
});
|
||||
|
||||
it('can handle question and description spanned across multiple lines', function() {
|
||||
const data = MarkdownEditingDescriptor.markdownToXml(`\
|
||||
>>The following languages
|
||||
are in the
|
||||
Indo-European family:
|
||||
||
|
||||
first second
|
||||
third
|
||||
<<
|
||||
[x] Urdu
|
||||
[ ] Finnish
|
||||
[x] Marathi\
|
||||
`);
|
||||
expect(data).toXMLEqual(`\
|
||||
<problem>
|
||||
<choiceresponse>
|
||||
<label>The following languages are in the Indo-European family:</label>
|
||||
<description>first second third</description>
|
||||
<checkboxgroup>
|
||||
<choice correct="true">Urdu</choice>
|
||||
<choice correct="false">Finnish</choice>
|
||||
<choice correct="true">Marathi</choice>
|
||||
</checkboxgroup>
|
||||
</choiceresponse>
|
||||
</problem>\
|
||||
`);
|
||||
});
|
||||
|
||||
it('will not add empty description', function() {
|
||||
const data = MarkdownEditingDescriptor.markdownToXml(`\
|
||||
>>The following languages are in the Indo-European family:||<<
|
||||
[x] Urdu
|
||||
[ ] Finnish\
|
||||
`);
|
||||
expect(data).toXMLEqual(`\
|
||||
<problem>
|
||||
<choiceresponse>
|
||||
<label>The following languages are in the Indo-European family:</label>
|
||||
<checkboxgroup>
|
||||
<choice correct="true">Urdu</choice>
|
||||
<choice correct="false">Finnish</choice>
|
||||
</checkboxgroup>
|
||||
</choiceresponse>
|
||||
</problem>\
|
||||
`);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,996 +0,0 @@
|
||||
# This file tests the parsing of extended-hints, double bracket sections {{ .. }}
|
||||
# for all sorts of markdown.
|
||||
describe 'Markdown to xml extended hint dropdown', ->
|
||||
it 'produces xml', ->
|
||||
data = MarkdownEditingDescriptor.markdownToXml("""
|
||||
Translation between Dropdown and ________ is straightforward.
|
||||
|
||||
[[
|
||||
(Multiple Choice) {{ Good Job::Yes, multiple choice is the right answer. }}
|
||||
Text Input {{ No, text input problems don't present options. }}
|
||||
Numerical Input {{ No, numerical input problems don't present options. }}
|
||||
]]
|
||||
|
||||
|
||||
|
||||
Clowns have funny _________ to make people laugh.
|
||||
|
||||
[[
|
||||
dogs {{ NOPE::Not dogs, not cats, not toads }}
|
||||
(FACES) {{ With lots of makeup, doncha know?}}
|
||||
|
||||
money {{ Clowns don't have any money, of course }}
|
||||
donkeys {{don't be an ass.}}
|
||||
-no hint-
|
||||
]]
|
||||
|
||||
""")
|
||||
expect(data).toXMLEqual("""
|
||||
<problem>
|
||||
<p>Translation between Dropdown and ________ is straightforward.</p>
|
||||
<optionresponse>
|
||||
<optioninput>
|
||||
<option correct="True">Multiple Choice
|
||||
<optionhint label="Good Job">Yes, multiple choice is the right answer.</optionhint>
|
||||
</option>
|
||||
<option correct="False">Text Input
|
||||
<optionhint>No, text input problems don't present options.</optionhint>
|
||||
</option>
|
||||
<option correct="False">Numerical Input
|
||||
<optionhint>No, numerical input problems don't present options.</optionhint>
|
||||
</option>
|
||||
</optioninput>
|
||||
</optionresponse>
|
||||
<p>Clowns have funny _________ to make people laugh.</p>
|
||||
<optionresponse>
|
||||
<optioninput>
|
||||
<option correct="False">dogs
|
||||
<optionhint label="NOPE">Not dogs, not cats, not toads</optionhint>
|
||||
</option>
|
||||
<option correct="True">FACES
|
||||
<optionhint>With lots of makeup, doncha know?</optionhint>
|
||||
</option>
|
||||
<option correct="False">money
|
||||
<optionhint>Clowns don't have any money, of course</optionhint>
|
||||
</option>
|
||||
<option correct="False">donkeys
|
||||
<optionhint>don't be an ass.</optionhint>
|
||||
</option>
|
||||
<option correct="False">-no hint-</option>
|
||||
</optioninput>
|
||||
</optionresponse>
|
||||
</problem>
|
||||
""")
|
||||
|
||||
it 'produces xml with demand hint', ->
|
||||
data = MarkdownEditingDescriptor.markdownToXml("""
|
||||
Translation between Dropdown and ________ is straightforward.
|
||||
|
||||
[[
|
||||
(Right) {{ Good Job::yes }}
|
||||
Wrong 1 {{no}}
|
||||
Wrong 2 {{ Label::no }}
|
||||
]]
|
||||
|
||||
|| 0) zero ||
|
||||
|| 1) one ||
|
||||
|| 2) two ||
|
||||
""")
|
||||
expect(data).toXMLEqual("""
|
||||
<problem>
|
||||
<optionresponse>
|
||||
<p>Translation between Dropdown and ________ is straightforward.</p>
|
||||
<optioninput>
|
||||
<option correct="True">Right <optionhint label="Good Job">yes</optionhint>
|
||||
</option>
|
||||
<option correct="False">Wrong 1 <optionhint>no</optionhint>
|
||||
</option>
|
||||
<option correct="False">Wrong 2 <optionhint label="Label">no</optionhint>
|
||||
</option>
|
||||
</optioninput>
|
||||
</optionresponse>
|
||||
|
||||
<demandhint>
|
||||
<hint>0) zero</hint>
|
||||
<hint>1) one</hint>
|
||||
<hint>2) two</hint>
|
||||
</demandhint>
|
||||
</problem>
|
||||
""")
|
||||
|
||||
it 'produces xml with single-line markdown syntax', ->
|
||||
data = MarkdownEditingDescriptor.markdownToXml("""
|
||||
A Question ________ is answered.
|
||||
|
||||
[[(Right), Wrong 1, Wrong 2]]
|
||||
|| 0) zero ||
|
||||
|| 1) one ||
|
||||
""")
|
||||
expect(data).toXMLEqual("""
|
||||
<problem>
|
||||
<optionresponse>
|
||||
<p>A Question ________ is answered.</p>
|
||||
<optioninput options="('Right','Wrong 1','Wrong 2')" correct="Right"/>
|
||||
</optionresponse>
|
||||
|
||||
<demandhint>
|
||||
<hint>0) zero</hint>
|
||||
<hint>1) one</hint>
|
||||
</demandhint>
|
||||
</problem>
|
||||
""")
|
||||
|
||||
it 'produces xml with fewer newlines', ->
|
||||
data = MarkdownEditingDescriptor.markdownToXml("""
|
||||
>>q1<<
|
||||
[[ (aa) {{ hint1 }}
|
||||
bb
|
||||
cc {{ hint2 }} ]]
|
||||
""")
|
||||
expect(data).toXMLEqual("""
|
||||
<problem>
|
||||
<optionresponse>
|
||||
<label>q1</label>
|
||||
<optioninput>
|
||||
<option correct="True">aa <optionhint>hint1</optionhint>
|
||||
</option>
|
||||
<option correct="False">bb</option>
|
||||
<option correct="False">cc <optionhint>hint2</optionhint>
|
||||
</option>
|
||||
</optioninput>
|
||||
</optionresponse>
|
||||
|
||||
|
||||
</problem>
|
||||
""")
|
||||
|
||||
it 'produces xml even with lots of whitespace', ->
|
||||
data = MarkdownEditingDescriptor.markdownToXml("""
|
||||
>>q1<<
|
||||
[[
|
||||
|
||||
|
||||
aa {{ hint1 }}
|
||||
|
||||
bb {{ hint2 }}
|
||||
(cc)
|
||||
|
||||
]]
|
||||
""")
|
||||
expect(data).toXMLEqual("""
|
||||
<problem>
|
||||
<optionresponse>
|
||||
<label>q1</label>
|
||||
<optioninput>
|
||||
<option correct="False">aa <optionhint>hint1</optionhint>
|
||||
</option>
|
||||
<option correct="False">bb <optionhint>hint2</optionhint>
|
||||
</option>
|
||||
<option correct="True">cc</option>
|
||||
</optioninput>
|
||||
</optionresponse>
|
||||
|
||||
|
||||
</problem>
|
||||
""")
|
||||
|
||||
describe 'Markdown to xml extended hint checkbox', ->
|
||||
it 'produces xml', ->
|
||||
data = MarkdownEditingDescriptor.markdownToXml("""
|
||||
>>Select all the fruits from the list<<
|
||||
|
||||
[x] Apple {{ selected: You're right that apple is a fruit. }, {unselected: Remember that apple is also a fruit.}}
|
||||
[ ] Mushroom {{U: You're right that mushrooms aren't fruit}, { selected: Mushroom is a fungus, not a fruit.}}
|
||||
[x] Grape {{ selected: You're right that grape is a fruit }, {unselected: Remember that grape is also a fruit.}}
|
||||
[ ] Mustang
|
||||
[ ] Camero {{S:I don't know what a Camero is but it isn't a fruit.},{U:What is a camero anyway?}}
|
||||
|
||||
|
||||
{{ ((A*B)) You're right that apple is a fruit, but there's one you're missing. Also, mushroom is not a fruit.}}
|
||||
{{ ((B*C)) You're right that grape is a fruit, but there's one you're missing. Also, mushroom is not a fruit. }}
|
||||
|
||||
|
||||
>>Select all the vegetables from the list<<
|
||||
|
||||
[ ] Banana {{ selected: No, sorry, a banana is a fruit. }, {unselected: poor banana.}}
|
||||
[ ] Ice Cream
|
||||
[ ] Mushroom {{U: You're right that mushrooms aren't vegetables.}, { selected: Mushroom is a fungus, not a vegetable.}}
|
||||
[x] Brussel Sprout {{S: Brussel sprouts are vegetables.}, {u: Brussel sprout is the only vegetable in this list.}}
|
||||
|
||||
|
||||
{{ ((A*B)) Making a banana split? }}
|
||||
{{ ((B*D)) That will make a horrible dessert: a brussel sprout split? }}
|
||||
""")
|
||||
expect(data).toXMLEqual("""
|
||||
<problem>
|
||||
<label>Select all the fruits from the list</label>
|
||||
<choiceresponse>
|
||||
<checkboxgroup>
|
||||
<choice correct="true">Apple
|
||||
<choicehint selected="true">You're right that apple is a fruit.</choicehint>
|
||||
<choicehint selected="false">Remember that apple is also a fruit.</choicehint>
|
||||
</choice>
|
||||
<choice correct="false">Mushroom
|
||||
<choicehint selected="true">Mushroom is a fungus, not a fruit.</choicehint>
|
||||
<choicehint selected="false">You're right that mushrooms aren't fruit</choicehint>
|
||||
</choice>
|
||||
<choice correct="true">Grape
|
||||
<choicehint selected="true">You're right that grape is a fruit</choicehint>
|
||||
<choicehint selected="false">Remember that grape is also a fruit.</choicehint>
|
||||
</choice>
|
||||
<choice correct="false">Mustang</choice>
|
||||
<choice correct="false">Camero
|
||||
<choicehint selected="true">I don't know what a Camero is but it isn't a fruit.</choicehint>
|
||||
<choicehint selected="false">What is a camero anyway?</choicehint>
|
||||
</choice>
|
||||
<compoundhint value="A*B">You're right that apple is a fruit, but there's one you're missing. Also, mushroom is not a fruit.</compoundhint>
|
||||
<compoundhint value="B*C">You're right that grape is a fruit, but there's one you're missing. Also, mushroom is not a fruit.</compoundhint>
|
||||
</checkboxgroup>
|
||||
</choiceresponse>
|
||||
|
||||
<label>Select all the vegetables from the list</label>
|
||||
<choiceresponse>
|
||||
<checkboxgroup>
|
||||
<choice correct="false">Banana
|
||||
<choicehint selected="true">No, sorry, a banana is a fruit.</choicehint>
|
||||
<choicehint selected="false">poor banana.</choicehint>
|
||||
</choice>
|
||||
<choice correct="false">Ice Cream</choice>
|
||||
<choice correct="false">Mushroom
|
||||
<choicehint selected="true">Mushroom is a fungus, not a vegetable.</choicehint>
|
||||
<choicehint selected="false">You're right that mushrooms aren't vegetables.</choicehint>
|
||||
</choice>
|
||||
<choice correct="true">Brussel Sprout
|
||||
<choicehint selected="true">Brussel sprouts are vegetables.</choicehint>
|
||||
<choicehint selected="false">Brussel sprout is the only vegetable in this list.</choicehint>
|
||||
</choice>
|
||||
<compoundhint value="A*B">Making a banana split?</compoundhint>
|
||||
<compoundhint value="B*D">That will make a horrible dessert: a brussel sprout split?</compoundhint>
|
||||
</checkboxgroup>
|
||||
</choiceresponse>
|
||||
</problem>
|
||||
""")
|
||||
|
||||
it 'produces xml also with demand hints', ->
|
||||
data = MarkdownEditingDescriptor.markdownToXml("""
|
||||
>>Select all the fruits from the list<<
|
||||
|
||||
[x] Apple {{ selected: You're right that apple is a fruit. }, {unselected: Remember that apple is also a fruit.}}
|
||||
[ ] Mushroom {{U: You're right that mushrooms aren't fruit}, { selected: Mushroom is a fungus, not a fruit.}}
|
||||
[x] Grape {{ selected: You're right that grape is a fruit }, {unselected: Remember that grape is also a fruit.}}
|
||||
[ ] Mustang
|
||||
[ ] Camero {{S:I don't know what a Camero is but it isn't a fruit.},{U:What is a camero anyway?}}
|
||||
|
||||
{{ ((A*B)) You're right that apple is a fruit, but there's one you're missing. Also, mushroom is not a fruit.}}
|
||||
{{ ((B*C)) You're right that grape is a fruit, but there's one you're missing. Also, mushroom is not a fruit.}}
|
||||
|
||||
>>Select all the vegetables from the list<<
|
||||
|
||||
[ ] Banana {{ selected: No, sorry, a banana is a fruit. }, {unselected: poor banana.}}
|
||||
[ ] Ice Cream
|
||||
[ ] Mushroom {{U: You're right that mushrooms aren't vegatbles}, { selected: Mushroom is a fungus, not a vegetable.}}
|
||||
[x] Brussel Sprout {{S: Brussel sprouts are vegetables.}, {u: Brussel sprout is the only vegetable in this list.}}
|
||||
|
||||
{{ ((A*B)) Making a banana split? }}
|
||||
{{ ((B*D)) That will make a horrible dessert: a brussel sprout split? }}
|
||||
|
||||
|| Hint one.||
|
||||
|| Hint two. ||
|
||||
|| Hint three. ||
|
||||
""")
|
||||
expect(data).toXMLEqual("""
|
||||
<problem>
|
||||
<label>Select all the fruits from the list</label>
|
||||
<choiceresponse>
|
||||
<checkboxgroup>
|
||||
<choice correct="true">Apple
|
||||
<choicehint selected="true">You're right that apple is a fruit.</choicehint>
|
||||
<choicehint selected="false">Remember that apple is also a fruit.</choicehint>
|
||||
</choice>
|
||||
<choice correct="false">Mushroom
|
||||
<choicehint selected="true">Mushroom is a fungus, not a fruit.</choicehint>
|
||||
<choicehint selected="false">You're right that mushrooms aren't fruit</choicehint>
|
||||
</choice>
|
||||
<choice correct="true">Grape
|
||||
<choicehint selected="true">You're right that grape is a fruit</choicehint>
|
||||
<choicehint selected="false">Remember that grape is also a fruit.</choicehint>
|
||||
</choice>
|
||||
<choice correct="false">Mustang</choice>
|
||||
<choice correct="false">Camero
|
||||
<choicehint selected="true">I don't know what a Camero is but it isn't a fruit.</choicehint>
|
||||
<choicehint selected="false">What is a camero anyway?</choicehint>
|
||||
</choice>
|
||||
<compoundhint value="A*B">You're right that apple is a fruit, but there's one you're missing. Also, mushroom is not a fruit.</compoundhint>
|
||||
<compoundhint value="B*C">You're right that grape is a fruit, but there's one you're missing. Also, mushroom is not a fruit.</compoundhint>
|
||||
</checkboxgroup>
|
||||
</choiceresponse>
|
||||
|
||||
<label>Select all the vegetables from the list</label>
|
||||
<choiceresponse>
|
||||
<checkboxgroup>
|
||||
<choice correct="false">Banana
|
||||
<choicehint selected="true">No, sorry, a banana is a fruit.</choicehint>
|
||||
<choicehint selected="false">poor banana.</choicehint>
|
||||
</choice>
|
||||
<choice correct="false">Ice Cream</choice>
|
||||
<choice correct="false">Mushroom
|
||||
<choicehint selected="true">Mushroom is a fungus, not a vegetable.</choicehint>
|
||||
<choicehint selected="false">You're right that mushrooms aren't vegatbles</choicehint>
|
||||
</choice>
|
||||
<choice correct="true">Brussel Sprout
|
||||
<choicehint selected="true">Brussel sprouts are vegetables.</choicehint>
|
||||
<choicehint selected="false">Brussel sprout is the only vegetable in this list.</choicehint>
|
||||
</choice>
|
||||
<compoundhint value="A*B">Making a banana split?</compoundhint>
|
||||
<compoundhint value="B*D">That will make a horrible dessert: a brussel sprout split?</compoundhint>
|
||||
</checkboxgroup>
|
||||
</choiceresponse>
|
||||
|
||||
<demandhint>
|
||||
<hint>Hint one.</hint>
|
||||
<hint>Hint two.</hint>
|
||||
<hint>Hint three.</hint>
|
||||
</demandhint>
|
||||
</problem>
|
||||
""")
|
||||
|
||||
|
||||
describe 'Markdown to xml extended hint multiple choice', ->
|
||||
it 'produces xml', ->
|
||||
data = MarkdownEditingDescriptor.markdownToXml("""
|
||||
>>Select the fruit from the list<<
|
||||
|
||||
() Mushroom {{ Mushroom is a fungus, not a fruit.}}
|
||||
() Potato
|
||||
(x) Apple {{ OUTSTANDING::Apple is indeed a fruit.}}
|
||||
|
||||
>>Select the vegetables from the list<<
|
||||
|
||||
() Mushroom {{ Mushroom is a fungus, not a vegetable.}}
|
||||
(x) Potato {{ Potato is a root vegetable. }}
|
||||
() Apple {{ OOPS::Apple is a fruit.}}
|
||||
""")
|
||||
expect(data).toXMLEqual("""
|
||||
<problem>
|
||||
<label>Select the fruit from the list</label>
|
||||
<multiplechoiceresponse>
|
||||
<choicegroup type="MultipleChoice">
|
||||
<choice correct="false">Mushroom
|
||||
<choicehint>Mushroom is a fungus, not a fruit.</choicehint>
|
||||
</choice>
|
||||
<choice correct="false">Potato</choice>
|
||||
<choice correct="true">Apple
|
||||
<choicehint label="OUTSTANDING">Apple is indeed a fruit.</choicehint>
|
||||
</choice>
|
||||
</choicegroup>
|
||||
</multiplechoiceresponse>
|
||||
|
||||
<label>Select the vegetables from the list</label>
|
||||
<multiplechoiceresponse>
|
||||
<choicegroup type="MultipleChoice">
|
||||
<choice correct="false">Mushroom
|
||||
<choicehint>Mushroom is a fungus, not a vegetable.</choicehint>
|
||||
</choice>
|
||||
<choice correct="true">Potato
|
||||
<choicehint>Potato is a root vegetable.</choicehint>
|
||||
</choice>
|
||||
<choice correct="false">Apple
|
||||
<choicehint label="OOPS">Apple is a fruit.</choicehint>
|
||||
</choice>
|
||||
</choicegroup>
|
||||
</multiplechoiceresponse>
|
||||
</problem>
|
||||
""")
|
||||
|
||||
it 'produces xml with demand hints', ->
|
||||
data = MarkdownEditingDescriptor.markdownToXml("""
|
||||
>>Select the fruit from the list<<
|
||||
|
||||
() Mushroom {{ Mushroom is a fungus, not a fruit.}}
|
||||
() Potato
|
||||
(x) Apple {{ OUTSTANDING::Apple is indeed a fruit.}}
|
||||
|
||||
|| 0) spaces on previous line. ||
|
||||
|| 1) roses are red. ||
|
||||
|
||||
>>Select the vegetables from the list<<
|
||||
|
||||
() Mushroom {{ Mushroom is a fungus, not a vegetable.}}
|
||||
(x) Potato {{ Potato is a root vegetable. }}
|
||||
() Apple {{ OOPS::Apple is a fruit.}}
|
||||
|
||||
|| 2) where are the lions? ||
|
||||
|
||||
""")
|
||||
expect(data).toXMLEqual("""
|
||||
<problem>
|
||||
<label>Select the fruit from the list</label>
|
||||
<multiplechoiceresponse>
|
||||
<choicegroup type="MultipleChoice">
|
||||
<choice correct="false">Mushroom
|
||||
<choicehint>Mushroom is a fungus, not a fruit.</choicehint>
|
||||
</choice>
|
||||
<choice correct="false">Potato</choice>
|
||||
<choice correct="true">Apple
|
||||
<choicehint label="OUTSTANDING">Apple is indeed a fruit.</choicehint>
|
||||
</choice>
|
||||
</choicegroup>
|
||||
</multiplechoiceresponse>
|
||||
|
||||
<label>Select the vegetables from the list</label>
|
||||
<multiplechoiceresponse>
|
||||
<choicegroup type="MultipleChoice">
|
||||
<choice correct="false">Mushroom
|
||||
<choicehint>Mushroom is a fungus, not a vegetable.</choicehint>
|
||||
</choice>
|
||||
<choice correct="true">Potato
|
||||
<choicehint>Potato is a root vegetable.</choicehint>
|
||||
</choice>
|
||||
<choice correct="false">Apple
|
||||
<choicehint label="OOPS">Apple is a fruit.</choicehint>
|
||||
</choice>
|
||||
</choicegroup>
|
||||
</multiplechoiceresponse>
|
||||
|
||||
<demandhint>
|
||||
<hint>0) spaces on previous line.</hint>
|
||||
<hint>1) roses are red.</hint>
|
||||
<hint>2) where are the lions?</hint>
|
||||
</demandhint>
|
||||
</problem>
|
||||
""")
|
||||
|
||||
|
||||
describe 'Markdown to xml extended hint text input', ->
|
||||
it 'produces xml', ->
|
||||
data = MarkdownEditingDescriptor.markdownToXml(""">>In which country would you find the city of Paris?<<
|
||||
= France {{ BRAVO::Viva la France! }}
|
||||
|
||||
""")
|
||||
expect(data).toXMLEqual("""
|
||||
<problem>
|
||||
<stringresponse answer="France" type="ci">
|
||||
<label>In which country would you find the city of Paris?</label>
|
||||
<correcthint label="BRAVO">Viva la France!</correcthint>
|
||||
<textline size="20"/>
|
||||
</stringresponse>
|
||||
|
||||
|
||||
</problem>
|
||||
""")
|
||||
|
||||
it 'produces xml with or=', ->
|
||||
data = MarkdownEditingDescriptor.markdownToXml(""">>Where Paris?<<
|
||||
= France {{ BRAVO::hint1}}
|
||||
or= USA {{ meh::hint2 }}
|
||||
|
||||
""")
|
||||
expect(data).toXMLEqual("""
|
||||
<problem>
|
||||
<stringresponse answer="France" type="ci">
|
||||
<label>Where Paris?</label>
|
||||
<correcthint label="BRAVO">hint1</correcthint>
|
||||
<additional_answer answer="USA"><correcthint label="meh">hint2</correcthint>
|
||||
</additional_answer>
|
||||
<textline size="20"/>
|
||||
</stringresponse>
|
||||
|
||||
|
||||
</problem>
|
||||
""")
|
||||
|
||||
it 'produces xml with not=', ->
|
||||
data = MarkdownEditingDescriptor.markdownToXml(""">>Revenge is a dish best served<<
|
||||
= cold {{khaaaaaan!}}
|
||||
not= warm {{feedback2}}
|
||||
|
||||
""")
|
||||
expect(data).toXMLEqual("""
|
||||
<problem>
|
||||
<stringresponse answer="cold" type="ci">
|
||||
<label>Revenge is a dish best served</label>
|
||||
<correcthint>khaaaaaan!</correcthint>
|
||||
<stringequalhint answer="warm">feedback2</stringequalhint>
|
||||
<textline size="20"/>
|
||||
</stringresponse>
|
||||
|
||||
|
||||
</problem>
|
||||
""")
|
||||
|
||||
it 'produces xml with s=', ->
|
||||
data = MarkdownEditingDescriptor.markdownToXml(""">>q<<
|
||||
s= 2 {{feedback1}}
|
||||
|
||||
""")
|
||||
expect(data).toXMLEqual("""
|
||||
<problem>
|
||||
<stringresponse answer="2" type="ci">
|
||||
<label>q</label>
|
||||
<correcthint>feedback1</correcthint>
|
||||
<textline size="20"/>
|
||||
</stringresponse>
|
||||
|
||||
|
||||
</problem>
|
||||
""")
|
||||
|
||||
it 'produces xml with = and or= and not=', ->
|
||||
data = MarkdownEditingDescriptor.markdownToXml(""">>q<<
|
||||
= aaa
|
||||
or= bbb {{feedback1}}
|
||||
not= no {{feedback2}}
|
||||
or= ccc
|
||||
|
||||
""")
|
||||
expect(data).toXMLEqual("""
|
||||
<problem>
|
||||
<stringresponse answer="aaa" type="ci">
|
||||
<label>q</label>
|
||||
<additional_answer answer="bbb"><correcthint>feedback1</correcthint>
|
||||
</additional_answer>
|
||||
<stringequalhint answer="no">feedback2</stringequalhint>
|
||||
<additional_answer answer="ccc"/>
|
||||
<textline size="20"/>
|
||||
</stringresponse>
|
||||
|
||||
|
||||
</problem>
|
||||
""")
|
||||
|
||||
it 'produces xml with s= and or=', ->
|
||||
data = MarkdownEditingDescriptor.markdownToXml(""">>q<<
|
||||
s= 2 {{feedback1}}
|
||||
or= bbb {{feedback2}}
|
||||
or= ccc
|
||||
|
||||
""")
|
||||
expect(data).toXMLEqual("""
|
||||
<problem>
|
||||
<stringresponse answer="2" type="ci">
|
||||
<label>q</label>
|
||||
<correcthint>feedback1</correcthint>
|
||||
<additional_answer answer="bbb"><correcthint>feedback2</correcthint>
|
||||
</additional_answer>
|
||||
<additional_answer answer="ccc"/>
|
||||
<textline size="20"/>
|
||||
</stringresponse>
|
||||
|
||||
|
||||
</problem>
|
||||
""")
|
||||
|
||||
it 'produces xml with each = making a new question', ->
|
||||
data = MarkdownEditingDescriptor.markdownToXml("""
|
||||
>>q<<
|
||||
= aaa
|
||||
or= bbb
|
||||
s= ccc
|
||||
""")
|
||||
expect(data).toXMLEqual("""
|
||||
<problem>
|
||||
<label>q</label>
|
||||
<stringresponse answer="aaa" type="ci">
|
||||
<additional_answer answer="bbb"></additional_answer>
|
||||
<textline size="20"/>
|
||||
</stringresponse>
|
||||
<stringresponse answer="ccc" type="ci">
|
||||
<textline size="20"/>
|
||||
</stringresponse>
|
||||
</problem>
|
||||
""")
|
||||
|
||||
it 'produces xml with each = making a new question amid blank lines and paragraphs', ->
|
||||
data = MarkdownEditingDescriptor.markdownToXml("""
|
||||
paragraph
|
||||
>>q<<
|
||||
= aaa
|
||||
|
||||
or= bbb
|
||||
s= ccc
|
||||
|
||||
paragraph 2
|
||||
|
||||
""")
|
||||
expect(data).toXMLEqual("""
|
||||
<problem>
|
||||
<p>paragraph</p>
|
||||
<label>q</label>
|
||||
<stringresponse answer="aaa" type="ci">
|
||||
<additional_answer answer="bbb"></additional_answer>
|
||||
<textline size="20"/>
|
||||
</stringresponse>
|
||||
<stringresponse answer="ccc" type="ci">
|
||||
<textline size="20"/>
|
||||
</stringresponse>
|
||||
<p>paragraph 2</p>
|
||||
</problem>
|
||||
""")
|
||||
|
||||
it 'produces xml without a question when or= is just hung out there by itself', ->
|
||||
data = MarkdownEditingDescriptor.markdownToXml("""
|
||||
paragraph
|
||||
>>q<<
|
||||
or= aaa
|
||||
paragraph 2
|
||||
|
||||
""")
|
||||
expect(data).toXMLEqual("""
|
||||
<problem>
|
||||
<p>paragraph</p>
|
||||
<label>q</label>
|
||||
<p>or= aaa</p>
|
||||
<p>paragraph 2</p>
|
||||
</problem>
|
||||
""")
|
||||
|
||||
it 'produces xml with each = with feedback making a new question', ->
|
||||
data = MarkdownEditingDescriptor.markdownToXml("""
|
||||
>>q<<
|
||||
s= aaa
|
||||
or= bbb {{feedback1}}
|
||||
= ccc {{feedback2}}
|
||||
|
||||
""")
|
||||
expect(data).toXMLEqual("""
|
||||
<problem>
|
||||
<label>q</label>
|
||||
<stringresponse answer="aaa" type="ci">
|
||||
<additional_answer answer="bbb">
|
||||
<correcthint>feedback1</correcthint>
|
||||
</additional_answer>
|
||||
<textline size="20"/>
|
||||
</stringresponse>
|
||||
<stringresponse answer="ccc" type="ci">
|
||||
<correcthint>feedback2</correcthint>
|
||||
<textline size="20"/>
|
||||
</stringresponse>
|
||||
</problem>
|
||||
""")
|
||||
|
||||
it 'produces xml with demand hints', ->
|
||||
data = MarkdownEditingDescriptor.markdownToXml(""">>Where Paris?<<
|
||||
= France {{ BRAVO::hint1 }}
|
||||
|
||||
|| There are actually two countries with cities named Paris. ||
|
||||
|| Paris is the capital of one of those countries. ||
|
||||
|
||||
""")
|
||||
expect(data).toXMLEqual("""
|
||||
<problem>
|
||||
<stringresponse answer="France" type="ci">
|
||||
<label>Where Paris?</label>
|
||||
<correcthint label="BRAVO">hint1</correcthint>
|
||||
<textline size="20"/>
|
||||
</stringresponse>
|
||||
|
||||
<demandhint>
|
||||
<hint>There are actually two countries with cities named Paris.</hint>
|
||||
<hint>Paris is the capital of one of those countries.</hint>
|
||||
</demandhint>
|
||||
</problem>""")
|
||||
|
||||
|
||||
describe 'Markdown to xml extended hint numeric input', ->
|
||||
it 'produces xml', ->
|
||||
data = MarkdownEditingDescriptor.markdownToXml("""
|
||||
>>Enter the numerical value of Pi:<<
|
||||
= 3.14159 +- .02 {{ Pie for everyone! }}
|
||||
|
||||
>>Enter the approximate value of 502*9:<<
|
||||
= 4518 +- 15% {{PIE:: No pie for you!}}
|
||||
|
||||
>>Enter the number of fingers on a human hand<<
|
||||
= 5
|
||||
|
||||
""")
|
||||
expect(data).toXMLEqual("""
|
||||
<problem>
|
||||
<label>Enter the numerical value of Pi:</label>
|
||||
<numericalresponse answer="3.14159">
|
||||
<responseparam type="tolerance" default=".02"/>
|
||||
<formulaequationinput/>
|
||||
<correcthint>Pie for everyone!</correcthint>
|
||||
</numericalresponse>
|
||||
|
||||
<label>Enter the approximate value of 502*9:</label>
|
||||
<numericalresponse answer="4518">
|
||||
<responseparam type="tolerance" default="15%"/>
|
||||
<formulaequationinput/>
|
||||
<correcthint label="PIE">No pie for you!</correcthint>
|
||||
</numericalresponse>
|
||||
|
||||
<label>Enter the number of fingers on a human hand</label>
|
||||
<numericalresponse answer="5">
|
||||
<formulaequationinput/>
|
||||
</numericalresponse>
|
||||
</problem>
|
||||
""")
|
||||
|
||||
# The output xml here shows some of the quirks of how historical markdown parsing does or does not put
|
||||
# in blank lines.
|
||||
it 'numeric input with hints and demand hints', ->
|
||||
data = MarkdownEditingDescriptor.markdownToXml("""
|
||||
>>text1<<
|
||||
= 1 {{ hint1 }}
|
||||
|| hintA ||
|
||||
>>text2<<
|
||||
= 2 {{ hint2 }}
|
||||
|
||||
|| hintB ||
|
||||
|
||||
""")
|
||||
expect(data).toXMLEqual("""
|
||||
<problem>
|
||||
<label>text1</label>
|
||||
<numericalresponse answer="1">
|
||||
<formulaequationinput/>
|
||||
<correcthint>hint1</correcthint>
|
||||
</numericalresponse>
|
||||
<label>text2</label>
|
||||
<numericalresponse answer="2">
|
||||
<formulaequationinput/>
|
||||
<correcthint>hint2</correcthint>
|
||||
</numericalresponse>
|
||||
|
||||
<demandhint>
|
||||
<hint>hintA</hint>
|
||||
<hint>hintB</hint>
|
||||
</demandhint>
|
||||
</problem>
|
||||
""")
|
||||
|
||||
|
||||
describe 'Markdown to xml extended hint with multiline hints', ->
|
||||
it 'produces xml', ->
|
||||
data = MarkdownEditingDescriptor.markdownToXml("""
|
||||
>>Checkboxes<<
|
||||
|
||||
[x] A {{
|
||||
selected: aaa },
|
||||
{unselected:bbb}}
|
||||
[ ] B {{U: c}, {
|
||||
selected: d.}}
|
||||
|
||||
{{ ((A*B)) A*B hint}}
|
||||
|
||||
>>What is 1 + 1?<<
|
||||
= 2 {{ part one, and
|
||||
part two
|
||||
}}
|
||||
|
||||
>>hello?<<
|
||||
= hello {{
|
||||
hello
|
||||
hint
|
||||
}}
|
||||
|
||||
>>multiple choice<<
|
||||
(x) AA{{hint1}}
|
||||
() BB {{
|
||||
hint2
|
||||
}}
|
||||
( ) CC {{ hint3
|
||||
}}
|
||||
|
||||
>>dropdown<<
|
||||
[[
|
||||
W1 {{
|
||||
no }}
|
||||
W2 {{
|
||||
nope}}
|
||||
(C1) {{ yes
|
||||
}}
|
||||
]]
|
||||
|
||||
|| aaa ||
|
||||
||bbb||
|
||||
|| ccc ||
|
||||
|
||||
""")
|
||||
expect(data).toXMLEqual("""
|
||||
<problem>
|
||||
<label>Checkboxes</label>
|
||||
<choiceresponse>
|
||||
<checkboxgroup>
|
||||
<choice correct="true">A
|
||||
<choicehint selected="true">aaa</choicehint>
|
||||
<choicehint selected="false">bbb</choicehint>
|
||||
</choice>
|
||||
<choice correct="false">B
|
||||
<choicehint selected="true">d.</choicehint>
|
||||
<choicehint selected="false">c</choicehint>
|
||||
</choice>
|
||||
<compoundhint value="A*B">A*B hint</compoundhint>
|
||||
</checkboxgroup>
|
||||
</choiceresponse>
|
||||
|
||||
<label>What is 1 + 1?</label>
|
||||
<numericalresponse answer="2">
|
||||
<formulaequationinput/>
|
||||
<correcthint>part one, and part two</correcthint>
|
||||
</numericalresponse>
|
||||
|
||||
<label>hello?</label>
|
||||
<stringresponse answer="hello" type="ci">
|
||||
<correcthint>hello hint</correcthint>
|
||||
<textline size="20"/>
|
||||
</stringresponse>
|
||||
|
||||
<label>multiple choice</label>
|
||||
<multiplechoiceresponse>
|
||||
<choicegroup type="MultipleChoice">
|
||||
<choice correct="true">AA
|
||||
<choicehint>hint1</choicehint>
|
||||
</choice>
|
||||
<choice correct="false">BB
|
||||
<choicehint>hint2</choicehint>
|
||||
</choice>
|
||||
<choice correct="false">CC
|
||||
<choicehint>hint3</choicehint>
|
||||
</choice>
|
||||
</choicegroup>
|
||||
</multiplechoiceresponse>
|
||||
|
||||
<label>dropdown</label>
|
||||
<optionresponse>
|
||||
<optioninput>
|
||||
<option correct="False">W1
|
||||
<optionhint>no</optionhint>
|
||||
</option>
|
||||
<option correct="False">W2
|
||||
<optionhint>nope</optionhint>
|
||||
</option>
|
||||
<option correct="True">C1
|
||||
<optionhint>yes</optionhint>
|
||||
</option>
|
||||
</optioninput>
|
||||
</optionresponse>
|
||||
|
||||
<demandhint>
|
||||
<hint>aaa</hint>
|
||||
<hint>bbb</hint>
|
||||
<hint>ccc</hint>
|
||||
</demandhint>
|
||||
</problem>
|
||||
""")
|
||||
|
||||
describe 'Markdown to xml extended hint with tricky syntax cases', ->
|
||||
# I'm entering this as utf-8 in this file.
|
||||
# I cannot find a way to set the encoding for .coffee files but it seems to work.
|
||||
it 'produces xml with unicode', ->
|
||||
data = MarkdownEditingDescriptor.markdownToXml("""
|
||||
>>á and Ø<<
|
||||
|
||||
(x) Ø{{Ø}}
|
||||
() BB
|
||||
|
||||
|| Ø ||
|
||||
|
||||
""")
|
||||
expect(data).toXMLEqual("""
|
||||
<problem>
|
||||
<multiplechoiceresponse>
|
||||
<label>á and Ø</label>
|
||||
<choicegroup type="MultipleChoice">
|
||||
<choice correct="true">Ø
|
||||
<choicehint>Ø</choicehint>
|
||||
</choice>
|
||||
<choice correct="false">BB</choice>
|
||||
</choicegroup>
|
||||
</multiplechoiceresponse>
|
||||
|
||||
<demandhint>
|
||||
<hint>Ø</hint>
|
||||
</demandhint>
|
||||
</problem>
|
||||
""")
|
||||
|
||||
it 'produces xml with quote-type characters', ->
|
||||
data = MarkdownEditingDescriptor.markdownToXml("""
|
||||
>>"quotes" aren't `fun`<<
|
||||
() "hello" {{ isn't }}
|
||||
(x) "isn't" {{ "hello" }}
|
||||
|
||||
""")
|
||||
expect(data).toXMLEqual("""
|
||||
<problem>
|
||||
<multiplechoiceresponse>
|
||||
<label>"quotes" aren't `fun`</label>
|
||||
<choicegroup type="MultipleChoice">
|
||||
<choice correct="false">"hello"
|
||||
<choicehint>isn't</choicehint>
|
||||
</choice>
|
||||
<choice correct="true">"isn't"
|
||||
<choicehint>"hello"</choicehint>
|
||||
</choice>
|
||||
</choicegroup>
|
||||
</multiplechoiceresponse>
|
||||
</problem>
|
||||
""")
|
||||
|
||||
it 'produces xml with almost but not quite multiple choice syntax', ->
|
||||
data = MarkdownEditingDescriptor.markdownToXml("""
|
||||
>>q1<<
|
||||
this (x)
|
||||
() a {{ (hint) }}
|
||||
(x) b
|
||||
that (y)
|
||||
""")
|
||||
expect(data).toXMLEqual("""
|
||||
<problem>
|
||||
<multiplechoiceresponse>
|
||||
<label>q1</label>
|
||||
<p>this (x)</p>
|
||||
<choicegroup type="MultipleChoice">
|
||||
<choice correct="false">a <choicehint>(hint)</choicehint>
|
||||
</choice>
|
||||
<choice correct="true">b</choice>
|
||||
</choicegroup>
|
||||
<p>that (y)</p>
|
||||
</multiplechoiceresponse>
|
||||
|
||||
|
||||
</problem>
|
||||
""")
|
||||
|
||||
# An incomplete checkbox hint passes through to cue the author
|
||||
it 'produce xml with almost but not quite checkboxgroup syntax', ->
|
||||
data = MarkdownEditingDescriptor.markdownToXml("""
|
||||
>>q1<<
|
||||
this [x]
|
||||
[ ] a [square]
|
||||
[x] b {{ this hint passes through }}
|
||||
that []
|
||||
""")
|
||||
expect(data).toXMLEqual("""
|
||||
<problem>
|
||||
<choiceresponse>
|
||||
<label>q1</label>
|
||||
<p>this [x]</p>
|
||||
<checkboxgroup>
|
||||
<choice correct="false">a [square]</choice>
|
||||
<choice correct="true">b {{ this hint passes through }}</choice>
|
||||
</checkboxgroup>
|
||||
<p>that []</p>
|
||||
</choiceresponse>
|
||||
|
||||
|
||||
</problem>
|
||||
""")
|
||||
|
||||
# It's sort of a pain to edit DOS line endings without some editor or other "fixing" them
|
||||
# for you. Therefore, we construct DOS line endings on the fly just for the test.
|
||||
it 'produces xml with DOS \r\n line endings', ->
|
||||
markdown = """
|
||||
>>q22<<
|
||||
|
||||
[[
|
||||
(x) {{ hintx
|
||||
these
|
||||
span
|
||||
}}
|
||||
|
||||
yy {{ meh::hinty }}
|
||||
zzz {{ hintz }}
|
||||
]]
|
||||
"""
|
||||
markdown = markdown.replace(/\n/g, '\r\n') # make DOS line endings
|
||||
data = MarkdownEditingDescriptor.markdownToXml(markdown)
|
||||
expect(data).toXMLEqual("""
|
||||
<problem>
|
||||
<optionresponse>
|
||||
<label>q22</label>
|
||||
<optioninput>
|
||||
<option correct="True">x <optionhint>hintx these span</optionhint>
|
||||
</option>
|
||||
<option correct="False">yy <optionhint label="meh">hinty</optionhint>
|
||||
</option>
|
||||
<option correct="False">zzz <optionhint>hintz</optionhint>
|
||||
</option>
|
||||
</optioninput>
|
||||
</optionresponse>
|
||||
|
||||
|
||||
</problem>
|
||||
""")
|
||||
1031
common/lib/xmodule/xmodule/js/spec/problem/edit_spec_hint.js
Normal file
1031
common/lib/xmodule/xmodule/js/spec/problem/edit_spec_hint.js
Normal file
@@ -0,0 +1,1031 @@
|
||||
// This file tests the parsing of extended-hints, double bracket sections {{ .. }}
|
||||
// for all sorts of markdown.
|
||||
describe('Markdown to xml extended hint dropdown', function() {
|
||||
it('produces xml', function() {
|
||||
const data = MarkdownEditingDescriptor.markdownToXml(`\
|
||||
Translation between Dropdown and ________ is straightforward.
|
||||
|
||||
[[
|
||||
(Multiple Choice) {{ Good Job::Yes, multiple choice is the right answer. }}
|
||||
Text Input {{ No, text input problems don't present options. }}
|
||||
Numerical Input {{ No, numerical input problems don't present options. }}
|
||||
]]
|
||||
|
||||
|
||||
|
||||
Clowns have funny _________ to make people laugh.
|
||||
|
||||
[[
|
||||
dogs {{ NOPE::Not dogs, not cats, not toads }}
|
||||
(FACES) {{ With lots of makeup, doncha know?}}
|
||||
|
||||
money {{ Clowns don't have any money, of course }}
|
||||
donkeys {{don't be an ass.}}
|
||||
-no hint-
|
||||
]]
|
||||
\
|
||||
`);
|
||||
expect(data).toXMLEqual(`\
|
||||
<problem>
|
||||
<p>Translation between Dropdown and ________ is straightforward.</p>
|
||||
<optionresponse>
|
||||
<optioninput>
|
||||
<option correct="True">Multiple Choice
|
||||
<optionhint label="Good Job">Yes, multiple choice is the right answer.</optionhint>
|
||||
</option>
|
||||
<option correct="False">Text Input
|
||||
<optionhint>No, text input problems don't present options.</optionhint>
|
||||
</option>
|
||||
<option correct="False">Numerical Input
|
||||
<optionhint>No, numerical input problems don't present options.</optionhint>
|
||||
</option>
|
||||
</optioninput>
|
||||
</optionresponse>
|
||||
<p>Clowns have funny _________ to make people laugh.</p>
|
||||
<optionresponse>
|
||||
<optioninput>
|
||||
<option correct="False">dogs
|
||||
<optionhint label="NOPE">Not dogs, not cats, not toads</optionhint>
|
||||
</option>
|
||||
<option correct="True">FACES
|
||||
<optionhint>With lots of makeup, doncha know?</optionhint>
|
||||
</option>
|
||||
<option correct="False">money
|
||||
<optionhint>Clowns don't have any money, of course</optionhint>
|
||||
</option>
|
||||
<option correct="False">donkeys
|
||||
<optionhint>don't be an ass.</optionhint>
|
||||
</option>
|
||||
<option correct="False">-no hint-</option>
|
||||
</optioninput>
|
||||
</optionresponse>
|
||||
</problem>\
|
||||
`);
|
||||
});
|
||||
|
||||
it('produces xml with demand hint', function() {
|
||||
const data = MarkdownEditingDescriptor.markdownToXml(`\
|
||||
Translation between Dropdown and ________ is straightforward.
|
||||
|
||||
[[
|
||||
(Right) {{ Good Job::yes }}
|
||||
Wrong 1 {{no}}
|
||||
Wrong 2 {{ Label::no }}
|
||||
]]
|
||||
|
||||
|| 0) zero ||
|
||||
|| 1) one ||
|
||||
|| 2) two ||\
|
||||
`);
|
||||
expect(data).toXMLEqual(`\
|
||||
<problem>
|
||||
<optionresponse>
|
||||
<p>Translation between Dropdown and ________ is straightforward.</p>
|
||||
<optioninput>
|
||||
<option correct="True">Right <optionhint label="Good Job">yes</optionhint>
|
||||
</option>
|
||||
<option correct="False">Wrong 1 <optionhint>no</optionhint>
|
||||
</option>
|
||||
<option correct="False">Wrong 2 <optionhint label="Label">no</optionhint>
|
||||
</option>
|
||||
</optioninput>
|
||||
</optionresponse>
|
||||
|
||||
<demandhint>
|
||||
<hint>0) zero</hint>
|
||||
<hint>1) one</hint>
|
||||
<hint>2) two</hint>
|
||||
</demandhint>
|
||||
</problem>\
|
||||
`);
|
||||
});
|
||||
|
||||
it('produces xml with single-line markdown syntax', function() {
|
||||
const data = MarkdownEditingDescriptor.markdownToXml(`\
|
||||
A Question ________ is answered.
|
||||
|
||||
[[(Right), Wrong 1, Wrong 2]]
|
||||
|| 0) zero ||
|
||||
|| 1) one ||\
|
||||
`);
|
||||
expect(data).toXMLEqual(`\
|
||||
<problem>
|
||||
<optionresponse>
|
||||
<p>A Question ________ is answered.</p>
|
||||
<optioninput options="('Right','Wrong 1','Wrong 2')" correct="Right"/>
|
||||
</optionresponse>
|
||||
|
||||
<demandhint>
|
||||
<hint>0) zero</hint>
|
||||
<hint>1) one</hint>
|
||||
</demandhint>
|
||||
</problem>\
|
||||
`);
|
||||
});
|
||||
|
||||
it('produces xml with fewer newlines', function() {
|
||||
const data = MarkdownEditingDescriptor.markdownToXml(`\
|
||||
>>q1<<
|
||||
[[ (aa) {{ hint1 }}
|
||||
bb
|
||||
cc {{ hint2 }} ]]\
|
||||
`);
|
||||
expect(data).toXMLEqual(`\
|
||||
<problem>
|
||||
<optionresponse>
|
||||
<label>q1</label>
|
||||
<optioninput>
|
||||
<option correct="True">aa <optionhint>hint1</optionhint>
|
||||
</option>
|
||||
<option correct="False">bb</option>
|
||||
<option correct="False">cc <optionhint>hint2</optionhint>
|
||||
</option>
|
||||
</optioninput>
|
||||
</optionresponse>
|
||||
|
||||
|
||||
</problem>\
|
||||
`);
|
||||
});
|
||||
|
||||
it('produces xml even with lots of whitespace', function() {
|
||||
const data = MarkdownEditingDescriptor.markdownToXml(`\
|
||||
>>q1<<
|
||||
[[
|
||||
|
||||
|
||||
aa {{ hint1 }}
|
||||
|
||||
bb {{ hint2 }}
|
||||
(cc)
|
||||
|
||||
]]\
|
||||
`);
|
||||
expect(data).toXMLEqual(`\
|
||||
<problem>
|
||||
<optionresponse>
|
||||
<label>q1</label>
|
||||
<optioninput>
|
||||
<option correct="False">aa <optionhint>hint1</optionhint>
|
||||
</option>
|
||||
<option correct="False">bb <optionhint>hint2</optionhint>
|
||||
</option>
|
||||
<option correct="True">cc</option>
|
||||
</optioninput>
|
||||
</optionresponse>
|
||||
|
||||
|
||||
</problem>\
|
||||
`);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Markdown to xml extended hint checkbox', function() {
|
||||
it('produces xml', function() {
|
||||
const data = MarkdownEditingDescriptor.markdownToXml(`\
|
||||
>>Select all the fruits from the list<<
|
||||
|
||||
[x] Apple {{ selected: You're right that apple is a fruit. }, {unselected: Remember that apple is also a fruit.}}
|
||||
[ ] Mushroom {{U: You're right that mushrooms aren't fruit}, { selected: Mushroom is a fungus, not a fruit.}}
|
||||
[x] Grape {{ selected: You're right that grape is a fruit }, {unselected: Remember that grape is also a fruit.}}
|
||||
[ ] Mustang
|
||||
[ ] Camero {{S:I don't know what a Camero is but it isn't a fruit.},{U:What is a camero anyway?}}
|
||||
|
||||
|
||||
{{ ((A*B)) You're right that apple is a fruit, but there's one you're missing. Also, mushroom is not a fruit.}}
|
||||
{{ ((B*C)) You're right that grape is a fruit, but there's one you're missing. Also, mushroom is not a fruit. }}
|
||||
|
||||
|
||||
>>Select all the vegetables from the list<<
|
||||
|
||||
[ ] Banana {{ selected: No, sorry, a banana is a fruit. }, {unselected: poor banana.}}
|
||||
[ ] Ice Cream
|
||||
[ ] Mushroom {{U: You're right that mushrooms aren't vegetables.}, { selected: Mushroom is a fungus, not a vegetable.}}
|
||||
[x] Brussel Sprout {{S: Brussel sprouts are vegetables.}, {u: Brussel sprout is the only vegetable in this list.}}
|
||||
|
||||
|
||||
{{ ((A*B)) Making a banana split? }}
|
||||
{{ ((B*D)) That will make a horrible dessert: a brussel sprout split? }}\
|
||||
`);
|
||||
expect(data).toXMLEqual(`\
|
||||
<problem>
|
||||
<label>Select all the fruits from the list</label>
|
||||
<choiceresponse>
|
||||
<checkboxgroup>
|
||||
<choice correct="true">Apple
|
||||
<choicehint selected="true">You're right that apple is a fruit.</choicehint>
|
||||
<choicehint selected="false">Remember that apple is also a fruit.</choicehint>
|
||||
</choice>
|
||||
<choice correct="false">Mushroom
|
||||
<choicehint selected="true">Mushroom is a fungus, not a fruit.</choicehint>
|
||||
<choicehint selected="false">You're right that mushrooms aren't fruit</choicehint>
|
||||
</choice>
|
||||
<choice correct="true">Grape
|
||||
<choicehint selected="true">You're right that grape is a fruit</choicehint>
|
||||
<choicehint selected="false">Remember that grape is also a fruit.</choicehint>
|
||||
</choice>
|
||||
<choice correct="false">Mustang</choice>
|
||||
<choice correct="false">Camero
|
||||
<choicehint selected="true">I don't know what a Camero is but it isn't a fruit.</choicehint>
|
||||
<choicehint selected="false">What is a camero anyway?</choicehint>
|
||||
</choice>
|
||||
<compoundhint value="A*B">You're right that apple is a fruit, but there's one you're missing. Also, mushroom is not a fruit.</compoundhint>
|
||||
<compoundhint value="B*C">You're right that grape is a fruit, but there's one you're missing. Also, mushroom is not a fruit.</compoundhint>
|
||||
</checkboxgroup>
|
||||
</choiceresponse>
|
||||
|
||||
<label>Select all the vegetables from the list</label>
|
||||
<choiceresponse>
|
||||
<checkboxgroup>
|
||||
<choice correct="false">Banana
|
||||
<choicehint selected="true">No, sorry, a banana is a fruit.</choicehint>
|
||||
<choicehint selected="false">poor banana.</choicehint>
|
||||
</choice>
|
||||
<choice correct="false">Ice Cream</choice>
|
||||
<choice correct="false">Mushroom
|
||||
<choicehint selected="true">Mushroom is a fungus, not a vegetable.</choicehint>
|
||||
<choicehint selected="false">You're right that mushrooms aren't vegetables.</choicehint>
|
||||
</choice>
|
||||
<choice correct="true">Brussel Sprout
|
||||
<choicehint selected="true">Brussel sprouts are vegetables.</choicehint>
|
||||
<choicehint selected="false">Brussel sprout is the only vegetable in this list.</choicehint>
|
||||
</choice>
|
||||
<compoundhint value="A*B">Making a banana split?</compoundhint>
|
||||
<compoundhint value="B*D">That will make a horrible dessert: a brussel sprout split?</compoundhint>
|
||||
</checkboxgroup>
|
||||
</choiceresponse>
|
||||
</problem>\
|
||||
`);
|
||||
});
|
||||
|
||||
it('produces xml also with demand hints', function() {
|
||||
const data = MarkdownEditingDescriptor.markdownToXml(`\
|
||||
>>Select all the fruits from the list<<
|
||||
|
||||
[x] Apple {{ selected: You're right that apple is a fruit. }, {unselected: Remember that apple is also a fruit.}}
|
||||
[ ] Mushroom {{U: You're right that mushrooms aren't fruit}, { selected: Mushroom is a fungus, not a fruit.}}
|
||||
[x] Grape {{ selected: You're right that grape is a fruit }, {unselected: Remember that grape is also a fruit.}}
|
||||
[ ] Mustang
|
||||
[ ] Camero {{S:I don't know what a Camero is but it isn't a fruit.},{U:What is a camero anyway?}}
|
||||
|
||||
{{ ((A*B)) You're right that apple is a fruit, but there's one you're missing. Also, mushroom is not a fruit.}}
|
||||
{{ ((B*C)) You're right that grape is a fruit, but there's one you're missing. Also, mushroom is not a fruit.}}
|
||||
|
||||
>>Select all the vegetables from the list<<
|
||||
|
||||
[ ] Banana {{ selected: No, sorry, a banana is a fruit. }, {unselected: poor banana.}}
|
||||
[ ] Ice Cream
|
||||
[ ] Mushroom {{U: You're right that mushrooms aren't vegatbles}, { selected: Mushroom is a fungus, not a vegetable.}}
|
||||
[x] Brussel Sprout {{S: Brussel sprouts are vegetables.}, {u: Brussel sprout is the only vegetable in this list.}}
|
||||
|
||||
{{ ((A*B)) Making a banana split? }}
|
||||
{{ ((B*D)) That will make a horrible dessert: a brussel sprout split? }}
|
||||
|
||||
|| Hint one.||
|
||||
|| Hint two. ||
|
||||
|| Hint three. ||\
|
||||
`);
|
||||
expect(data).toXMLEqual(`\
|
||||
<problem>
|
||||
<label>Select all the fruits from the list</label>
|
||||
<choiceresponse>
|
||||
<checkboxgroup>
|
||||
<choice correct="true">Apple
|
||||
<choicehint selected="true">You're right that apple is a fruit.</choicehint>
|
||||
<choicehint selected="false">Remember that apple is also a fruit.</choicehint>
|
||||
</choice>
|
||||
<choice correct="false">Mushroom
|
||||
<choicehint selected="true">Mushroom is a fungus, not a fruit.</choicehint>
|
||||
<choicehint selected="false">You're right that mushrooms aren't fruit</choicehint>
|
||||
</choice>
|
||||
<choice correct="true">Grape
|
||||
<choicehint selected="true">You're right that grape is a fruit</choicehint>
|
||||
<choicehint selected="false">Remember that grape is also a fruit.</choicehint>
|
||||
</choice>
|
||||
<choice correct="false">Mustang</choice>
|
||||
<choice correct="false">Camero
|
||||
<choicehint selected="true">I don't know what a Camero is but it isn't a fruit.</choicehint>
|
||||
<choicehint selected="false">What is a camero anyway?</choicehint>
|
||||
</choice>
|
||||
<compoundhint value="A*B">You're right that apple is a fruit, but there's one you're missing. Also, mushroom is not a fruit.</compoundhint>
|
||||
<compoundhint value="B*C">You're right that grape is a fruit, but there's one you're missing. Also, mushroom is not a fruit.</compoundhint>
|
||||
</checkboxgroup>
|
||||
</choiceresponse>
|
||||
|
||||
<label>Select all the vegetables from the list</label>
|
||||
<choiceresponse>
|
||||
<checkboxgroup>
|
||||
<choice correct="false">Banana
|
||||
<choicehint selected="true">No, sorry, a banana is a fruit.</choicehint>
|
||||
<choicehint selected="false">poor banana.</choicehint>
|
||||
</choice>
|
||||
<choice correct="false">Ice Cream</choice>
|
||||
<choice correct="false">Mushroom
|
||||
<choicehint selected="true">Mushroom is a fungus, not a vegetable.</choicehint>
|
||||
<choicehint selected="false">You're right that mushrooms aren't vegatbles</choicehint>
|
||||
</choice>
|
||||
<choice correct="true">Brussel Sprout
|
||||
<choicehint selected="true">Brussel sprouts are vegetables.</choicehint>
|
||||
<choicehint selected="false">Brussel sprout is the only vegetable in this list.</choicehint>
|
||||
</choice>
|
||||
<compoundhint value="A*B">Making a banana split?</compoundhint>
|
||||
<compoundhint value="B*D">That will make a horrible dessert: a brussel sprout split?</compoundhint>
|
||||
</checkboxgroup>
|
||||
</choiceresponse>
|
||||
|
||||
<demandhint>
|
||||
<hint>Hint one.</hint>
|
||||
<hint>Hint two.</hint>
|
||||
<hint>Hint three.</hint>
|
||||
</demandhint>
|
||||
</problem>\
|
||||
`);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
describe('Markdown to xml extended hint multiple choice', function() {
|
||||
it('produces xml', function() {
|
||||
const data = MarkdownEditingDescriptor.markdownToXml(`\
|
||||
>>Select the fruit from the list<<
|
||||
|
||||
() Mushroom {{ Mushroom is a fungus, not a fruit.}}
|
||||
() Potato
|
||||
(x) Apple {{ OUTSTANDING::Apple is indeed a fruit.}}
|
||||
|
||||
>>Select the vegetables from the list<<
|
||||
|
||||
() Mushroom {{ Mushroom is a fungus, not a vegetable.}}
|
||||
(x) Potato {{ Potato is a root vegetable. }}
|
||||
() Apple {{ OOPS::Apple is a fruit.}}\
|
||||
`);
|
||||
expect(data).toXMLEqual(`\
|
||||
<problem>
|
||||
<label>Select the fruit from the list</label>
|
||||
<multiplechoiceresponse>
|
||||
<choicegroup type="MultipleChoice">
|
||||
<choice correct="false">Mushroom
|
||||
<choicehint>Mushroom is a fungus, not a fruit.</choicehint>
|
||||
</choice>
|
||||
<choice correct="false">Potato</choice>
|
||||
<choice correct="true">Apple
|
||||
<choicehint label="OUTSTANDING">Apple is indeed a fruit.</choicehint>
|
||||
</choice>
|
||||
</choicegroup>
|
||||
</multiplechoiceresponse>
|
||||
|
||||
<label>Select the vegetables from the list</label>
|
||||
<multiplechoiceresponse>
|
||||
<choicegroup type="MultipleChoice">
|
||||
<choice correct="false">Mushroom
|
||||
<choicehint>Mushroom is a fungus, not a vegetable.</choicehint>
|
||||
</choice>
|
||||
<choice correct="true">Potato
|
||||
<choicehint>Potato is a root vegetable.</choicehint>
|
||||
</choice>
|
||||
<choice correct="false">Apple
|
||||
<choicehint label="OOPS">Apple is a fruit.</choicehint>
|
||||
</choice>
|
||||
</choicegroup>
|
||||
</multiplechoiceresponse>
|
||||
</problem>\
|
||||
`);
|
||||
});
|
||||
|
||||
it('produces xml with demand hints', function() {
|
||||
const data = MarkdownEditingDescriptor.markdownToXml(`\
|
||||
>>Select the fruit from the list<<
|
||||
|
||||
() Mushroom {{ Mushroom is a fungus, not a fruit.}}
|
||||
() Potato
|
||||
(x) Apple {{ OUTSTANDING::Apple is indeed a fruit.}}
|
||||
|
||||
|| 0) spaces on previous line. ||
|
||||
|| 1) roses are red. ||
|
||||
|
||||
>>Select the vegetables from the list<<
|
||||
|
||||
() Mushroom {{ Mushroom is a fungus, not a vegetable.}}
|
||||
(x) Potato {{ Potato is a root vegetable. }}
|
||||
() Apple {{ OOPS::Apple is a fruit.}}
|
||||
|
||||
|| 2) where are the lions? ||
|
||||
\
|
||||
`);
|
||||
expect(data).toXMLEqual(`\
|
||||
<problem>
|
||||
<label>Select the fruit from the list</label>
|
||||
<multiplechoiceresponse>
|
||||
<choicegroup type="MultipleChoice">
|
||||
<choice correct="false">Mushroom
|
||||
<choicehint>Mushroom is a fungus, not a fruit.</choicehint>
|
||||
</choice>
|
||||
<choice correct="false">Potato</choice>
|
||||
<choice correct="true">Apple
|
||||
<choicehint label="OUTSTANDING">Apple is indeed a fruit.</choicehint>
|
||||
</choice>
|
||||
</choicegroup>
|
||||
</multiplechoiceresponse>
|
||||
|
||||
<label>Select the vegetables from the list</label>
|
||||
<multiplechoiceresponse>
|
||||
<choicegroup type="MultipleChoice">
|
||||
<choice correct="false">Mushroom
|
||||
<choicehint>Mushroom is a fungus, not a vegetable.</choicehint>
|
||||
</choice>
|
||||
<choice correct="true">Potato
|
||||
<choicehint>Potato is a root vegetable.</choicehint>
|
||||
</choice>
|
||||
<choice correct="false">Apple
|
||||
<choicehint label="OOPS">Apple is a fruit.</choicehint>
|
||||
</choice>
|
||||
</choicegroup>
|
||||
</multiplechoiceresponse>
|
||||
|
||||
<demandhint>
|
||||
<hint>0) spaces on previous line.</hint>
|
||||
<hint>1) roses are red.</hint>
|
||||
<hint>2) where are the lions?</hint>
|
||||
</demandhint>
|
||||
</problem>\
|
||||
`);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
describe('Markdown to xml extended hint text input', function() {
|
||||
it('produces xml', function() {
|
||||
const data = MarkdownEditingDescriptor.markdownToXml(`>>In which country would you find the city of Paris?<<
|
||||
= France {{ BRAVO::Viva la France! }}
|
||||
\
|
||||
`);
|
||||
expect(data).toXMLEqual(`\
|
||||
<problem>
|
||||
<stringresponse answer="France" type="ci">
|
||||
<label>In which country would you find the city of Paris?</label>
|
||||
<correcthint label="BRAVO">Viva la France!</correcthint>
|
||||
<textline size="20"/>
|
||||
</stringresponse>
|
||||
|
||||
|
||||
</problem>\
|
||||
`);
|
||||
});
|
||||
|
||||
it('produces xml with or=', function() {
|
||||
const data = MarkdownEditingDescriptor.markdownToXml(`>>Where Paris?<<
|
||||
= France {{ BRAVO::hint1}}
|
||||
or= USA {{ meh::hint2 }}
|
||||
\
|
||||
`);
|
||||
expect(data).toXMLEqual(`\
|
||||
<problem>
|
||||
<stringresponse answer="France" type="ci">
|
||||
<label>Where Paris?</label>
|
||||
<correcthint label="BRAVO">hint1</correcthint>
|
||||
<additional_answer answer="USA"><correcthint label="meh">hint2</correcthint>
|
||||
</additional_answer>
|
||||
<textline size="20"/>
|
||||
</stringresponse>
|
||||
|
||||
|
||||
</problem>\
|
||||
`);
|
||||
});
|
||||
|
||||
it('produces xml with not=', function() {
|
||||
const data = MarkdownEditingDescriptor.markdownToXml(`>>Revenge is a dish best served<<
|
||||
= cold {{khaaaaaan!}}
|
||||
not= warm {{feedback2}}
|
||||
\
|
||||
`);
|
||||
expect(data).toXMLEqual(`\
|
||||
<problem>
|
||||
<stringresponse answer="cold" type="ci">
|
||||
<label>Revenge is a dish best served</label>
|
||||
<correcthint>khaaaaaan!</correcthint>
|
||||
<stringequalhint answer="warm">feedback2</stringequalhint>
|
||||
<textline size="20"/>
|
||||
</stringresponse>
|
||||
|
||||
|
||||
</problem>\
|
||||
`);
|
||||
});
|
||||
|
||||
it('produces xml with s=', function() {
|
||||
const data = MarkdownEditingDescriptor.markdownToXml(`>>q<<
|
||||
s= 2 {{feedback1}}
|
||||
\
|
||||
`);
|
||||
expect(data).toXMLEqual(`\
|
||||
<problem>
|
||||
<stringresponse answer="2" type="ci">
|
||||
<label>q</label>
|
||||
<correcthint>feedback1</correcthint>
|
||||
<textline size="20"/>
|
||||
</stringresponse>
|
||||
|
||||
|
||||
</problem>\
|
||||
`);
|
||||
});
|
||||
|
||||
it('produces xml with = and or= and not=', function() {
|
||||
const data = MarkdownEditingDescriptor.markdownToXml(`>>q<<
|
||||
= aaa
|
||||
or= bbb {{feedback1}}
|
||||
not= no {{feedback2}}
|
||||
or= ccc
|
||||
\
|
||||
`);
|
||||
expect(data).toXMLEqual(`\
|
||||
<problem>
|
||||
<stringresponse answer="aaa" type="ci">
|
||||
<label>q</label>
|
||||
<additional_answer answer="bbb"><correcthint>feedback1</correcthint>
|
||||
</additional_answer>
|
||||
<stringequalhint answer="no">feedback2</stringequalhint>
|
||||
<additional_answer answer="ccc"/>
|
||||
<textline size="20"/>
|
||||
</stringresponse>
|
||||
|
||||
|
||||
</problem>\
|
||||
`);
|
||||
});
|
||||
|
||||
it('produces xml with s= and or=', function() {
|
||||
const data = MarkdownEditingDescriptor.markdownToXml(`>>q<<
|
||||
s= 2 {{feedback1}}
|
||||
or= bbb {{feedback2}}
|
||||
or= ccc
|
||||
\
|
||||
`);
|
||||
expect(data).toXMLEqual(`\
|
||||
<problem>
|
||||
<stringresponse answer="2" type="ci">
|
||||
<label>q</label>
|
||||
<correcthint>feedback1</correcthint>
|
||||
<additional_answer answer="bbb"><correcthint>feedback2</correcthint>
|
||||
</additional_answer>
|
||||
<additional_answer answer="ccc"/>
|
||||
<textline size="20"/>
|
||||
</stringresponse>
|
||||
|
||||
|
||||
</problem>\
|
||||
`);
|
||||
});
|
||||
|
||||
it('produces xml with each = making a new question', function() {
|
||||
const data = MarkdownEditingDescriptor.markdownToXml(`\
|
||||
>>q<<
|
||||
= aaa
|
||||
or= bbb
|
||||
s= ccc\
|
||||
`);
|
||||
expect(data).toXMLEqual(`\
|
||||
<problem>
|
||||
<label>q</label>
|
||||
<stringresponse answer="aaa" type="ci">
|
||||
<additional_answer answer="bbb"></additional_answer>
|
||||
<textline size="20"/>
|
||||
</stringresponse>
|
||||
<stringresponse answer="ccc" type="ci">
|
||||
<textline size="20"/>
|
||||
</stringresponse>
|
||||
</problem>\
|
||||
`);
|
||||
});
|
||||
|
||||
it('produces xml with each = making a new question amid blank lines and paragraphs', function() {
|
||||
const data = MarkdownEditingDescriptor.markdownToXml(`\
|
||||
paragraph
|
||||
>>q<<
|
||||
= aaa
|
||||
|
||||
or= bbb
|
||||
s= ccc
|
||||
|
||||
paragraph 2
|
||||
\
|
||||
`);
|
||||
expect(data).toXMLEqual(`\
|
||||
<problem>
|
||||
<p>paragraph</p>
|
||||
<label>q</label>
|
||||
<stringresponse answer="aaa" type="ci">
|
||||
<additional_answer answer="bbb"></additional_answer>
|
||||
<textline size="20"/>
|
||||
</stringresponse>
|
||||
<stringresponse answer="ccc" type="ci">
|
||||
<textline size="20"/>
|
||||
</stringresponse>
|
||||
<p>paragraph 2</p>
|
||||
</problem>\
|
||||
`);
|
||||
});
|
||||
|
||||
it('produces xml without a question when or= is just hung out there by itself', function() {
|
||||
const data = MarkdownEditingDescriptor.markdownToXml(`\
|
||||
paragraph
|
||||
>>q<<
|
||||
or= aaa
|
||||
paragraph 2
|
||||
\
|
||||
`);
|
||||
expect(data).toXMLEqual(`\
|
||||
<problem>
|
||||
<p>paragraph</p>
|
||||
<label>q</label>
|
||||
<p>or= aaa</p>
|
||||
<p>paragraph 2</p>
|
||||
</problem>\
|
||||
`);
|
||||
});
|
||||
|
||||
it('produces xml with each = with feedback making a new question', function() {
|
||||
const data = MarkdownEditingDescriptor.markdownToXml(`\
|
||||
>>q<<
|
||||
s= aaa
|
||||
or= bbb {{feedback1}}
|
||||
= ccc {{feedback2}}
|
||||
\
|
||||
`);
|
||||
expect(data).toXMLEqual(`\
|
||||
<problem>
|
||||
<label>q</label>
|
||||
<stringresponse answer="aaa" type="ci">
|
||||
<additional_answer answer="bbb">
|
||||
<correcthint>feedback1</correcthint>
|
||||
</additional_answer>
|
||||
<textline size="20"/>
|
||||
</stringresponse>
|
||||
<stringresponse answer="ccc" type="ci">
|
||||
<correcthint>feedback2</correcthint>
|
||||
<textline size="20"/>
|
||||
</stringresponse>
|
||||
</problem>\
|
||||
`);
|
||||
});
|
||||
|
||||
it('produces xml with demand hints', function() {
|
||||
const data = MarkdownEditingDescriptor.markdownToXml(`>>Where Paris?<<
|
||||
= France {{ BRAVO::hint1 }}
|
||||
|
||||
|| There are actually two countries with cities named Paris. ||
|
||||
|| Paris is the capital of one of those countries. ||
|
||||
\
|
||||
`);
|
||||
expect(data).toXMLEqual(`\
|
||||
<problem>
|
||||
<stringresponse answer="France" type="ci">
|
||||
<label>Where Paris?</label>
|
||||
<correcthint label="BRAVO">hint1</correcthint>
|
||||
<textline size="20"/>
|
||||
</stringresponse>
|
||||
|
||||
<demandhint>
|
||||
<hint>There are actually two countries with cities named Paris.</hint>
|
||||
<hint>Paris is the capital of one of those countries.</hint>
|
||||
</demandhint>
|
||||
</problem>`);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
describe('Markdown to xml extended hint numeric input', function() {
|
||||
it('produces xml', function() {
|
||||
const data = MarkdownEditingDescriptor.markdownToXml(`\
|
||||
>>Enter the numerical value of Pi:<<
|
||||
= 3.14159 +- .02 {{ Pie for everyone! }}
|
||||
|
||||
>>Enter the approximate value of 502*9:<<
|
||||
= 4518 +- 15% {{PIE:: No pie for you!}}
|
||||
|
||||
>>Enter the number of fingers on a human hand<<
|
||||
= 5
|
||||
\
|
||||
`);
|
||||
expect(data).toXMLEqual(`\
|
||||
<problem>
|
||||
<label>Enter the numerical value of Pi:</label>
|
||||
<numericalresponse answer="3.14159">
|
||||
<responseparam type="tolerance" default=".02"/>
|
||||
<formulaequationinput/>
|
||||
<correcthint>Pie for everyone!</correcthint>
|
||||
</numericalresponse>
|
||||
|
||||
<label>Enter the approximate value of 502*9:</label>
|
||||
<numericalresponse answer="4518">
|
||||
<responseparam type="tolerance" default="15%"/>
|
||||
<formulaequationinput/>
|
||||
<correcthint label="PIE">No pie for you!</correcthint>
|
||||
</numericalresponse>
|
||||
|
||||
<label>Enter the number of fingers on a human hand</label>
|
||||
<numericalresponse answer="5">
|
||||
<formulaequationinput/>
|
||||
</numericalresponse>
|
||||
</problem>\
|
||||
`);
|
||||
});
|
||||
|
||||
// The output xml here shows some of the quirks of how historical markdown parsing does or does not put
|
||||
// in blank lines.
|
||||
it('numeric input with hints and demand hints', function() {
|
||||
const data = MarkdownEditingDescriptor.markdownToXml(`\
|
||||
>>text1<<
|
||||
= 1 {{ hint1 }}
|
||||
|| hintA ||
|
||||
>>text2<<
|
||||
= 2 {{ hint2 }}
|
||||
|
||||
|| hintB ||
|
||||
\
|
||||
`);
|
||||
expect(data).toXMLEqual(`\
|
||||
<problem>
|
||||
<label>text1</label>
|
||||
<numericalresponse answer="1">
|
||||
<formulaequationinput/>
|
||||
<correcthint>hint1</correcthint>
|
||||
</numericalresponse>
|
||||
<label>text2</label>
|
||||
<numericalresponse answer="2">
|
||||
<formulaequationinput/>
|
||||
<correcthint>hint2</correcthint>
|
||||
</numericalresponse>
|
||||
|
||||
<demandhint>
|
||||
<hint>hintA</hint>
|
||||
<hint>hintB</hint>
|
||||
</demandhint>
|
||||
</problem>\
|
||||
`);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
describe('Markdown to xml extended hint with multiline hints', () =>
|
||||
it('produces xml', function() {
|
||||
const data = MarkdownEditingDescriptor.markdownToXml(`\
|
||||
>>Checkboxes<<
|
||||
|
||||
[x] A {{
|
||||
selected: aaa },
|
||||
{unselected:bbb}}
|
||||
[ ] B {{U: c}, {
|
||||
selected: d.}}
|
||||
|
||||
{{ ((A*B)) A*B hint}}
|
||||
|
||||
>>What is 1 + 1?<<
|
||||
= 2 {{ part one, and
|
||||
part two
|
||||
}}
|
||||
|
||||
>>hello?<<
|
||||
= hello {{
|
||||
hello
|
||||
hint
|
||||
}}
|
||||
|
||||
>>multiple choice<<
|
||||
(x) AA{{hint1}}
|
||||
() BB {{
|
||||
hint2
|
||||
}}
|
||||
( ) CC {{ hint3
|
||||
}}
|
||||
|
||||
>>dropdown<<
|
||||
[[
|
||||
W1 {{
|
||||
no }}
|
||||
W2 {{
|
||||
nope}}
|
||||
(C1) {{ yes
|
||||
}}
|
||||
]]
|
||||
|
||||
|| aaa ||
|
||||
||bbb||
|
||||
|| ccc ||
|
||||
\
|
||||
`);
|
||||
expect(data).toXMLEqual(`\
|
||||
<problem>
|
||||
<label>Checkboxes</label>
|
||||
<choiceresponse>
|
||||
<checkboxgroup>
|
||||
<choice correct="true">A
|
||||
<choicehint selected="true">aaa</choicehint>
|
||||
<choicehint selected="false">bbb</choicehint>
|
||||
</choice>
|
||||
<choice correct="false">B
|
||||
<choicehint selected="true">d.</choicehint>
|
||||
<choicehint selected="false">c</choicehint>
|
||||
</choice>
|
||||
<compoundhint value="A*B">A*B hint</compoundhint>
|
||||
</checkboxgroup>
|
||||
</choiceresponse>
|
||||
|
||||
<label>What is 1 + 1?</label>
|
||||
<numericalresponse answer="2">
|
||||
<formulaequationinput/>
|
||||
<correcthint>part one, and part two</correcthint>
|
||||
</numericalresponse>
|
||||
|
||||
<label>hello?</label>
|
||||
<stringresponse answer="hello" type="ci">
|
||||
<correcthint>hello hint</correcthint>
|
||||
<textline size="20"/>
|
||||
</stringresponse>
|
||||
|
||||
<label>multiple choice</label>
|
||||
<multiplechoiceresponse>
|
||||
<choicegroup type="MultipleChoice">
|
||||
<choice correct="true">AA
|
||||
<choicehint>hint1</choicehint>
|
||||
</choice>
|
||||
<choice correct="false">BB
|
||||
<choicehint>hint2</choicehint>
|
||||
</choice>
|
||||
<choice correct="false">CC
|
||||
<choicehint>hint3</choicehint>
|
||||
</choice>
|
||||
</choicegroup>
|
||||
</multiplechoiceresponse>
|
||||
|
||||
<label>dropdown</label>
|
||||
<optionresponse>
|
||||
<optioninput>
|
||||
<option correct="False">W1
|
||||
<optionhint>no</optionhint>
|
||||
</option>
|
||||
<option correct="False">W2
|
||||
<optionhint>nope</optionhint>
|
||||
</option>
|
||||
<option correct="True">C1
|
||||
<optionhint>yes</optionhint>
|
||||
</option>
|
||||
</optioninput>
|
||||
</optionresponse>
|
||||
|
||||
<demandhint>
|
||||
<hint>aaa</hint>
|
||||
<hint>bbb</hint>
|
||||
<hint>ccc</hint>
|
||||
</demandhint>
|
||||
</problem>\
|
||||
`);
|
||||
})
|
||||
);
|
||||
|
||||
describe('Markdown to xml extended hint with tricky syntax cases', function() {
|
||||
// I'm entering this as utf-8 in this file.
|
||||
// I cannot find a way to set the encoding for .coffee files but it seems to work.
|
||||
it('produces xml with unicode', function() {
|
||||
const data = MarkdownEditingDescriptor.markdownToXml(`\
|
||||
>>á and Ø<<
|
||||
|
||||
(x) Ø{{Ø}}
|
||||
() BB
|
||||
|
||||
|| Ø ||
|
||||
\
|
||||
`);
|
||||
expect(data).toXMLEqual(`\
|
||||
<problem>
|
||||
<multiplechoiceresponse>
|
||||
<label>á and Ø</label>
|
||||
<choicegroup type="MultipleChoice">
|
||||
<choice correct="true">Ø
|
||||
<choicehint>Ø</choicehint>
|
||||
</choice>
|
||||
<choice correct="false">BB</choice>
|
||||
</choicegroup>
|
||||
</multiplechoiceresponse>
|
||||
|
||||
<demandhint>
|
||||
<hint>Ø</hint>
|
||||
</demandhint>
|
||||
</problem>\
|
||||
`);
|
||||
});
|
||||
|
||||
it('produces xml with quote-type characters', function() {
|
||||
const data = MarkdownEditingDescriptor.markdownToXml(`\
|
||||
>>"quotes" aren't \`fun\`<<
|
||||
() "hello" {{ isn't }}
|
||||
(x) "isn't" {{ "hello" }}
|
||||
\
|
||||
`);
|
||||
expect(data).toXMLEqual(`\
|
||||
<problem>
|
||||
<multiplechoiceresponse>
|
||||
<label>"quotes" aren't \`fun\`</label>
|
||||
<choicegroup type="MultipleChoice">
|
||||
<choice correct="false">"hello"
|
||||
<choicehint>isn't</choicehint>
|
||||
</choice>
|
||||
<choice correct="true">"isn't"
|
||||
<choicehint>"hello"</choicehint>
|
||||
</choice>
|
||||
</choicegroup>
|
||||
</multiplechoiceresponse>
|
||||
</problem>\
|
||||
`);
|
||||
});
|
||||
|
||||
it('produces xml with almost but not quite multiple choice syntax', function() {
|
||||
const data = MarkdownEditingDescriptor.markdownToXml(`\
|
||||
>>q1<<
|
||||
this (x)
|
||||
() a {{ (hint) }}
|
||||
(x) b
|
||||
that (y)\
|
||||
`);
|
||||
expect(data).toXMLEqual(`\
|
||||
<problem>
|
||||
<multiplechoiceresponse>
|
||||
<label>q1</label>
|
||||
<p>this (x)</p>
|
||||
<choicegroup type="MultipleChoice">
|
||||
<choice correct="false">a <choicehint>(hint)</choicehint>
|
||||
</choice>
|
||||
<choice correct="true">b</choice>
|
||||
</choicegroup>
|
||||
<p>that (y)</p>
|
||||
</multiplechoiceresponse>
|
||||
|
||||
|
||||
</problem>\
|
||||
`);
|
||||
});
|
||||
|
||||
// An incomplete checkbox hint passes through to cue the author
|
||||
it('produce xml with almost but not quite checkboxgroup syntax', function() {
|
||||
const data = MarkdownEditingDescriptor.markdownToXml(`\
|
||||
>>q1<<
|
||||
this [x]
|
||||
[ ] a [square]
|
||||
[x] b {{ this hint passes through }}
|
||||
that []\
|
||||
`);
|
||||
expect(data).toXMLEqual(`\
|
||||
<problem>
|
||||
<choiceresponse>
|
||||
<label>q1</label>
|
||||
<p>this [x]</p>
|
||||
<checkboxgroup>
|
||||
<choice correct="false">a [square]</choice>
|
||||
<choice correct="true">b {{ this hint passes through }}</choice>
|
||||
</checkboxgroup>
|
||||
<p>that []</p>
|
||||
</choiceresponse>
|
||||
|
||||
|
||||
</problem>\
|
||||
`);
|
||||
});
|
||||
|
||||
// It's sort of a pain to edit DOS line endings without some editor or other "fixing" them
|
||||
// for you. Therefore, we construct DOS line endings on the fly just for the test.
|
||||
it('produces xml with DOS \r\n line endings', function() {
|
||||
let markdown = `\
|
||||
>>q22<<
|
||||
|
||||
[[
|
||||
(x) {{ hintx
|
||||
these
|
||||
span
|
||||
}}
|
||||
|
||||
yy {{ meh::hinty }}
|
||||
zzz {{ hintz }}
|
||||
]]\
|
||||
`;
|
||||
markdown = markdown.replace(/\n/g, '\r\n'); // make DOS line endings
|
||||
const data = MarkdownEditingDescriptor.markdownToXml(markdown);
|
||||
expect(data).toXMLEqual(`\
|
||||
<problem>
|
||||
<optionresponse>
|
||||
<label>q22</label>
|
||||
<optioninput>
|
||||
<option correct="True">x <optionhint>hintx these span</optionhint>
|
||||
</option>
|
||||
<option correct="False">yy <optionhint label="meh">hinty</optionhint>
|
||||
</option>
|
||||
<option correct="False">zzz <optionhint>hintz</optionhint>
|
||||
</option>
|
||||
</optioninput>
|
||||
</optionresponse>
|
||||
|
||||
|
||||
</problem>\
|
||||
`);
|
||||
});
|
||||
});
|
||||
@@ -1,90 +0,0 @@
|
||||
describe "TabsEditingDescriptor", ->
|
||||
beforeEach ->
|
||||
@isInactiveClass = "is-inactive"
|
||||
@isCurrent = "current"
|
||||
loadFixtures 'tabs-edit.html'
|
||||
@descriptor = new TabsEditingDescriptor($('.xblock'))
|
||||
@html_id = 'test_id'
|
||||
@tab_0_switch = jasmine.createSpy('tab_0_switch');
|
||||
@tab_0_modelUpdate = jasmine.createSpy('tab_0_modelUpdate');
|
||||
@tab_1_switch = jasmine.createSpy('tab_1_switch');
|
||||
@tab_1_modelUpdate = jasmine.createSpy('tab_1_modelUpdate');
|
||||
TabsEditingDescriptor.Model.addModelUpdate(@html_id, 'Tab 0 Editor', @tab_0_modelUpdate)
|
||||
TabsEditingDescriptor.Model.addOnSwitch(@html_id, 'Tab 0 Editor', @tab_0_switch)
|
||||
TabsEditingDescriptor.Model.addModelUpdate(@html_id, 'Tab 1 Transcripts', @tab_1_modelUpdate)
|
||||
TabsEditingDescriptor.Model.addOnSwitch(@html_id, 'Tab 1 Transcripts', @tab_1_switch)
|
||||
|
||||
spyOn($.fn, 'hide').and.callThrough()
|
||||
spyOn($.fn, 'show').and.callThrough()
|
||||
spyOn(TabsEditingDescriptor.Model, 'initialize')
|
||||
spyOn(TabsEditingDescriptor.Model, 'updateValue')
|
||||
|
||||
afterEach ->
|
||||
TabsEditingDescriptor.Model.modules= {}
|
||||
|
||||
describe "constructor", ->
|
||||
it "first tab should be visible", ->
|
||||
expect(@descriptor.$tabs.first()).toHaveClass(@isCurrent)
|
||||
expect(@descriptor.$content.first()).not.toHaveClass(@isInactiveClass)
|
||||
|
||||
describe "onSwitchEditor", ->
|
||||
it "switching tabs changes styles", ->
|
||||
@descriptor.$tabs.eq(1).trigger("click")
|
||||
expect(@descriptor.$tabs.eq(0)).not.toHaveClass(@isCurrent)
|
||||
expect(@descriptor.$content.eq(0)).toHaveClass(@isInactiveClass)
|
||||
expect(@descriptor.$tabs.eq(1)).toHaveClass(@isCurrent)
|
||||
expect(@descriptor.$content.eq(1)).not.toHaveClass(@isInactiveClass)
|
||||
expect(@tab_1_switch).toHaveBeenCalled()
|
||||
|
||||
it "if click on current tab, nothing should happen", ->
|
||||
spyOn($.fn, 'trigger').and.callThrough()
|
||||
currentTab = @descriptor.$tabs.filter('.' + @isCurrent)
|
||||
@descriptor.$tabs.eq(0).trigger("click")
|
||||
expect(@descriptor.$tabs.filter('.' + @isCurrent)).toEqual(currentTab)
|
||||
expect($.fn.trigger.calls.count()).toEqual(1)
|
||||
|
||||
it "onSwitch function call", ->
|
||||
@descriptor.$tabs.eq(1).trigger("click")
|
||||
expect(TabsEditingDescriptor.Model.updateValue).toHaveBeenCalled()
|
||||
expect(@tab_1_switch).toHaveBeenCalled()
|
||||
|
||||
describe "save", ->
|
||||
it "function for current tab should be called", ->
|
||||
@descriptor.$tabs.eq(1).trigger("click")
|
||||
data = @descriptor.save().data
|
||||
expect(@tab_1_modelUpdate).toHaveBeenCalled()
|
||||
|
||||
it "detach click event", ->
|
||||
spyOn($.fn, "off")
|
||||
@descriptor.save()
|
||||
expect($.fn.off).toHaveBeenCalledWith(
|
||||
'click',
|
||||
'.editor-tabs .tab',
|
||||
@descriptor.onSwitchEditor
|
||||
)
|
||||
|
||||
describe "TabsEditingDescriptor special save cases", ->
|
||||
beforeEach ->
|
||||
@isInactiveClass = "is-inactive"
|
||||
@isCurrent = "current"
|
||||
loadFixtures 'tabs-edit.html'
|
||||
@descriptor = new window.TabsEditingDescriptor($('.xblock'))
|
||||
@html_id = 'test_id'
|
||||
|
||||
describe "save", ->
|
||||
it "case: no init", ->
|
||||
data = @descriptor.save().data
|
||||
expect(data).toEqual(null)
|
||||
|
||||
it "case: no function in model update", ->
|
||||
TabsEditingDescriptor.Model.initialize(@html_id)
|
||||
data = @descriptor.save().data
|
||||
expect(data).toEqual(null)
|
||||
|
||||
it "case: no function in model update, but value presented", ->
|
||||
@tab_0_modelUpdate = jasmine.createSpy('tab_0_modelUpdate').and.returnValue(1)
|
||||
TabsEditingDescriptor.Model.addModelUpdate(@html_id, 'Tab 0 Editor', @tab_0_modelUpdate)
|
||||
@descriptor.$tabs.eq(1).trigger("click")
|
||||
expect(@tab_0_modelUpdate).toHaveBeenCalled()
|
||||
data = @descriptor.save().data
|
||||
expect(data).toEqual(1)
|
||||
106
common/lib/xmodule/xmodule/js/spec/tabs/edit.js
Normal file
106
common/lib/xmodule/xmodule/js/spec/tabs/edit.js
Normal file
@@ -0,0 +1,106 @@
|
||||
describe("TabsEditingDescriptor", function() {
|
||||
beforeEach(function() {
|
||||
this.isInactiveClass = "is-inactive";
|
||||
this.isCurrent = "current";
|
||||
loadFixtures('tabs-edit.html');
|
||||
this.descriptor = new TabsEditingDescriptor($('.xblock'));
|
||||
this.html_id = 'test_id';
|
||||
this.tab_0_switch = jasmine.createSpy('tab_0_switch');
|
||||
this.tab_0_modelUpdate = jasmine.createSpy('tab_0_modelUpdate');
|
||||
this.tab_1_switch = jasmine.createSpy('tab_1_switch');
|
||||
this.tab_1_modelUpdate = jasmine.createSpy('tab_1_modelUpdate');
|
||||
TabsEditingDescriptor.Model.addModelUpdate(this.html_id, 'Tab 0 Editor', this.tab_0_modelUpdate);
|
||||
TabsEditingDescriptor.Model.addOnSwitch(this.html_id, 'Tab 0 Editor', this.tab_0_switch);
|
||||
TabsEditingDescriptor.Model.addModelUpdate(this.html_id, 'Tab 1 Transcripts', this.tab_1_modelUpdate);
|
||||
TabsEditingDescriptor.Model.addOnSwitch(this.html_id, 'Tab 1 Transcripts', this.tab_1_switch);
|
||||
|
||||
spyOn($.fn, 'hide').and.callThrough();
|
||||
spyOn($.fn, 'show').and.callThrough();
|
||||
spyOn(TabsEditingDescriptor.Model, 'initialize');
|
||||
spyOn(TabsEditingDescriptor.Model, 'updateValue');
|
||||
});
|
||||
|
||||
afterEach(() => TabsEditingDescriptor.Model.modules= {});
|
||||
|
||||
describe("constructor", () =>
|
||||
it("first tab should be visible", function() {
|
||||
expect(this.descriptor.$tabs.first()).toHaveClass(this.isCurrent);
|
||||
expect(this.descriptor.$content.first()).not.toHaveClass(this.isInactiveClass);
|
||||
})
|
||||
);
|
||||
|
||||
describe("onSwitchEditor", function() {
|
||||
it("switching tabs changes styles", function() {
|
||||
this.descriptor.$tabs.eq(1).trigger("click");
|
||||
expect(this.descriptor.$tabs.eq(0)).not.toHaveClass(this.isCurrent);
|
||||
expect(this.descriptor.$content.eq(0)).toHaveClass(this.isInactiveClass);
|
||||
expect(this.descriptor.$tabs.eq(1)).toHaveClass(this.isCurrent);
|
||||
expect(this.descriptor.$content.eq(1)).not.toHaveClass(this.isInactiveClass);
|
||||
expect(this.tab_1_switch).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("if click on current tab, nothing should happen", function() {
|
||||
spyOn($.fn, 'trigger').and.callThrough();
|
||||
const currentTab = this.descriptor.$tabs.filter(`.${this.isCurrent}`);
|
||||
this.descriptor.$tabs.eq(0).trigger("click");
|
||||
expect(this.descriptor.$tabs.filter(`.${this.isCurrent}`)).toEqual(currentTab);
|
||||
expect($.fn.trigger.calls.count()).toEqual(1);
|
||||
});
|
||||
|
||||
it("onSwitch function call", function() {
|
||||
this.descriptor.$tabs.eq(1).trigger("click");
|
||||
expect(TabsEditingDescriptor.Model.updateValue).toHaveBeenCalled();
|
||||
expect(this.tab_1_switch).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe("save", function() {
|
||||
it("function for current tab should be called", function() {
|
||||
this.descriptor.$tabs.eq(1).trigger("click");
|
||||
const { data } = this.descriptor.save();
|
||||
expect(this.tab_1_modelUpdate).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("detach click event", function() {
|
||||
spyOn($.fn, "off");
|
||||
this.descriptor.save();
|
||||
expect($.fn.off).toHaveBeenCalledWith(
|
||||
'click',
|
||||
'.editor-tabs .tab',
|
||||
this.descriptor.onSwitchEditor
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("TabsEditingDescriptor special save cases", function() {
|
||||
beforeEach(function() {
|
||||
this.isInactiveClass = "is-inactive";
|
||||
this.isCurrent = "current";
|
||||
loadFixtures('tabs-edit.html');
|
||||
this.descriptor = new window.TabsEditingDescriptor($('.xblock'));
|
||||
this.html_id = 'test_id';
|
||||
});
|
||||
|
||||
describe("save", function() {
|
||||
it("case: no init", function() {
|
||||
const { data } = this.descriptor.save();
|
||||
expect(data).toEqual(null);
|
||||
});
|
||||
|
||||
it("case: no function in model update", function() {
|
||||
TabsEditingDescriptor.Model.initialize(this.html_id);
|
||||
const { data } = this.descriptor.save();
|
||||
expect(data).toEqual(null);
|
||||
});
|
||||
|
||||
it("case: no function in model update, but value presented", function() {
|
||||
this.tab_0_modelUpdate = jasmine.createSpy('tab_0_modelUpdate').and.returnValue(1);
|
||||
TabsEditingDescriptor.Model.addModelUpdate(this.html_id, 'Tab 0 Editor', this.tab_0_modelUpdate);
|
||||
this.descriptor.$tabs.eq(1).trigger("click");
|
||||
expect(this.tab_0_modelUpdate).toHaveBeenCalled();
|
||||
const { data } = this.descriptor.save();
|
||||
expect(data).toEqual(1);
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user