CoffeeScript tests migration: Decaffeinate files
This is running decaffeinate, with no additional cleanup.
This commit is contained in:
committed by
Calen Pennington
parent
5c64da2f63
commit
0880502f26
@@ -1,10 +1,20 @@
|
||||
define ["js/models/course"], (Course) ->
|
||||
describe "Course", ->
|
||||
describe "basic", ->
|
||||
beforeEach ->
|
||||
@model = new Course({
|
||||
/*
|
||||
* decaffeinate suggestions:
|
||||
* DS102: Remove unnecessary code created because of implicit returns
|
||||
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
|
||||
*/
|
||||
define(["js/models/course"], Course =>
|
||||
describe("Course", () =>
|
||||
describe("basic", function() {
|
||||
beforeEach(function() {
|
||||
return this.model = new Course({
|
||||
name: "Greek Hero"
|
||||
})
|
||||
});
|
||||
});
|
||||
|
||||
it "should take a name argument", ->
|
||||
expect(@model.get("name")).toEqual("Greek Hero")
|
||||
return it("should take a name argument", function() {
|
||||
return expect(this.model.get("name")).toEqual("Greek Hero");
|
||||
});
|
||||
})
|
||||
)
|
||||
);
|
||||
|
||||
@@ -1,59 +1,74 @@
|
||||
define ["js/models/metadata"], (Metadata) ->
|
||||
describe "Metadata", ->
|
||||
it "knows when the value has not been modified", ->
|
||||
model = new Metadata(
|
||||
{'value': 'original', 'explicitly_set': false})
|
||||
expect(model.isModified()).toBeFalsy()
|
||||
/*
|
||||
* decaffeinate suggestions:
|
||||
* DS102: Remove unnecessary code created because of implicit returns
|
||||
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
|
||||
*/
|
||||
define(["js/models/metadata"], Metadata =>
|
||||
describe("Metadata", function() {
|
||||
it("knows when the value has not been modified", function() {
|
||||
let model = new Metadata(
|
||||
{'value': 'original', 'explicitly_set': false});
|
||||
expect(model.isModified()).toBeFalsy();
|
||||
|
||||
model = new Metadata(
|
||||
{'value': 'original', 'explicitly_set': true})
|
||||
model.setValue('original')
|
||||
expect(model.isModified()).toBeFalsy()
|
||||
{'value': 'original', 'explicitly_set': true});
|
||||
model.setValue('original');
|
||||
return expect(model.isModified()).toBeFalsy();
|
||||
});
|
||||
|
||||
it "knows when the value has been modified", ->
|
||||
model = new Metadata(
|
||||
{'value': 'original', 'explicitly_set': false})
|
||||
model.setValue('original')
|
||||
expect(model.isModified()).toBeTruthy()
|
||||
it("knows when the value has been modified", function() {
|
||||
let model = new Metadata(
|
||||
{'value': 'original', 'explicitly_set': false});
|
||||
model.setValue('original');
|
||||
expect(model.isModified()).toBeTruthy();
|
||||
|
||||
model = new Metadata(
|
||||
{'value': 'original', 'explicitly_set': true})
|
||||
model.setValue('modified')
|
||||
expect(model.isModified()).toBeTruthy()
|
||||
{'value': 'original', 'explicitly_set': true});
|
||||
model.setValue('modified');
|
||||
return expect(model.isModified()).toBeTruthy();
|
||||
});
|
||||
|
||||
it "tracks when values have been explicitly set", ->
|
||||
model = new Metadata(
|
||||
{'value': 'original', 'explicitly_set': false})
|
||||
expect(model.isExplicitlySet()).toBeFalsy()
|
||||
model.setValue('original')
|
||||
expect(model.isExplicitlySet()).toBeTruthy()
|
||||
it("tracks when values have been explicitly set", function() {
|
||||
const model = new Metadata(
|
||||
{'value': 'original', 'explicitly_set': false});
|
||||
expect(model.isExplicitlySet()).toBeFalsy();
|
||||
model.setValue('original');
|
||||
return expect(model.isExplicitlySet()).toBeTruthy();
|
||||
});
|
||||
|
||||
it "has both 'display value' and a 'value' methods", ->
|
||||
model = new Metadata(
|
||||
{'value': 'default', 'explicitly_set': false})
|
||||
expect(model.getValue()).toBeNull
|
||||
expect(model.getDisplayValue()).toBe('default')
|
||||
model.setValue('modified')
|
||||
expect(model.getValue()).toBe('modified')
|
||||
expect(model.getDisplayValue()).toBe('modified')
|
||||
it("has both 'display value' and a 'value' methods", function() {
|
||||
const model = new Metadata(
|
||||
{'value': 'default', 'explicitly_set': false});
|
||||
expect(model.getValue()).toBeNull;
|
||||
expect(model.getDisplayValue()).toBe('default');
|
||||
model.setValue('modified');
|
||||
expect(model.getValue()).toBe('modified');
|
||||
return expect(model.getDisplayValue()).toBe('modified');
|
||||
});
|
||||
|
||||
it "has a clear method for reverting to the default", ->
|
||||
model = new Metadata(
|
||||
{'value': 'original', 'default_value' : 'default', 'explicitly_set': true})
|
||||
model.clear()
|
||||
expect(model.getValue()).toBeNull
|
||||
expect(model.getDisplayValue()).toBe('default')
|
||||
expect(model.isExplicitlySet()).toBeFalsy()
|
||||
it("has a clear method for reverting to the default", function() {
|
||||
const model = new Metadata(
|
||||
{'value': 'original', 'default_value' : 'default', 'explicitly_set': true});
|
||||
model.clear();
|
||||
expect(model.getValue()).toBeNull;
|
||||
expect(model.getDisplayValue()).toBe('default');
|
||||
return expect(model.isExplicitlySet()).toBeFalsy();
|
||||
});
|
||||
|
||||
it "has a getter for field name", ->
|
||||
model = new Metadata({'field_name': 'foo'})
|
||||
expect(model.getFieldName()).toBe('foo')
|
||||
it("has a getter for field name", function() {
|
||||
const model = new Metadata({'field_name': 'foo'});
|
||||
return expect(model.getFieldName()).toBe('foo');
|
||||
});
|
||||
|
||||
it "has a getter for options", ->
|
||||
model = new Metadata({'options': ['foo', 'bar']})
|
||||
expect(model.getOptions()).toEqual(['foo', 'bar'])
|
||||
it("has a getter for options", function() {
|
||||
const model = new Metadata({'options': ['foo', 'bar']});
|
||||
return expect(model.getOptions()).toEqual(['foo', 'bar']);
|
||||
});
|
||||
|
||||
it "has a getter for type", ->
|
||||
model = new Metadata({'type': 'Integer'})
|
||||
expect(model.getType()).toBe(Metadata.INTEGER_TYPE)
|
||||
return it("has a getter for type", function() {
|
||||
const model = new Metadata({'type': 'Integer'});
|
||||
return expect(model.getType()).toBe(Metadata.INTEGER_TYPE);
|
||||
});
|
||||
})
|
||||
);
|
||||
|
||||
|
||||
@@ -1,50 +1,67 @@
|
||||
define ["js/models/section", "edx-ui-toolkit/js/utils/spec-helpers/ajax-helpers", "js/utils/module"], (Section, AjaxHelpers, ModuleUtils) ->
|
||||
describe "Section", ->
|
||||
describe "basic", ->
|
||||
beforeEach ->
|
||||
@model = new Section({
|
||||
id: 42
|
||||
/*
|
||||
* decaffeinate suggestions:
|
||||
* DS102: Remove unnecessary code created because of implicit returns
|
||||
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
|
||||
*/
|
||||
define(["js/models/section", "edx-ui-toolkit/js/utils/spec-helpers/ajax-helpers", "js/utils/module"], (Section, AjaxHelpers, ModuleUtils) =>
|
||||
describe("Section", function() {
|
||||
describe("basic", function() {
|
||||
beforeEach(function() {
|
||||
return this.model = new Section({
|
||||
id: 42,
|
||||
name: "Life, the Universe, and Everything"
|
||||
})
|
||||
});
|
||||
});
|
||||
|
||||
it "should take an id argument", ->
|
||||
expect(@model.get("id")).toEqual(42)
|
||||
it("should take an id argument", function() {
|
||||
return expect(this.model.get("id")).toEqual(42);
|
||||
});
|
||||
|
||||
it "should take a name argument", ->
|
||||
expect(@model.get("name")).toEqual("Life, the Universe, and Everything")
|
||||
it("should take a name argument", function() {
|
||||
return expect(this.model.get("name")).toEqual("Life, the Universe, and Everything");
|
||||
});
|
||||
|
||||
it "should have a URL set", ->
|
||||
expect(@model.url()).toEqual(ModuleUtils.getUpdateUrl(42))
|
||||
it("should have a URL set", function() {
|
||||
return expect(this.model.url()).toEqual(ModuleUtils.getUpdateUrl(42));
|
||||
});
|
||||
|
||||
it "should serialize to JSON correctly", ->
|
||||
expect(@model.toJSON()).toEqual({
|
||||
return it("should serialize to JSON correctly", function() {
|
||||
return expect(this.model.toJSON()).toEqual({
|
||||
metadata:
|
||||
{
|
||||
display_name: "Life, the Universe, and Everything"
|
||||
}
|
||||
})
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe "XHR", ->
|
||||
beforeEach ->
|
||||
spyOn(Section.prototype, 'showNotification')
|
||||
spyOn(Section.prototype, 'hideNotification')
|
||||
@model = new Section({
|
||||
id: 42
|
||||
return describe("XHR", function() {
|
||||
beforeEach(function() {
|
||||
spyOn(Section.prototype, 'showNotification');
|
||||
spyOn(Section.prototype, 'hideNotification');
|
||||
return this.model = new Section({
|
||||
id: 42,
|
||||
name: "Life, the Universe, and Everything"
|
||||
})
|
||||
});
|
||||
});
|
||||
|
||||
it "show/hide a notification when it saves to the server", ->
|
||||
server = AjaxHelpers.server([200, {"Content-Type": "application/json"}, "{}"])
|
||||
it("show/hide a notification when it saves to the server", function() {
|
||||
const server = AjaxHelpers.server([200, {"Content-Type": "application/json"}, "{}"]);
|
||||
|
||||
@model.save()
|
||||
expect(Section.prototype.showNotification).toHaveBeenCalled()
|
||||
server.respond()
|
||||
expect(Section.prototype.hideNotification).toHaveBeenCalled()
|
||||
this.model.save();
|
||||
expect(Section.prototype.showNotification).toHaveBeenCalled();
|
||||
server.respond();
|
||||
return expect(Section.prototype.hideNotification).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it "don't hide notification when saving fails", ->
|
||||
# this is handled by the global AJAX error handler
|
||||
server = AjaxHelpers.server([500, {"Content-Type": "application/json"}, "{}"])
|
||||
return it("don't hide notification when saving fails", function() {
|
||||
// this is handled by the global AJAX error handler
|
||||
const server = AjaxHelpers.server([500, {"Content-Type": "application/json"}, "{}"]);
|
||||
|
||||
@model.save()
|
||||
server.respond()
|
||||
expect(Section.prototype.hideNotification).not.toHaveBeenCalled()
|
||||
this.model.save();
|
||||
server.respond();
|
||||
return expect(Section.prototype.hideNotification).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
})
|
||||
);
|
||||
|
||||
@@ -1,39 +1,52 @@
|
||||
define ["js/models/settings/course_grader"], (CourseGrader) ->
|
||||
describe "CourseGraderModel", ->
|
||||
describe "parseWeight", ->
|
||||
it "converts a float to an integer", ->
|
||||
model = new CourseGrader({weight: 7.0001, min_count: 3.67, drop_count: 1.88}, {parse:true})
|
||||
expect(model.get('weight')).toBe(7)
|
||||
expect(model.get('min_count')).toBe(4)
|
||||
expect(model.get('drop_count')).toBe(2)
|
||||
/*
|
||||
* decaffeinate suggestions:
|
||||
* DS102: Remove unnecessary code created because of implicit returns
|
||||
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
|
||||
*/
|
||||
define(["js/models/settings/course_grader"], CourseGrader =>
|
||||
describe("CourseGraderModel", () =>
|
||||
describe("parseWeight", function() {
|
||||
it("converts a float to an integer", function() {
|
||||
const model = new CourseGrader({weight: 7.0001, min_count: 3.67, drop_count: 1.88}, {parse:true});
|
||||
expect(model.get('weight')).toBe(7);
|
||||
expect(model.get('min_count')).toBe(4);
|
||||
return expect(model.get('drop_count')).toBe(2);
|
||||
});
|
||||
|
||||
it "converts float value of weight to an integer with rounding", ->
|
||||
model = new CourseGrader({weight: 28.999999999999996}, {parse:true})
|
||||
expect(model.get('weight')).toBe(29)
|
||||
it("converts float value of weight to an integer with rounding", function() {
|
||||
const model = new CourseGrader({weight: 28.999999999999996}, {parse:true});
|
||||
return expect(model.get('weight')).toBe(29);
|
||||
});
|
||||
|
||||
it "converts a string to an integer", ->
|
||||
model = new CourseGrader({weight: '7.0001', min_count: '3.67', drop_count: '1.88'}, {parse:true})
|
||||
expect(model.get('weight')).toBe(7)
|
||||
expect(model.get('min_count')).toBe(4)
|
||||
expect(model.get('drop_count')).toBe(2)
|
||||
it("converts a string to an integer", function() {
|
||||
const model = new CourseGrader({weight: '7.0001', min_count: '3.67', drop_count: '1.88'}, {parse:true});
|
||||
expect(model.get('weight')).toBe(7);
|
||||
expect(model.get('min_count')).toBe(4);
|
||||
return expect(model.get('drop_count')).toBe(2);
|
||||
});
|
||||
|
||||
it "does a no-op for integers", ->
|
||||
model = new CourseGrader({weight: 7, min_count: 3, drop_count: 1}, {parse:true})
|
||||
expect(model.get('weight')).toBe(7)
|
||||
expect(model.get('min_count')).toBe(3)
|
||||
expect(model.get('drop_count')).toBe(1)
|
||||
it("does a no-op for integers", function() {
|
||||
const model = new CourseGrader({weight: 7, min_count: 3, drop_count: 1}, {parse:true});
|
||||
expect(model.get('weight')).toBe(7);
|
||||
expect(model.get('min_count')).toBe(3);
|
||||
return expect(model.get('drop_count')).toBe(1);
|
||||
});
|
||||
|
||||
it "gives validation error if min_count is less than 1 or drop_count is NaN", ->
|
||||
model = new CourseGrader()
|
||||
errors = model.validate({min_count: 0, drop_count: ''}, {validate:true})
|
||||
expect(errors.min_count).toBe('Please enter an integer greater than 0.')
|
||||
expect(errors.drop_count).toBe('Please enter non-negative integer.')
|
||||
# don't allow negative integers
|
||||
errors = model.validate({min_count: -12, drop_count: -1}, {validate:true})
|
||||
expect(errors.min_count).toBe('Please enter an integer greater than 0.')
|
||||
expect(errors.drop_count).toBe('Please enter non-negative integer.')
|
||||
# don't allow floats
|
||||
errors = model.validate({min_count: 12.2, drop_count: 1.5}, {validate:true})
|
||||
expect(errors.min_count).toBe('Please enter an integer greater than 0.')
|
||||
expect(errors.drop_count).toBe('Please enter non-negative integer.')
|
||||
return it("gives validation error if min_count is less than 1 or drop_count is NaN", function() {
|
||||
const model = new CourseGrader();
|
||||
let errors = model.validate({min_count: 0, drop_count: ''}, {validate:true});
|
||||
expect(errors.min_count).toBe('Please enter an integer greater than 0.');
|
||||
expect(errors.drop_count).toBe('Please enter non-negative integer.');
|
||||
// don't allow negative integers
|
||||
errors = model.validate({min_count: -12, drop_count: -1}, {validate:true});
|
||||
expect(errors.min_count).toBe('Please enter an integer greater than 0.');
|
||||
expect(errors.drop_count).toBe('Please enter non-negative integer.');
|
||||
// don't allow floats
|
||||
errors = model.validate({min_count: 12.2, drop_count: 1.5}, {validate:true});
|
||||
expect(errors.min_count).toBe('Please enter an integer greater than 0.');
|
||||
return expect(errors.drop_count).toBe('Please enter non-negative integer.');
|
||||
});
|
||||
})
|
||||
)
|
||||
);
|
||||
|
||||
|
||||
@@ -1,36 +1,52 @@
|
||||
define ["underscore", "js/models/settings/course_grading_policy"], (_, CourseGradingPolicy) ->
|
||||
describe "CourseGradingPolicy", ->
|
||||
beforeEach ->
|
||||
@model = new CourseGradingPolicy()
|
||||
/*
|
||||
* decaffeinate suggestions:
|
||||
* DS102: Remove unnecessary code created because of implicit returns
|
||||
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
|
||||
*/
|
||||
define(["underscore", "js/models/settings/course_grading_policy"], (_, CourseGradingPolicy) =>
|
||||
describe("CourseGradingPolicy", function() {
|
||||
beforeEach(function() {
|
||||
return this.model = new CourseGradingPolicy();
|
||||
});
|
||||
|
||||
describe "parse", ->
|
||||
it "sets a null grace period to 00:00", ->
|
||||
attrs = @model.parse(grace_period: null)
|
||||
expect(attrs.grace_period).toEqual(
|
||||
describe("parse", () =>
|
||||
it("sets a null grace period to 00:00", function() {
|
||||
const attrs = this.model.parse({grace_period: null});
|
||||
return expect(attrs.grace_period).toEqual({
|
||||
hours: 0,
|
||||
minutes: 0
|
||||
)
|
||||
});
|
||||
})
|
||||
);
|
||||
|
||||
describe "parseGracePeriod", ->
|
||||
it "parses a time in HH:MM format", ->
|
||||
time = @model.parseGracePeriod("07:19")
|
||||
expect(time).toEqual(
|
||||
describe("parseGracePeriod", function() {
|
||||
it("parses a time in HH:MM format", function() {
|
||||
const time = this.model.parseGracePeriod("07:19");
|
||||
return expect(time).toEqual({
|
||||
hours: 7,
|
||||
minutes: 19
|
||||
)
|
||||
});
|
||||
});
|
||||
|
||||
it "returns null on an incorrectly formatted string", ->
|
||||
expect(@model.parseGracePeriod("asdf")).toBe(null)
|
||||
expect(@model.parseGracePeriod("7:19")).toBe(null)
|
||||
expect(@model.parseGracePeriod("1000:00")).toBe(null)
|
||||
return it("returns null on an incorrectly formatted string", function() {
|
||||
expect(this.model.parseGracePeriod("asdf")).toBe(null);
|
||||
expect(this.model.parseGracePeriod("7:19")).toBe(null);
|
||||
return expect(this.model.parseGracePeriod("1000:00")).toBe(null);
|
||||
});
|
||||
});
|
||||
|
||||
describe "validate", ->
|
||||
it "enforces that the passing grade is <= the minimum grade to receive credit if credit is enabled", ->
|
||||
@model.set({minimum_grade_credit: 0.8, grace_period: '01:00', is_credit_course: true})
|
||||
@model.set('grade_cutoffs', [0.9], validate: true)
|
||||
expect(_.keys(@model.validationError)).toContain('minimum_grade_credit')
|
||||
return describe("validate", function() {
|
||||
it("enforces that the passing grade is <= the minimum grade to receive credit if credit is enabled", function() {
|
||||
this.model.set({minimum_grade_credit: 0.8, grace_period: '01:00', is_credit_course: true});
|
||||
this.model.set('grade_cutoffs', [0.9], {validate: true});
|
||||
return expect(_.keys(this.model.validationError)).toContain('minimum_grade_credit');
|
||||
});
|
||||
|
||||
it "does not enforce the passing grade limit in non-credit courses", ->
|
||||
@model.set({minimum_grade_credit: 0.8, grace_period: '01:00', is_credit_course: false})
|
||||
@model.set({grade_cutoffs: [0.9]}, validate: true)
|
||||
expect(@model.validationError).toBe(null)
|
||||
return it("does not enforce the passing grade limit in non-credit courses", function() {
|
||||
this.model.set({minimum_grade_credit: 0.8, grace_period: '01:00', is_credit_course: false});
|
||||
this.model.set({grade_cutoffs: [0.9]}, {validate: true});
|
||||
return expect(this.model.validationError).toBe(null);
|
||||
});
|
||||
});
|
||||
})
|
||||
);
|
||||
|
||||
@@ -1,78 +1,98 @@
|
||||
/*
|
||||
* decaffeinate suggestions:
|
||||
* DS102: Remove unnecessary code created because of implicit returns
|
||||
* DS203: Remove `|| {}` from converted for-own loops
|
||||
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
|
||||
*/
|
||||
|
||||
define ["backbone", "js/models/textbook", "js/collections/textbook", "js/models/chapter", "js/collections/chapter", "cms/js/main"],
|
||||
(Backbone, Textbook, TextbookSet, Chapter, ChapterSet, main) ->
|
||||
define(["backbone", "js/models/textbook", "js/collections/textbook", "js/models/chapter", "js/collections/chapter", "cms/js/main"],
|
||||
function(Backbone, Textbook, TextbookSet, Chapter, ChapterSet, main) {
|
||||
|
||||
describe "Textbook model", ->
|
||||
beforeEach ->
|
||||
main()
|
||||
@model = new Textbook()
|
||||
CMS.URL.TEXTBOOKS = "/textbooks"
|
||||
describe("Textbook model", function() {
|
||||
beforeEach(function() {
|
||||
main();
|
||||
this.model = new Textbook();
|
||||
return CMS.URL.TEXTBOOKS = "/textbooks";
|
||||
});
|
||||
|
||||
afterEach ->
|
||||
delete CMS.URL.TEXTBOOKS
|
||||
afterEach(() => delete CMS.URL.TEXTBOOKS);
|
||||
|
||||
describe "Basic", ->
|
||||
it "should have an empty name by default", ->
|
||||
expect(@model.get("name")).toEqual("")
|
||||
describe("Basic", function() {
|
||||
it("should have an empty name by default", function() {
|
||||
return expect(this.model.get("name")).toEqual("");
|
||||
});
|
||||
|
||||
it "should not show chapters by default", ->
|
||||
expect(@model.get("showChapters")).toBeFalsy()
|
||||
it("should not show chapters by default", function() {
|
||||
return expect(this.model.get("showChapters")).toBeFalsy();
|
||||
});
|
||||
|
||||
it "should have a ChapterSet with one chapter by default", ->
|
||||
chapters = @model.get("chapters")
|
||||
expect(chapters).toBeInstanceOf(ChapterSet)
|
||||
expect(chapters.length).toEqual(1)
|
||||
expect(chapters.at(0).isEmpty()).toBeTruthy()
|
||||
it("should have a ChapterSet with one chapter by default", function() {
|
||||
const chapters = this.model.get("chapters");
|
||||
expect(chapters).toBeInstanceOf(ChapterSet);
|
||||
expect(chapters.length).toEqual(1);
|
||||
return expect(chapters.at(0).isEmpty()).toBeTruthy();
|
||||
});
|
||||
|
||||
it "should be empty by default", ->
|
||||
expect(@model.isEmpty()).toBeTruthy()
|
||||
it("should be empty by default", function() {
|
||||
return expect(this.model.isEmpty()).toBeTruthy();
|
||||
});
|
||||
|
||||
it "should have a URL root", ->
|
||||
urlRoot = _.result(@model, 'urlRoot')
|
||||
expect(urlRoot).toBeTruthy()
|
||||
it("should have a URL root", function() {
|
||||
const urlRoot = _.result(this.model, 'urlRoot');
|
||||
return expect(urlRoot).toBeTruthy();
|
||||
});
|
||||
|
||||
it "should be able to reset itself", ->
|
||||
@model.set("name", "foobar")
|
||||
@model.reset()
|
||||
expect(@model.get("name")).toEqual("")
|
||||
it("should be able to reset itself", function() {
|
||||
this.model.set("name", "foobar");
|
||||
this.model.reset();
|
||||
return expect(this.model.get("name")).toEqual("");
|
||||
});
|
||||
|
||||
it "should not be dirty by default", ->
|
||||
expect(@model.isDirty()).toBeFalsy()
|
||||
it("should not be dirty by default", function() {
|
||||
return expect(this.model.isDirty()).toBeFalsy();
|
||||
});
|
||||
|
||||
it "should be dirty after it's been changed", ->
|
||||
@model.set("name", "foobar")
|
||||
expect(@model.isDirty()).toBeTruthy()
|
||||
it("should be dirty after it's been changed", function() {
|
||||
this.model.set("name", "foobar");
|
||||
return expect(this.model.isDirty()).toBeTruthy();
|
||||
});
|
||||
|
||||
it "should not be dirty after calling setOriginalAttributes", ->
|
||||
@model.set("name", "foobar")
|
||||
@model.setOriginalAttributes()
|
||||
expect(@model.isDirty()).toBeFalsy()
|
||||
return it("should not be dirty after calling setOriginalAttributes", function() {
|
||||
this.model.set("name", "foobar");
|
||||
this.model.setOriginalAttributes();
|
||||
return expect(this.model.isDirty()).toBeFalsy();
|
||||
});
|
||||
});
|
||||
|
||||
describe "Input/Output", ->
|
||||
deepAttributes = (obj) ->
|
||||
if obj instanceof Backbone.Model
|
||||
deepAttributes(obj.attributes)
|
||||
else if obj instanceof Backbone.Collection
|
||||
obj.map(deepAttributes);
|
||||
else if _.isArray(obj)
|
||||
_.map(obj, deepAttributes);
|
||||
else if _.isObject(obj)
|
||||
attributes = {};
|
||||
for own prop, val of obj
|
||||
attributes[prop] = deepAttributes(val)
|
||||
attributes
|
||||
else
|
||||
obj
|
||||
describe("Input/Output", function() {
|
||||
var deepAttributes = function(obj) {
|
||||
if (obj instanceof Backbone.Model) {
|
||||
return deepAttributes(obj.attributes);
|
||||
} else if (obj instanceof Backbone.Collection) {
|
||||
return obj.map(deepAttributes);
|
||||
} else if (_.isArray(obj)) {
|
||||
return _.map(obj, deepAttributes);
|
||||
} else if (_.isObject(obj)) {
|
||||
const attributes = {};
|
||||
for (let prop of Object.keys(obj || {})) {
|
||||
const val = obj[prop];
|
||||
attributes[prop] = deepAttributes(val);
|
||||
}
|
||||
return attributes;
|
||||
} else {
|
||||
return obj;
|
||||
}
|
||||
};
|
||||
|
||||
it "should match server model to client model", ->
|
||||
serverModelSpec = {
|
||||
return it("should match server model to client model", function() {
|
||||
const serverModelSpec = {
|
||||
"tab_title": "My Textbook",
|
||||
"chapters": [
|
||||
{"title": "Chapter 1", "url": "/ch1.pdf"},
|
||||
{"title": "Chapter 2", "url": "/ch2.pdf"},
|
||||
]
|
||||
}
|
||||
clientModelSpec = {
|
||||
};
|
||||
const clientModelSpec = {
|
||||
"name": "My Textbook",
|
||||
"showChapters": false,
|
||||
"editing": false,
|
||||
@@ -86,113 +106,142 @@ define ["backbone", "js/models/textbook", "js/collections/textbook", "js/models/
|
||||
"order": 2
|
||||
}
|
||||
]
|
||||
}
|
||||
};
|
||||
|
||||
model = new Textbook(serverModelSpec, {parse: true})
|
||||
expect(deepAttributes(model)).toEqual(clientModelSpec)
|
||||
expect(model.toJSON()).toEqual(serverModelSpec)
|
||||
const model = new Textbook(serverModelSpec, {parse: true});
|
||||
expect(deepAttributes(model)).toEqual(clientModelSpec);
|
||||
return expect(model.toJSON()).toEqual(serverModelSpec);
|
||||
});
|
||||
});
|
||||
|
||||
describe "Validation", ->
|
||||
it "requires a name", ->
|
||||
model = new Textbook({name: ""})
|
||||
expect(model.isValid()).toBeFalsy()
|
||||
return describe("Validation", function() {
|
||||
it("requires a name", function() {
|
||||
const model = new Textbook({name: ""});
|
||||
return expect(model.isValid()).toBeFalsy();
|
||||
});
|
||||
|
||||
it "requires at least one chapter", ->
|
||||
model = new Textbook({name: "foo"})
|
||||
model.get("chapters").reset()
|
||||
expect(model.isValid()).toBeFalsy()
|
||||
it("requires at least one chapter", function() {
|
||||
const model = new Textbook({name: "foo"});
|
||||
model.get("chapters").reset();
|
||||
return expect(model.isValid()).toBeFalsy();
|
||||
});
|
||||
|
||||
it "requires a valid chapter", ->
|
||||
chapter = new Chapter()
|
||||
chapter.isValid = -> false
|
||||
model = new Textbook({name: "foo"})
|
||||
model.get("chapters").reset([chapter])
|
||||
expect(model.isValid()).toBeFalsy()
|
||||
it("requires a valid chapter", function() {
|
||||
const chapter = new Chapter();
|
||||
chapter.isValid = () => false;
|
||||
const model = new Textbook({name: "foo"});
|
||||
model.get("chapters").reset([chapter]);
|
||||
return expect(model.isValid()).toBeFalsy();
|
||||
});
|
||||
|
||||
it "requires all chapters to be valid", ->
|
||||
chapter1 = new Chapter()
|
||||
chapter1.isValid = -> true
|
||||
chapter2 = new Chapter()
|
||||
chapter2.isValid = -> false
|
||||
model = new Textbook({name: "foo"})
|
||||
model.get("chapters").reset([chapter1, chapter2])
|
||||
expect(model.isValid()).toBeFalsy()
|
||||
it("requires all chapters to be valid", function() {
|
||||
const chapter1 = new Chapter();
|
||||
chapter1.isValid = () => true;
|
||||
const chapter2 = new Chapter();
|
||||
chapter2.isValid = () => false;
|
||||
const model = new Textbook({name: "foo"});
|
||||
model.get("chapters").reset([chapter1, chapter2]);
|
||||
return expect(model.isValid()).toBeFalsy();
|
||||
});
|
||||
|
||||
it "can pass validation", ->
|
||||
chapter = new Chapter()
|
||||
chapter.isValid = -> true
|
||||
model = new Textbook({name: "foo"})
|
||||
model.get("chapters").reset([chapter])
|
||||
expect(model.isValid()).toBeTruthy()
|
||||
return it("can pass validation", function() {
|
||||
const chapter = new Chapter();
|
||||
chapter.isValid = () => true;
|
||||
const model = new Textbook({name: "foo"});
|
||||
model.get("chapters").reset([chapter]);
|
||||
return expect(model.isValid()).toBeTruthy();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
describe "Textbook collection", ->
|
||||
beforeEach ->
|
||||
CMS.URL.TEXTBOOKS = "/textbooks"
|
||||
@collection = new TextbookSet()
|
||||
describe("Textbook collection", function() {
|
||||
beforeEach(function() {
|
||||
CMS.URL.TEXTBOOKS = "/textbooks";
|
||||
return this.collection = new TextbookSet();
|
||||
});
|
||||
|
||||
afterEach ->
|
||||
delete CMS.URL.TEXTBOOKS
|
||||
afterEach(() => delete CMS.URL.TEXTBOOKS);
|
||||
|
||||
it "should have a url set", ->
|
||||
url = _.result(@collection, 'url')
|
||||
expect(url).toEqual("/textbooks")
|
||||
return it("should have a url set", function() {
|
||||
const url = _.result(this.collection, 'url');
|
||||
return expect(url).toEqual("/textbooks");
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
describe "Chapter model", ->
|
||||
beforeEach ->
|
||||
@model = new Chapter()
|
||||
describe("Chapter model", function() {
|
||||
beforeEach(function() {
|
||||
return this.model = new Chapter();
|
||||
});
|
||||
|
||||
describe "Basic", ->
|
||||
it "should have a name by default", ->
|
||||
expect(@model.get("name")).toEqual("")
|
||||
describe("Basic", function() {
|
||||
it("should have a name by default", function() {
|
||||
return expect(this.model.get("name")).toEqual("");
|
||||
});
|
||||
|
||||
it "should have an asset_path by default", ->
|
||||
expect(@model.get("asset_path")).toEqual("")
|
||||
it("should have an asset_path by default", function() {
|
||||
return expect(this.model.get("asset_path")).toEqual("");
|
||||
});
|
||||
|
||||
it "should have an order by default", ->
|
||||
expect(@model.get("order")).toEqual(1)
|
||||
it("should have an order by default", function() {
|
||||
return expect(this.model.get("order")).toEqual(1);
|
||||
});
|
||||
|
||||
it "should be empty by default", ->
|
||||
expect(@model.isEmpty()).toBeTruthy()
|
||||
return it("should be empty by default", function() {
|
||||
return expect(this.model.isEmpty()).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
describe "Validation", ->
|
||||
it "requires a name", ->
|
||||
model = new Chapter({name: "", asset_path: "a.pdf"})
|
||||
expect(model.isValid()).toBeFalsy()
|
||||
return describe("Validation", function() {
|
||||
it("requires a name", function() {
|
||||
const model = new Chapter({name: "", asset_path: "a.pdf"});
|
||||
return expect(model.isValid()).toBeFalsy();
|
||||
});
|
||||
|
||||
it "requires an asset_path", ->
|
||||
model = new Chapter({name: "a", asset_path: ""})
|
||||
expect(model.isValid()).toBeFalsy()
|
||||
it("requires an asset_path", function() {
|
||||
const model = new Chapter({name: "a", asset_path: ""});
|
||||
return expect(model.isValid()).toBeFalsy();
|
||||
});
|
||||
|
||||
it "can pass validation", ->
|
||||
model = new Chapter({name: "a", asset_path: "a.pdf"})
|
||||
expect(model.isValid()).toBeTruthy()
|
||||
return it("can pass validation", function() {
|
||||
const model = new Chapter({name: "a", asset_path: "a.pdf"});
|
||||
return expect(model.isValid()).toBeTruthy();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
describe "Chapter collection", ->
|
||||
beforeEach ->
|
||||
@collection = new ChapterSet()
|
||||
return describe("Chapter collection", function() {
|
||||
beforeEach(function() {
|
||||
return this.collection = new ChapterSet();
|
||||
});
|
||||
|
||||
it "is empty by default", ->
|
||||
expect(@collection.isEmpty()).toBeTruthy()
|
||||
it("is empty by default", function() {
|
||||
return expect(this.collection.isEmpty()).toBeTruthy();
|
||||
});
|
||||
|
||||
it "is empty if all chapters are empty", ->
|
||||
@collection.add([{}, {}, {}])
|
||||
expect(@collection.isEmpty()).toBeTruthy()
|
||||
it("is empty if all chapters are empty", function() {
|
||||
this.collection.add([{}, {}, {}]);
|
||||
return expect(this.collection.isEmpty()).toBeTruthy();
|
||||
});
|
||||
|
||||
it "is not empty if a chapter is not empty", ->
|
||||
@collection.add([{}, {name: "full"}, {}])
|
||||
expect(@collection.isEmpty()).toBeFalsy()
|
||||
it("is not empty if a chapter is not empty", function() {
|
||||
this.collection.add([{}, {name: "full"}, {}]);
|
||||
return expect(this.collection.isEmpty()).toBeFalsy();
|
||||
});
|
||||
|
||||
it "should have a nextOrder function", ->
|
||||
expect(@collection.nextOrder()).toEqual(1)
|
||||
@collection.add([{}])
|
||||
expect(@collection.nextOrder()).toEqual(2)
|
||||
@collection.add([{}])
|
||||
expect(@collection.nextOrder()).toEqual(3)
|
||||
# verify that it doesn't just return an incrementing value each time
|
||||
expect(@collection.nextOrder()).toEqual(3)
|
||||
# try going back one
|
||||
@collection.remove(@collection.last())
|
||||
expect(@collection.nextOrder()).toEqual(2)
|
||||
return it("should have a nextOrder function", function() {
|
||||
expect(this.collection.nextOrder()).toEqual(1);
|
||||
this.collection.add([{}]);
|
||||
expect(this.collection.nextOrder()).toEqual(2);
|
||||
this.collection.add([{}]);
|
||||
expect(this.collection.nextOrder()).toEqual(3);
|
||||
// verify that it doesn't just return an incrementing value each time
|
||||
expect(this.collection.nextOrder()).toEqual(3);
|
||||
// try going back one
|
||||
this.collection.remove(this.collection.last());
|
||||
return expect(this.collection.nextOrder()).toEqual(2);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,71 +1,93 @@
|
||||
define ["js/models/uploads"], (FileUpload) ->
|
||||
/*
|
||||
* decaffeinate suggestions:
|
||||
* DS102: Remove unnecessary code created because of implicit returns
|
||||
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
|
||||
*/
|
||||
define(["js/models/uploads"], FileUpload =>
|
||||
|
||||
describe "FileUpload", ->
|
||||
beforeEach ->
|
||||
@model = new FileUpload()
|
||||
describe("FileUpload", function() {
|
||||
beforeEach(function() {
|
||||
return this.model = new FileUpload();
|
||||
});
|
||||
|
||||
it "is unfinished by default", ->
|
||||
expect(@model.get("finished")).toBeFalsy()
|
||||
it("is unfinished by default", function() {
|
||||
return expect(this.model.get("finished")).toBeFalsy();
|
||||
});
|
||||
|
||||
it "is not uploading by default", ->
|
||||
expect(@model.get("uploading")).toBeFalsy()
|
||||
it("is not uploading by default", function() {
|
||||
return expect(this.model.get("uploading")).toBeFalsy();
|
||||
});
|
||||
|
||||
it "is valid by default", ->
|
||||
expect(@model.isValid()).toBeTruthy()
|
||||
it("is valid by default", function() {
|
||||
return expect(this.model.isValid()).toBeTruthy();
|
||||
});
|
||||
|
||||
it "is valid for text files by default", ->
|
||||
file = {"type": "text/plain", "name": "filename.txt"}
|
||||
@model.set("selectedFile", file);
|
||||
expect(@model.isValid()).toBeTruthy()
|
||||
it("is valid for text files by default", function() {
|
||||
const file = {"type": "text/plain", "name": "filename.txt"};
|
||||
this.model.set("selectedFile", file);
|
||||
return expect(this.model.isValid()).toBeTruthy();
|
||||
});
|
||||
|
||||
it "is valid for PNG files by default", ->
|
||||
file = {"type": "image/png", "name": "filename.png"}
|
||||
@model.set("selectedFile", file);
|
||||
expect(@model.isValid()).toBeTruthy()
|
||||
it("is valid for PNG files by default", function() {
|
||||
const file = {"type": "image/png", "name": "filename.png"};
|
||||
this.model.set("selectedFile", file);
|
||||
return expect(this.model.isValid()).toBeTruthy();
|
||||
});
|
||||
|
||||
it "can accept a file type when explicitly set", ->
|
||||
file = {"type": "image/png", "name": "filename.png"}
|
||||
@model.set("mimeTypes": ["image/png"])
|
||||
@model.set("selectedFile", file)
|
||||
expect(@model.isValid()).toBeTruthy()
|
||||
it("can accept a file type when explicitly set", function() {
|
||||
const file = {"type": "image/png", "name": "filename.png"};
|
||||
this.model.set({"mimeTypes": ["image/png"]});
|
||||
this.model.set("selectedFile", file);
|
||||
return expect(this.model.isValid()).toBeTruthy();
|
||||
});
|
||||
|
||||
it "can accept a file format when explicitly set", ->
|
||||
file = {"type": "", "name": "filename.png"}
|
||||
@model.set("fileFormats": ["png"])
|
||||
@model.set("selectedFile", file)
|
||||
expect(@model.isValid()).toBeTruthy()
|
||||
it("can accept a file format when explicitly set", function() {
|
||||
const file = {"type": "", "name": "filename.png"};
|
||||
this.model.set({"fileFormats": ["png"]});
|
||||
this.model.set("selectedFile", file);
|
||||
return expect(this.model.isValid()).toBeTruthy();
|
||||
});
|
||||
|
||||
it "can accept multiple file types", ->
|
||||
file = {"type": "image/gif", "name": "filename.gif"}
|
||||
@model.set("mimeTypes": ["image/png", "image/jpeg", "image/gif"])
|
||||
@model.set("selectedFile", file)
|
||||
expect(@model.isValid()).toBeTruthy()
|
||||
it("can accept multiple file types", function() {
|
||||
const file = {"type": "image/gif", "name": "filename.gif"};
|
||||
this.model.set({"mimeTypes": ["image/png", "image/jpeg", "image/gif"]});
|
||||
this.model.set("selectedFile", file);
|
||||
return expect(this.model.isValid()).toBeTruthy();
|
||||
});
|
||||
|
||||
it "can accept multiple file formats", ->
|
||||
file = {"type": "image/gif", "name": "filename.gif"}
|
||||
@model.set("fileFormats": ["png", "jpeg", "gif"])
|
||||
@model.set("selectedFile", file)
|
||||
expect(@model.isValid()).toBeTruthy()
|
||||
it("can accept multiple file formats", function() {
|
||||
const file = {"type": "image/gif", "name": "filename.gif"};
|
||||
this.model.set({"fileFormats": ["png", "jpeg", "gif"]});
|
||||
this.model.set("selectedFile", file);
|
||||
return expect(this.model.isValid()).toBeTruthy();
|
||||
});
|
||||
|
||||
describe "fileTypes", ->
|
||||
it "returns a list of the uploader's file types", ->
|
||||
@model.set('mimeTypes', ['image/png', 'application/json'])
|
||||
@model.set('fileFormats', ['gif', 'srt'])
|
||||
expect(@model.fileTypes()).toEqual(['PNG', 'JSON', 'GIF', 'SRT'])
|
||||
describe("fileTypes", () =>
|
||||
it("returns a list of the uploader's file types", function() {
|
||||
this.model.set('mimeTypes', ['image/png', 'application/json']);
|
||||
this.model.set('fileFormats', ['gif', 'srt']);
|
||||
return expect(this.model.fileTypes()).toEqual(['PNG', 'JSON', 'GIF', 'SRT']);
|
||||
})
|
||||
);
|
||||
|
||||
describe "formatValidTypes", ->
|
||||
it "returns a map of formatted file types and extensions", ->
|
||||
@model.set('mimeTypes', ['image/png', 'image/jpeg', 'application/json'])
|
||||
formatted = @model.formatValidTypes()
|
||||
expect(formatted).toEqual(
|
||||
return describe("formatValidTypes", function() {
|
||||
it("returns a map of formatted file types and extensions", function() {
|
||||
this.model.set('mimeTypes', ['image/png', 'image/jpeg', 'application/json']);
|
||||
const formatted = this.model.formatValidTypes();
|
||||
return expect(formatted).toEqual({
|
||||
fileTypes: 'PNG, JPEG or JSON',
|
||||
fileExtensions: '.png, .jpeg or .json'
|
||||
)
|
||||
});
|
||||
});
|
||||
|
||||
it "does not format with only one mime type", ->
|
||||
@model.set('mimeTypes', ['application/pdf'])
|
||||
formatted = @model.formatValidTypes()
|
||||
expect(formatted).toEqual(
|
||||
return it("does not format with only one mime type", function() {
|
||||
this.model.set('mimeTypes', ['application/pdf']);
|
||||
const formatted = this.model.formatValidTypes();
|
||||
return expect(formatted).toEqual({
|
||||
fileTypes: 'PDF',
|
||||
fileExtensions: '.pdf'
|
||||
)
|
||||
});
|
||||
});
|
||||
});
|
||||
})
|
||||
);
|
||||
|
||||
@@ -1,221 +1,252 @@
|
||||
define ["jquery", "edx-ui-toolkit/js/utils/spec-helpers/ajax-helpers", "squire"],
|
||||
($, AjaxHelpers, Squire) ->
|
||||
/*
|
||||
* decaffeinate suggestions:
|
||||
* DS102: Remove unnecessary code created because of implicit returns
|
||||
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
|
||||
*/
|
||||
define(["jquery", "edx-ui-toolkit/js/utils/spec-helpers/ajax-helpers", "squire"],
|
||||
function($, AjaxHelpers, Squire) {
|
||||
|
||||
assetLibraryTpl = readFixtures('asset-library.underscore')
|
||||
assetTpl = readFixtures('asset.underscore')
|
||||
const assetLibraryTpl = readFixtures('asset-library.underscore');
|
||||
const assetTpl = readFixtures('asset.underscore');
|
||||
|
||||
describe "Asset view", ->
|
||||
beforeEach (done) ->
|
||||
setFixtures($("<script>", {id: "asset-tpl", type: "text/template"}).text(assetTpl))
|
||||
appendSetFixtures(sandbox({id: "page-prompt"}))
|
||||
describe("Asset view", function() {
|
||||
beforeEach(function(done) {
|
||||
setFixtures($("<script>", {id: "asset-tpl", type: "text/template"}).text(assetTpl));
|
||||
appendSetFixtures(sandbox({id: "page-prompt"}));
|
||||
|
||||
@promptSpies = jasmine.createSpyObj('Prompt.Warning', ["constructor", "show", "hide"])
|
||||
@promptSpies.constructor.and.returnValue(@promptSpies)
|
||||
@promptSpies.show.and.returnValue(@promptSpies)
|
||||
this.promptSpies = jasmine.createSpyObj('Prompt.Warning', ["constructor", "show", "hide"]);
|
||||
this.promptSpies.constructor.and.returnValue(this.promptSpies);
|
||||
this.promptSpies.show.and.returnValue(this.promptSpies);
|
||||
|
||||
@confirmationSpies = jasmine.createSpyObj('Notification.Confirmation', ["constructor", "show"])
|
||||
@confirmationSpies.constructor.and.returnValue(@confirmationSpies)
|
||||
@confirmationSpies.show.and.returnValue(@confirmationSpies)
|
||||
this.confirmationSpies = jasmine.createSpyObj('Notification.Confirmation', ["constructor", "show"]);
|
||||
this.confirmationSpies.constructor.and.returnValue(this.confirmationSpies);
|
||||
this.confirmationSpies.show.and.returnValue(this.confirmationSpies);
|
||||
|
||||
@savingSpies = jasmine.createSpyObj('Notification.Mini', ["constructor", "show", "hide"])
|
||||
@savingSpies.constructor.and.returnValue(@savingSpies)
|
||||
@savingSpies.show.and.returnValue(@savingSpies)
|
||||
this.savingSpies = jasmine.createSpyObj('Notification.Mini', ["constructor", "show", "hide"]);
|
||||
this.savingSpies.constructor.and.returnValue(this.savingSpies);
|
||||
this.savingSpies.show.and.returnValue(this.savingSpies);
|
||||
|
||||
@injector = new Squire()
|
||||
@injector.mock("common/js/components/views/feedback_prompt", {
|
||||
"Warning": @promptSpies.constructor
|
||||
})
|
||||
@injector.mock("common/js/components/views/feedback_notification", {
|
||||
"Confirmation": @confirmationSpies.constructor,
|
||||
"Mini": @savingSpies.constructor
|
||||
})
|
||||
this.injector = new Squire();
|
||||
this.injector.mock("common/js/components/views/feedback_prompt", {
|
||||
"Warning": this.promptSpies.constructor
|
||||
});
|
||||
this.injector.mock("common/js/components/views/feedback_notification", {
|
||||
"Confirmation": this.confirmationSpies.constructor,
|
||||
"Mini": this.savingSpies.constructor
|
||||
});
|
||||
|
||||
@injector.require ["js/models/asset", "js/collections/asset", "js/views/asset"],
|
||||
(AssetModel, AssetCollection, AssetView) =>
|
||||
@model = new AssetModel
|
||||
display_name: "test asset"
|
||||
url: 'actual_asset_url'
|
||||
portable_url: 'portable_url'
|
||||
date_added: 'date'
|
||||
thumbnail: null
|
||||
return this.injector.require(["js/models/asset", "js/collections/asset", "js/views/asset"],
|
||||
(AssetModel, AssetCollection, AssetView) => {
|
||||
this.model = new AssetModel({
|
||||
display_name: "test asset",
|
||||
url: 'actual_asset_url',
|
||||
portable_url: 'portable_url',
|
||||
date_added: 'date',
|
||||
thumbnail: null,
|
||||
id: 'id'
|
||||
spyOn(@model, "destroy").and.callThrough()
|
||||
spyOn(@model, "save").and.callThrough()
|
||||
});
|
||||
spyOn(this.model, "destroy").and.callThrough();
|
||||
spyOn(this.model, "save").and.callThrough();
|
||||
|
||||
@collection = new AssetCollection([@model])
|
||||
@collection.url = "assets-url"
|
||||
@createAssetView = (test) =>
|
||||
view = new AssetView({model: @model})
|
||||
requests = if test then AjaxHelpers["requests"](test) else null
|
||||
return {view: view, requests: requests}
|
||||
done()
|
||||
this.collection = new AssetCollection([this.model]);
|
||||
this.collection.url = "assets-url";
|
||||
this.createAssetView = test => {
|
||||
const view = new AssetView({model: this.model});
|
||||
const requests = test ? AjaxHelpers["requests"](test) : null;
|
||||
return {view, requests};
|
||||
};
|
||||
return done();
|
||||
});
|
||||
});
|
||||
|
||||
afterEach ->
|
||||
@injector.clean()
|
||||
@injector.remove()
|
||||
afterEach(function() {
|
||||
this.injector.clean();
|
||||
return this.injector.remove();
|
||||
});
|
||||
|
||||
describe "Basic", ->
|
||||
it "should render properly", ->
|
||||
{view: @view, requests: requests} = @createAssetView()
|
||||
@view.render()
|
||||
expect(@view.$el).toContainText("test asset")
|
||||
describe("Basic", function() {
|
||||
it("should render properly", function() {
|
||||
let requests;
|
||||
({view: this.view, requests} = this.createAssetView());
|
||||
this.view.render();
|
||||
return expect(this.view.$el).toContainText("test asset");
|
||||
});
|
||||
|
||||
it "should pop a delete confirmation when the delete button is clicked", ->
|
||||
{view: @view, requests: requests} = @createAssetView()
|
||||
@view.render().$(".remove-asset-button").click()
|
||||
expect(@promptSpies.constructor).toHaveBeenCalled()
|
||||
ctorOptions = @promptSpies.constructor.calls.mostRecent().args[0]
|
||||
expect(ctorOptions.title).toMatch('Delete File Confirmation')
|
||||
# hasn't actually been removed
|
||||
expect(@model.destroy).not.toHaveBeenCalled()
|
||||
expect(@collection).toContain(@model)
|
||||
return it("should pop a delete confirmation when the delete button is clicked", function() {
|
||||
let requests;
|
||||
({view: this.view, requests} = this.createAssetView());
|
||||
this.view.render().$(".remove-asset-button").click();
|
||||
expect(this.promptSpies.constructor).toHaveBeenCalled();
|
||||
const ctorOptions = this.promptSpies.constructor.calls.mostRecent().args[0];
|
||||
expect(ctorOptions.title).toMatch('Delete File Confirmation');
|
||||
// hasn't actually been removed
|
||||
expect(this.model.destroy).not.toHaveBeenCalled();
|
||||
return expect(this.collection).toContain(this.model);
|
||||
});
|
||||
});
|
||||
|
||||
describe "AJAX", ->
|
||||
it "should destroy itself on confirmation", ->
|
||||
{view: @view, requests: requests} = @createAssetView(this)
|
||||
return describe("AJAX", function() {
|
||||
it("should destroy itself on confirmation", function() {
|
||||
let requests;
|
||||
({view: this.view, requests} = this.createAssetView(this));
|
||||
|
||||
@view.render().$(".remove-asset-button").click()
|
||||
ctorOptions = @promptSpies.constructor.calls.mostRecent().args[0]
|
||||
# run the primary function to indicate confirmation
|
||||
ctorOptions.actions.primary.click(@promptSpies)
|
||||
# AJAX request has been sent, but not yet returned
|
||||
expect(@model.destroy).toHaveBeenCalled()
|
||||
expect(requests.length).toEqual(1)
|
||||
expect(@confirmationSpies.constructor).not.toHaveBeenCalled()
|
||||
expect(@collection.contains(@model)).toBeTruthy()
|
||||
# return a success response
|
||||
requests[0].respond(204)
|
||||
expect(@confirmationSpies.constructor).toHaveBeenCalled()
|
||||
expect(@confirmationSpies.show).toHaveBeenCalled()
|
||||
savingOptions = @confirmationSpies.constructor.calls.mostRecent().args[0]
|
||||
expect(savingOptions.title).toMatch("Your file has been deleted.")
|
||||
expect(@collection.contains(@model)).toBeFalsy()
|
||||
this.view.render().$(".remove-asset-button").click();
|
||||
const ctorOptions = this.promptSpies.constructor.calls.mostRecent().args[0];
|
||||
// run the primary function to indicate confirmation
|
||||
ctorOptions.actions.primary.click(this.promptSpies);
|
||||
// AJAX request has been sent, but not yet returned
|
||||
expect(this.model.destroy).toHaveBeenCalled();
|
||||
expect(requests.length).toEqual(1);
|
||||
expect(this.confirmationSpies.constructor).not.toHaveBeenCalled();
|
||||
expect(this.collection.contains(this.model)).toBeTruthy();
|
||||
// return a success response
|
||||
requests[0].respond(204);
|
||||
expect(this.confirmationSpies.constructor).toHaveBeenCalled();
|
||||
expect(this.confirmationSpies.show).toHaveBeenCalled();
|
||||
const savingOptions = this.confirmationSpies.constructor.calls.mostRecent().args[0];
|
||||
expect(savingOptions.title).toMatch("Your file has been deleted.");
|
||||
return expect(this.collection.contains(this.model)).toBeFalsy();
|
||||
});
|
||||
|
||||
it "should not destroy itself if server errors", ->
|
||||
{view: @view, requests: requests} = @createAssetView(this)
|
||||
it("should not destroy itself if server errors", function() {
|
||||
let requests;
|
||||
({view: this.view, requests} = this.createAssetView(this));
|
||||
|
||||
@view.render().$(".remove-asset-button").click()
|
||||
ctorOptions = @promptSpies.constructor.calls.mostRecent().args[0]
|
||||
# run the primary function to indicate confirmation
|
||||
ctorOptions.actions.primary.click(@promptSpies)
|
||||
# AJAX request has been sent, but not yet returned
|
||||
expect(@model.destroy).toHaveBeenCalled()
|
||||
# return an error response
|
||||
requests[0].respond(404)
|
||||
expect(@confirmationSpies.constructor).not.toHaveBeenCalled()
|
||||
expect(@collection.contains(@model)).toBeTruthy()
|
||||
this.view.render().$(".remove-asset-button").click();
|
||||
const ctorOptions = this.promptSpies.constructor.calls.mostRecent().args[0];
|
||||
// run the primary function to indicate confirmation
|
||||
ctorOptions.actions.primary.click(this.promptSpies);
|
||||
// AJAX request has been sent, but not yet returned
|
||||
expect(this.model.destroy).toHaveBeenCalled();
|
||||
// return an error response
|
||||
requests[0].respond(404);
|
||||
expect(this.confirmationSpies.constructor).not.toHaveBeenCalled();
|
||||
return expect(this.collection.contains(this.model)).toBeTruthy();
|
||||
});
|
||||
|
||||
it "should lock the asset on confirmation", ->
|
||||
{view: @view, requests: requests} = @createAssetView(this)
|
||||
it("should lock the asset on confirmation", function() {
|
||||
let requests;
|
||||
({view: this.view, requests} = this.createAssetView(this));
|
||||
|
||||
@view.render().$(".lock-checkbox").click()
|
||||
# AJAX request has been sent, but not yet returned
|
||||
expect(@model.save).toHaveBeenCalled()
|
||||
expect(requests.length).toEqual(1)
|
||||
expect(@savingSpies.constructor).toHaveBeenCalled()
|
||||
expect(@savingSpies.show).toHaveBeenCalled()
|
||||
savingOptions = @savingSpies.constructor.calls.mostRecent().args[0]
|
||||
expect(savingOptions.title).toMatch("Saving")
|
||||
expect(@model.get("locked")).toBeFalsy()
|
||||
# return a success response
|
||||
requests[0].respond(204)
|
||||
expect(@savingSpies.hide).toHaveBeenCalled()
|
||||
expect(@model.get("locked")).toBeTruthy()
|
||||
this.view.render().$(".lock-checkbox").click();
|
||||
// AJAX request has been sent, but not yet returned
|
||||
expect(this.model.save).toHaveBeenCalled();
|
||||
expect(requests.length).toEqual(1);
|
||||
expect(this.savingSpies.constructor).toHaveBeenCalled();
|
||||
expect(this.savingSpies.show).toHaveBeenCalled();
|
||||
const savingOptions = this.savingSpies.constructor.calls.mostRecent().args[0];
|
||||
expect(savingOptions.title).toMatch("Saving");
|
||||
expect(this.model.get("locked")).toBeFalsy();
|
||||
// return a success response
|
||||
requests[0].respond(204);
|
||||
expect(this.savingSpies.hide).toHaveBeenCalled();
|
||||
return expect(this.model.get("locked")).toBeTruthy();
|
||||
});
|
||||
|
||||
it "should not lock the asset if server errors", ->
|
||||
{view: @view, requests: requests} = @createAssetView(this)
|
||||
return it("should not lock the asset if server errors", function() {
|
||||
let requests;
|
||||
({view: this.view, requests} = this.createAssetView(this));
|
||||
|
||||
@view.render().$(".lock-checkbox").click()
|
||||
# return an error response
|
||||
requests[0].respond(404)
|
||||
# Don't call hide because that closes the notification showing the server error.
|
||||
expect(@savingSpies.hide).not.toHaveBeenCalled()
|
||||
expect(@model.get("locked")).toBeFalsy()
|
||||
this.view.render().$(".lock-checkbox").click();
|
||||
// return an error response
|
||||
requests[0].respond(404);
|
||||
// Don't call hide because that closes the notification showing the server error.
|
||||
expect(this.savingSpies.hide).not.toHaveBeenCalled();
|
||||
return expect(this.model.get("locked")).toBeFalsy();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe "Assets view", ->
|
||||
beforeEach (done) ->
|
||||
setFixtures($("<script>", {id: "asset-library-tpl", type: "text/template"}).text(assetLibraryTpl))
|
||||
appendSetFixtures($("<script>", {id: "asset-tpl", type: "text/template"}).text(assetTpl))
|
||||
window.analytics = jasmine.createSpyObj('analytics', ['track'])
|
||||
window.course_location_analytics = jasmine.createSpy()
|
||||
appendSetFixtures(sandbox({id: "asset_table_body"}))
|
||||
return describe("Assets view", function() {
|
||||
beforeEach(function(done) {
|
||||
setFixtures($("<script>", {id: "asset-library-tpl", type: "text/template"}).text(assetLibraryTpl));
|
||||
appendSetFixtures($("<script>", {id: "asset-tpl", type: "text/template"}).text(assetTpl));
|
||||
window.analytics = jasmine.createSpyObj('analytics', ['track']);
|
||||
window.course_location_analytics = jasmine.createSpy();
|
||||
appendSetFixtures(sandbox({id: "asset_table_body"}));
|
||||
|
||||
@promptSpies = jasmine.createSpyObj('Prompt.Warning', ["constructor", "show", "hide"])
|
||||
@promptSpies.constructor.and.returnValue(@promptSpies)
|
||||
@promptSpies.show.and.returnValue(@promptSpies)
|
||||
this.promptSpies = jasmine.createSpyObj('Prompt.Warning', ["constructor", "show", "hide"]);
|
||||
this.promptSpies.constructor.and.returnValue(this.promptSpies);
|
||||
this.promptSpies.show.and.returnValue(this.promptSpies);
|
||||
|
||||
@injector = new Squire()
|
||||
@injector.mock("common/js/components/views/feedback_prompt", {
|
||||
"Warning": @promptSpies.constructor
|
||||
})
|
||||
this.injector = new Squire();
|
||||
this.injector.mock("common/js/components/views/feedback_prompt", {
|
||||
"Warning": this.promptSpies.constructor
|
||||
});
|
||||
|
||||
@mockAsset1 = {
|
||||
display_name: "test asset 1"
|
||||
url: 'actual_asset_url_1'
|
||||
portable_url: 'portable_url_1'
|
||||
date_added: 'date_1'
|
||||
thumbnail: null
|
||||
this.mockAsset1 = {
|
||||
display_name: "test asset 1",
|
||||
url: 'actual_asset_url_1',
|
||||
portable_url: 'portable_url_1',
|
||||
date_added: 'date_1',
|
||||
thumbnail: null,
|
||||
id: 'id_1'
|
||||
}
|
||||
@mockAsset2 = {
|
||||
display_name: "test asset 2"
|
||||
url: 'actual_asset_url_2'
|
||||
portable_url: 'portable_url_2'
|
||||
date_added: 'date_2'
|
||||
thumbnail: null
|
||||
};
|
||||
this.mockAsset2 = {
|
||||
display_name: "test asset 2",
|
||||
url: 'actual_asset_url_2',
|
||||
portable_url: 'portable_url_2',
|
||||
date_added: 'date_2',
|
||||
thumbnail: null,
|
||||
id: 'id_2'
|
||||
}
|
||||
@mockAssetsResponse = {
|
||||
assets: [ @mockAsset1, @mockAsset2 ],
|
||||
};
|
||||
this.mockAssetsResponse = {
|
||||
assets: [ this.mockAsset1, this.mockAsset2 ],
|
||||
start: 0,
|
||||
end: 1,
|
||||
page: 0,
|
||||
pageSize: 5,
|
||||
totalCount: 2
|
||||
}
|
||||
};
|
||||
|
||||
@injector.require ["js/models/asset", "js/collections/asset", "js/views/assets"],
|
||||
(AssetModel, AssetCollection, AssetsView) =>
|
||||
@AssetModel = AssetModel
|
||||
@collection = new AssetCollection();
|
||||
@collection.url = "assets-url"
|
||||
@createAssetsView = (test) =>
|
||||
requests = AjaxHelpers.requests(test)
|
||||
view = new AssetsView
|
||||
collection: @collection
|
||||
this.injector.require(["js/models/asset", "js/collections/asset", "js/views/assets"],
|
||||
(AssetModel, AssetCollection, AssetsView) => {
|
||||
this.AssetModel = AssetModel;
|
||||
this.collection = new AssetCollection();
|
||||
this.collection.url = "assets-url";
|
||||
this.createAssetsView = test => {
|
||||
const requests = AjaxHelpers.requests(test);
|
||||
const view = new AssetsView({
|
||||
collection: this.collection,
|
||||
el: $('#asset_table_body')
|
||||
view.render()
|
||||
return {view: view, requests: requests}
|
||||
done()
|
||||
});
|
||||
view.render();
|
||||
return {view, requests};
|
||||
};
|
||||
return done();
|
||||
});
|
||||
|
||||
$.ajax()
|
||||
return $.ajax();
|
||||
});
|
||||
|
||||
afterEach ->
|
||||
delete window.analytics
|
||||
delete window.course_location_analytics
|
||||
afterEach(function() {
|
||||
delete window.analytics;
|
||||
delete window.course_location_analytics;
|
||||
|
||||
@injector.clean()
|
||||
@injector.remove()
|
||||
this.injector.clean();
|
||||
return this.injector.remove();
|
||||
});
|
||||
|
||||
addMockAsset = (requests) ->
|
||||
model = new @AssetModel
|
||||
display_name: "new asset"
|
||||
url: 'new_actual_asset_url'
|
||||
portable_url: 'portable_url'
|
||||
date_added: 'date'
|
||||
thumbnail: null
|
||||
const addMockAsset = function(requests) {
|
||||
const model = new this.AssetModel({
|
||||
display_name: "new asset",
|
||||
url: 'new_actual_asset_url',
|
||||
portable_url: 'portable_url',
|
||||
date_added: 'date',
|
||||
thumbnail: null,
|
||||
id: 'idx'
|
||||
@view.addAsset(model)
|
||||
AjaxHelpers.respondWithJson(requests,
|
||||
});
|
||||
this.view.addAsset(model);
|
||||
return AjaxHelpers.respondWithJson(requests,
|
||||
{
|
||||
assets: [
|
||||
@mockAsset1, @mockAsset2,
|
||||
this.mockAsset1, this.mockAsset2,
|
||||
{
|
||||
display_name: "new asset"
|
||||
url: 'new_actual_asset_url'
|
||||
portable_url: 'portable_url'
|
||||
date_added: 'date'
|
||||
thumbnail: null
|
||||
display_name: "new asset",
|
||||
url: 'new_actual_asset_url',
|
||||
portable_url: 'portable_url',
|
||||
date_added: 'date',
|
||||
thumbnail: null,
|
||||
id: 'idx'
|
||||
}
|
||||
],
|
||||
@@ -224,146 +255,179 @@ define ["jquery", "edx-ui-toolkit/js/utils/spec-helpers/ajax-helpers", "squire"]
|
||||
page: 0,
|
||||
pageSize: 5,
|
||||
totalCount: 3
|
||||
})
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
describe "Basic", ->
|
||||
# Separate setup method to work-around mis-parenting of beforeEach methods
|
||||
setup = (requests) ->
|
||||
@view.pagingView.setPage(1)
|
||||
AjaxHelpers.respondWithJson(requests, @mockAssetsResponse)
|
||||
describe("Basic", function() {
|
||||
// Separate setup method to work-around mis-parenting of beforeEach methods
|
||||
const setup = function(requests) {
|
||||
this.view.pagingView.setPage(1);
|
||||
return AjaxHelpers.respondWithJson(requests, this.mockAssetsResponse);
|
||||
};
|
||||
|
||||
$.fn.fileupload = ->
|
||||
return ''
|
||||
$.fn.fileupload = () => '';
|
||||
|
||||
clickEvent = (html_selector) ->
|
||||
$(html_selector).click()
|
||||
const clickEvent = html_selector => $(html_selector).click();
|
||||
|
||||
it "should show upload modal on clicking upload asset button", ->
|
||||
{view: @view, requests: requests} = @createAssetsView(this)
|
||||
spyOn(@view, "showUploadModal")
|
||||
setup.call(this, requests)
|
||||
expect(@view.showUploadModal).not.toHaveBeenCalled()
|
||||
@view.showUploadModal(clickEvent(".upload-button"))
|
||||
expect(@view.showUploadModal).toHaveBeenCalled()
|
||||
it("should show upload modal on clicking upload asset button", function() {
|
||||
let requests;
|
||||
({view: this.view, requests} = this.createAssetsView(this));
|
||||
spyOn(this.view, "showUploadModal");
|
||||
setup.call(this, requests);
|
||||
expect(this.view.showUploadModal).not.toHaveBeenCalled();
|
||||
this.view.showUploadModal(clickEvent(".upload-button"));
|
||||
return expect(this.view.showUploadModal).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it "should show file selection menu on choose file button", ->
|
||||
{view: @view, requests: requests} = @createAssetsView(this)
|
||||
spyOn(@view, "showFileSelectionMenu")
|
||||
setup.call(this, requests)
|
||||
expect(@view.showFileSelectionMenu).not.toHaveBeenCalled()
|
||||
@view.showFileSelectionMenu(clickEvent(".choose-file-button"))
|
||||
expect(@view.showFileSelectionMenu).toHaveBeenCalled()
|
||||
it("should show file selection menu on choose file button", function() {
|
||||
let requests;
|
||||
({view: this.view, requests} = this.createAssetsView(this));
|
||||
spyOn(this.view, "showFileSelectionMenu");
|
||||
setup.call(this, requests);
|
||||
expect(this.view.showFileSelectionMenu).not.toHaveBeenCalled();
|
||||
this.view.showFileSelectionMenu(clickEvent(".choose-file-button"));
|
||||
return expect(this.view.showFileSelectionMenu).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it "should hide upload modal on clicking close button", ->
|
||||
{view: @view, requests: requests} = @createAssetsView(this)
|
||||
spyOn(@view, "hideModal")
|
||||
setup.call(this, requests)
|
||||
expect(@view.hideModal).not.toHaveBeenCalled()
|
||||
@view.hideModal(clickEvent(".close-button"))
|
||||
expect(@view.hideModal).toHaveBeenCalled()
|
||||
it("should hide upload modal on clicking close button", function() {
|
||||
let requests;
|
||||
({view: this.view, requests} = this.createAssetsView(this));
|
||||
spyOn(this.view, "hideModal");
|
||||
setup.call(this, requests);
|
||||
expect(this.view.hideModal).not.toHaveBeenCalled();
|
||||
this.view.hideModal(clickEvent(".close-button"));
|
||||
return expect(this.view.hideModal).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it "should show a status indicator while loading", ->
|
||||
{view: @view, requests: requests} = @createAssetsView(this)
|
||||
appendSetFixtures('<div class="ui-loading"/>')
|
||||
expect($('.ui-loading').is(':visible')).toBe(true)
|
||||
setup.call(this, requests)
|
||||
expect($('.ui-loading').is(':visible')).toBe(false)
|
||||
it("should show a status indicator while loading", function() {
|
||||
let requests;
|
||||
({view: this.view, requests} = this.createAssetsView(this));
|
||||
appendSetFixtures('<div class="ui-loading"/>');
|
||||
expect($('.ui-loading').is(':visible')).toBe(true);
|
||||
setup.call(this, requests);
|
||||
return expect($('.ui-loading').is(':visible')).toBe(false);
|
||||
});
|
||||
|
||||
it "should hide the status indicator if an error occurs while loading", ->
|
||||
{view: @view, requests: requests} = @createAssetsView(this)
|
||||
appendSetFixtures('<div class="ui-loading"/>')
|
||||
expect($('.ui-loading').is(':visible')).toBe(true)
|
||||
@view.pagingView.setPage(1)
|
||||
AjaxHelpers.respondWithError(requests)
|
||||
expect($('.ui-loading').is(':visible')).toBe(false)
|
||||
it("should hide the status indicator if an error occurs while loading", function() {
|
||||
let requests;
|
||||
({view: this.view, requests} = this.createAssetsView(this));
|
||||
appendSetFixtures('<div class="ui-loading"/>');
|
||||
expect($('.ui-loading').is(':visible')).toBe(true);
|
||||
this.view.pagingView.setPage(1);
|
||||
AjaxHelpers.respondWithError(requests);
|
||||
return expect($('.ui-loading').is(':visible')).toBe(false);
|
||||
});
|
||||
|
||||
it "should render both assets", ->
|
||||
{view: @view, requests: requests} = @createAssetsView(this)
|
||||
setup.call(this, requests)
|
||||
expect(@view.$el).toContainText("test asset 1")
|
||||
expect(@view.$el).toContainText("test asset 2")
|
||||
it("should render both assets", function() {
|
||||
let requests;
|
||||
({view: this.view, requests} = this.createAssetsView(this));
|
||||
setup.call(this, requests);
|
||||
expect(this.view.$el).toContainText("test asset 1");
|
||||
return expect(this.view.$el).toContainText("test asset 2");
|
||||
});
|
||||
|
||||
it "should remove the deleted asset from the view", ->
|
||||
{view: @view, requests: requests} = @createAssetsView(this)
|
||||
AjaxHelpers.respondWithJson(requests, @mockAssetsResponse)
|
||||
setup.call(this, requests)
|
||||
# Delete the 2nd asset with success from server.
|
||||
@view.$(".remove-asset-button")[1].click()
|
||||
@promptSpies.constructor.calls.mostRecent().args[0].actions.primary.click(@promptSpies)
|
||||
AjaxHelpers.respondWithNoContent(requests)
|
||||
expect(@view.$el).toContainText("test asset 1")
|
||||
expect(@view.$el).not.toContainText("test asset 2")
|
||||
it("should remove the deleted asset from the view", function() {
|
||||
let requests;
|
||||
({view: this.view, requests} = this.createAssetsView(this));
|
||||
AjaxHelpers.respondWithJson(requests, this.mockAssetsResponse);
|
||||
setup.call(this, requests);
|
||||
// Delete the 2nd asset with success from server.
|
||||
this.view.$(".remove-asset-button")[1].click();
|
||||
this.promptSpies.constructor.calls.mostRecent().args[0].actions.primary.click(this.promptSpies);
|
||||
AjaxHelpers.respondWithNoContent(requests);
|
||||
expect(this.view.$el).toContainText("test asset 1");
|
||||
return expect(this.view.$el).not.toContainText("test asset 2");
|
||||
});
|
||||
|
||||
it "does not remove asset if deletion failed", ->
|
||||
{view: @view, requests: requests} = @createAssetsView(this)
|
||||
setup.call(this, requests)
|
||||
# Delete the 2nd asset, but mimic a failure from the server.
|
||||
@view.$(".remove-asset-button")[1].click()
|
||||
@promptSpies.constructor.calls.mostRecent().args[0].actions.primary.click(@promptSpies)
|
||||
AjaxHelpers.respondWithError(requests)
|
||||
expect(@view.$el).toContainText("test asset 1")
|
||||
expect(@view.$el).toContainText("test asset 2")
|
||||
it("does not remove asset if deletion failed", function() {
|
||||
let requests;
|
||||
({view: this.view, requests} = this.createAssetsView(this));
|
||||
setup.call(this, requests);
|
||||
// Delete the 2nd asset, but mimic a failure from the server.
|
||||
this.view.$(".remove-asset-button")[1].click();
|
||||
this.promptSpies.constructor.calls.mostRecent().args[0].actions.primary.click(this.promptSpies);
|
||||
AjaxHelpers.respondWithError(requests);
|
||||
expect(this.view.$el).toContainText("test asset 1");
|
||||
return expect(this.view.$el).toContainText("test asset 2");
|
||||
});
|
||||
|
||||
it "adds an asset if asset does not already exist", ->
|
||||
{view: @view, requests: requests} = @createAssetsView(this)
|
||||
setup.call(this, requests)
|
||||
addMockAsset.call(this, requests)
|
||||
expect(@view.$el).toContainText("new asset")
|
||||
expect(@collection.models.length).toBe(3)
|
||||
it("adds an asset if asset does not already exist", function() {
|
||||
let requests;
|
||||
({view: this.view, requests} = this.createAssetsView(this));
|
||||
setup.call(this, requests);
|
||||
addMockAsset.call(this, requests);
|
||||
expect(this.view.$el).toContainText("new asset");
|
||||
return expect(this.collection.models.length).toBe(3);
|
||||
});
|
||||
|
||||
it "does not add an asset if asset already exists", ->
|
||||
{view: @view, requests: requests} = @createAssetsView(this)
|
||||
setup.call(this, requests)
|
||||
spyOn(@collection, "add").and.callThrough()
|
||||
model = @collection.models[1]
|
||||
@view.addAsset(model)
|
||||
expect(@collection.add).not.toHaveBeenCalled()
|
||||
return it("does not add an asset if asset already exists", function() {
|
||||
let requests;
|
||||
({view: this.view, requests} = this.createAssetsView(this));
|
||||
setup.call(this, requests);
|
||||
spyOn(this.collection, "add").and.callThrough();
|
||||
const model = this.collection.models[1];
|
||||
this.view.addAsset(model);
|
||||
return expect(this.collection.add).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe "Sorting", ->
|
||||
# Separate setup method to work-around mis-parenting of beforeEach methods
|
||||
setup = (requests) ->
|
||||
@view.pagingView.setPage(1)
|
||||
AjaxHelpers.respondWithJson(requests, @mockAssetsResponse)
|
||||
return describe("Sorting", function() {
|
||||
// Separate setup method to work-around mis-parenting of beforeEach methods
|
||||
const setup = function(requests) {
|
||||
this.view.pagingView.setPage(1);
|
||||
return AjaxHelpers.respondWithJson(requests, this.mockAssetsResponse);
|
||||
};
|
||||
|
||||
it "should have the correct default sort order", ->
|
||||
{view: @view, requests: requests} = @createAssetsView(this)
|
||||
setup.call(this, requests)
|
||||
expect(@view.pagingView.sortDisplayName()).toBe("Date Added")
|
||||
expect(@view.collection.sortDirection).toBe("desc")
|
||||
it("should have the correct default sort order", function() {
|
||||
let requests;
|
||||
({view: this.view, requests} = this.createAssetsView(this));
|
||||
setup.call(this, requests);
|
||||
expect(this.view.pagingView.sortDisplayName()).toBe("Date Added");
|
||||
return expect(this.view.collection.sortDirection).toBe("desc");
|
||||
});
|
||||
|
||||
it "should toggle the sort order when clicking on the currently sorted column", ->
|
||||
{view: @view, requests: requests} = @createAssetsView(this)
|
||||
setup.call(this, requests)
|
||||
expect(@view.pagingView.sortDisplayName()).toBe("Date Added")
|
||||
expect(@view.collection.sortDirection).toBe("desc")
|
||||
@view.$("#js-asset-date-col").click()
|
||||
AjaxHelpers.respondWithJson(requests, @mockAssetsResponse)
|
||||
expect(@view.pagingView.sortDisplayName()).toBe("Date Added")
|
||||
expect(@view.collection.sortDirection).toBe("asc")
|
||||
@view.$("#js-asset-date-col").click()
|
||||
AjaxHelpers.respondWithJson(requests, @mockAssetsResponse)
|
||||
expect(@view.pagingView.sortDisplayName()).toBe("Date Added")
|
||||
expect(@view.collection.sortDirection).toBe("desc")
|
||||
it("should toggle the sort order when clicking on the currently sorted column", function() {
|
||||
let requests;
|
||||
({view: this.view, requests} = this.createAssetsView(this));
|
||||
setup.call(this, requests);
|
||||
expect(this.view.pagingView.sortDisplayName()).toBe("Date Added");
|
||||
expect(this.view.collection.sortDirection).toBe("desc");
|
||||
this.view.$("#js-asset-date-col").click();
|
||||
AjaxHelpers.respondWithJson(requests, this.mockAssetsResponse);
|
||||
expect(this.view.pagingView.sortDisplayName()).toBe("Date Added");
|
||||
expect(this.view.collection.sortDirection).toBe("asc");
|
||||
this.view.$("#js-asset-date-col").click();
|
||||
AjaxHelpers.respondWithJson(requests, this.mockAssetsResponse);
|
||||
expect(this.view.pagingView.sortDisplayName()).toBe("Date Added");
|
||||
return expect(this.view.collection.sortDirection).toBe("desc");
|
||||
});
|
||||
|
||||
it "should switch the sort order when clicking on a different column", ->
|
||||
{view: @view, requests: requests} = @createAssetsView(this)
|
||||
setup.call(this, requests)
|
||||
@view.$("#js-asset-name-col").click()
|
||||
AjaxHelpers.respondWithJson(requests, @mockAssetsResponse)
|
||||
expect(@view.pagingView.sortDisplayName()).toBe("Name")
|
||||
expect(@view.collection.sortDirection).toBe("asc")
|
||||
@view.$("#js-asset-name-col").click()
|
||||
AjaxHelpers.respondWithJson(requests, @mockAssetsResponse)
|
||||
expect(@view.pagingView.sortDisplayName()).toBe("Name")
|
||||
expect(@view.collection.sortDirection).toBe("desc")
|
||||
it("should switch the sort order when clicking on a different column", function() {
|
||||
let requests;
|
||||
({view: this.view, requests} = this.createAssetsView(this));
|
||||
setup.call(this, requests);
|
||||
this.view.$("#js-asset-name-col").click();
|
||||
AjaxHelpers.respondWithJson(requests, this.mockAssetsResponse);
|
||||
expect(this.view.pagingView.sortDisplayName()).toBe("Name");
|
||||
expect(this.view.collection.sortDirection).toBe("asc");
|
||||
this.view.$("#js-asset-name-col").click();
|
||||
AjaxHelpers.respondWithJson(requests, this.mockAssetsResponse);
|
||||
expect(this.view.pagingView.sortDisplayName()).toBe("Name");
|
||||
return expect(this.view.collection.sortDirection).toBe("desc");
|
||||
});
|
||||
|
||||
it "should switch sort to most recent date added when a new asset is added", ->
|
||||
{view: @view, requests: requests} = @createAssetsView(this)
|
||||
setup.call(this, requests)
|
||||
@view.$("#js-asset-name-col").click()
|
||||
AjaxHelpers.respondWithJson(requests, @mockAssetsResponse)
|
||||
addMockAsset.call(this, requests)
|
||||
AjaxHelpers.respondWithJson(requests, @mockAssetsResponse)
|
||||
expect(@view.pagingView.sortDisplayName()).toBe("Date Added")
|
||||
expect(@view.collection.sortDirection).toBe("desc")
|
||||
return it("should switch sort to most recent date added when a new asset is added", function() {
|
||||
let requests;
|
||||
({view: this.view, requests} = this.createAssetsView(this));
|
||||
setup.call(this, requests);
|
||||
this.view.$("#js-asset-name-col").click();
|
||||
AjaxHelpers.respondWithJson(requests, this.mockAssetsResponse);
|
||||
addMockAsset.call(this, requests);
|
||||
AjaxHelpers.respondWithJson(requests, this.mockAssetsResponse);
|
||||
expect(this.view.pagingView.sortDisplayName()).toBe("Date Added");
|
||||
return expect(this.view.collection.sortDirection).toBe("desc");
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,298 +1,339 @@
|
||||
define ["js/views/course_info_handout", "js/views/course_info_update", "js/models/module_info",
|
||||
/*
|
||||
* decaffeinate suggestions:
|
||||
* DS102: Remove unnecessary code created because of implicit returns
|
||||
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
|
||||
*/
|
||||
define(["js/views/course_info_handout", "js/views/course_info_update", "js/models/module_info",
|
||||
"js/collections/course_update", "edx-ui-toolkit/js/utils/spec-helpers/ajax-helpers"],
|
||||
(CourseInfoHandoutsView, CourseInfoUpdateView, ModuleInfo, CourseUpdateCollection, AjaxHelpers) ->
|
||||
(CourseInfoHandoutsView, CourseInfoUpdateView, ModuleInfo, CourseUpdateCollection, AjaxHelpers) =>
|
||||
|
||||
describe "Course Updates and Handouts", ->
|
||||
courseInfoPage = """
|
||||
<div class="course-info-wrapper">
|
||||
<div class="main-column window">
|
||||
<article class="course-updates" id="course-update-view">
|
||||
<ol class="update-list" id="course-update-list"></ol>
|
||||
</article>
|
||||
</div>
|
||||
<div class="sidebar window course-handouts" id="course-handouts-view"></div>
|
||||
</div>
|
||||
<div class="modal-cover"></div>
|
||||
"""
|
||||
describe("Course Updates and Handouts", function() {
|
||||
const courseInfoPage = `\
|
||||
<div class="course-info-wrapper">
|
||||
<div class="main-column window">
|
||||
<article class="course-updates" id="course-update-view">
|
||||
<ol class="update-list" id="course-update-list"></ol>
|
||||
</article>
|
||||
</div>
|
||||
<div class="sidebar window course-handouts" id="course-handouts-view"></div>
|
||||
</div>
|
||||
<div class="modal-cover"></div>\
|
||||
`;
|
||||
|
||||
beforeEach ->
|
||||
window.analytics = jasmine.createSpyObj('analytics', ['track'])
|
||||
window.course_location_analytics = jasmine.createSpy()
|
||||
beforeEach(function() {
|
||||
window.analytics = jasmine.createSpyObj('analytics', ['track']);
|
||||
return window.course_location_analytics = jasmine.createSpy();
|
||||
});
|
||||
|
||||
afterEach ->
|
||||
delete window.analytics
|
||||
delete window.course_location_analytics
|
||||
afterEach(function() {
|
||||
delete window.analytics;
|
||||
return delete window.course_location_analytics;
|
||||
});
|
||||
|
||||
describe "Course Updates without Push notification", ->
|
||||
courseInfoTemplate = readFixtures('course_info_update.underscore')
|
||||
describe("Course Updates without Push notification", function() {
|
||||
const courseInfoTemplate = readFixtures('course_info_update.underscore');
|
||||
|
||||
beforeEach ->
|
||||
setFixtures($("<script>", {id: "course_info_update-tpl", type: "text/template"}).text(courseInfoTemplate))
|
||||
appendSetFixtures courseInfoPage
|
||||
beforeEach(function() {
|
||||
let cancelEditingUpdate;
|
||||
setFixtures($("<script>", {id: "course_info_update-tpl", type: "text/template"}).text(courseInfoTemplate));
|
||||
appendSetFixtures(courseInfoPage);
|
||||
|
||||
@collection = new CourseUpdateCollection()
|
||||
@collection.url = 'course_info_update/'
|
||||
@courseInfoEdit = new CourseInfoUpdateView({
|
||||
this.collection = new CourseUpdateCollection();
|
||||
this.collection.url = 'course_info_update/';
|
||||
this.courseInfoEdit = new CourseInfoUpdateView({
|
||||
el: $('.course-updates'),
|
||||
collection: @collection,
|
||||
collection: this.collection,
|
||||
base_asset_url : 'base-asset-url/'
|
||||
})
|
||||
});
|
||||
|
||||
@courseInfoEdit.render()
|
||||
this.courseInfoEdit.render();
|
||||
|
||||
@event = {
|
||||
preventDefault : () -> 'no op'
|
||||
}
|
||||
this.event = {
|
||||
preventDefault() { return 'no op'; }
|
||||
};
|
||||
|
||||
@createNewUpdate = (text) ->
|
||||
# Edit button is not in the template under test (it is in parent HTML).
|
||||
# Therefore call onNew directly.
|
||||
@courseInfoEdit.onNew(@event)
|
||||
spyOn(@courseInfoEdit.$codeMirror, 'getValue').and.returnValue(text)
|
||||
@courseInfoEdit.$el.find('.save-button').click()
|
||||
this.createNewUpdate = function(text) {
|
||||
// Edit button is not in the template under test (it is in parent HTML).
|
||||
// Therefore call onNew directly.
|
||||
this.courseInfoEdit.onNew(this.event);
|
||||
spyOn(this.courseInfoEdit.$codeMirror, 'getValue').and.returnValue(text);
|
||||
return this.courseInfoEdit.$el.find('.save-button').click();
|
||||
};
|
||||
|
||||
@cancelNewCourseInfo = (useCancelButton) ->
|
||||
@courseInfoEdit.onNew(@event)
|
||||
spyOn(@courseInfoEdit.$modalCover, 'hide').and.callThrough()
|
||||
this.cancelNewCourseInfo = function(useCancelButton) {
|
||||
this.courseInfoEdit.onNew(this.event);
|
||||
spyOn(this.courseInfoEdit.$modalCover, 'hide').and.callThrough();
|
||||
|
||||
spyOn(@courseInfoEdit.$codeMirror, 'getValue').and.returnValue('unsaved changes')
|
||||
model = @collection.at(0)
|
||||
spyOn(model, "save").and.callThrough()
|
||||
spyOn(this.courseInfoEdit.$codeMirror, 'getValue').and.returnValue('unsaved changes');
|
||||
const model = this.collection.at(0);
|
||||
spyOn(model, "save").and.callThrough();
|
||||
|
||||
cancelEditingUpdate(@courseInfoEdit, @courseInfoEdit.$modalCover, useCancelButton)
|
||||
cancelEditingUpdate(this.courseInfoEdit, this.courseInfoEdit.$modalCover, useCancelButton);
|
||||
|
||||
expect(@courseInfoEdit.$modalCover.hide).toHaveBeenCalled()
|
||||
expect(model.save).not.toHaveBeenCalled()
|
||||
previewContents = @courseInfoEdit.$el.find('.update-contents').html()
|
||||
expect(previewContents).not.toEqual('unsaved changes')
|
||||
expect(this.courseInfoEdit.$modalCover.hide).toHaveBeenCalled();
|
||||
expect(model.save).not.toHaveBeenCalled();
|
||||
const previewContents = this.courseInfoEdit.$el.find('.update-contents').html();
|
||||
return expect(previewContents).not.toEqual('unsaved changes');
|
||||
};
|
||||
|
||||
@doNotCloseNewCourseInfo = () ->
|
||||
@courseInfoEdit.onNew(@event)
|
||||
spyOn(@courseInfoEdit.$modalCover, 'hide').and.callThrough()
|
||||
this.doNotCloseNewCourseInfo = function() {
|
||||
this.courseInfoEdit.onNew(this.event);
|
||||
spyOn(this.courseInfoEdit.$modalCover, 'hide').and.callThrough();
|
||||
|
||||
spyOn(@courseInfoEdit.$codeMirror, 'getValue').and.returnValue('unsaved changes')
|
||||
model = @collection.at(0)
|
||||
spyOn(model, "save").and.callThrough()
|
||||
spyOn(this.courseInfoEdit.$codeMirror, 'getValue').and.returnValue('unsaved changes');
|
||||
const model = this.collection.at(0);
|
||||
spyOn(model, "save").and.callThrough();
|
||||
|
||||
cancelEditingUpdate(@courseInfoEdit, @courseInfoEdit.$modalCover, false)
|
||||
cancelEditingUpdate(this.courseInfoEdit, this.courseInfoEdit.$modalCover, false);
|
||||
|
||||
expect(model.save).not.toHaveBeenCalled()
|
||||
expect(@courseInfoEdit.$modalCover.hide).not.toHaveBeenCalled()
|
||||
expect(model.save).not.toHaveBeenCalled();
|
||||
return expect(this.courseInfoEdit.$modalCover.hide).not.toHaveBeenCalled();
|
||||
};
|
||||
|
||||
@cancelExistingCourseInfo = (useCancelButton) ->
|
||||
@createNewUpdate('existing update')
|
||||
@courseInfoEdit.$el.find('.edit-button').click()
|
||||
spyOn(@courseInfoEdit.$modalCover, 'hide').and.callThrough()
|
||||
this.cancelExistingCourseInfo = function(useCancelButton) {
|
||||
this.createNewUpdate('existing update');
|
||||
this.courseInfoEdit.$el.find('.edit-button').click();
|
||||
spyOn(this.courseInfoEdit.$modalCover, 'hide').and.callThrough();
|
||||
|
||||
spyOn(@courseInfoEdit.$codeMirror, 'getValue').and.returnValue('modification')
|
||||
model = @collection.at(0)
|
||||
spyOn(model, "save").and.callThrough()
|
||||
model.id = "saved_to_server"
|
||||
cancelEditingUpdate(@courseInfoEdit, @courseInfoEdit.$modalCover, useCancelButton)
|
||||
spyOn(this.courseInfoEdit.$codeMirror, 'getValue').and.returnValue('modification');
|
||||
const model = this.collection.at(0);
|
||||
spyOn(model, "save").and.callThrough();
|
||||
model.id = "saved_to_server";
|
||||
cancelEditingUpdate(this.courseInfoEdit, this.courseInfoEdit.$modalCover, useCancelButton);
|
||||
|
||||
expect(@courseInfoEdit.$modalCover.hide).toHaveBeenCalled()
|
||||
expect(model.save).not.toHaveBeenCalled()
|
||||
previewContents = @courseInfoEdit.$el.find('.update-contents').html()
|
||||
expect(previewContents).toEqual('existing update')
|
||||
expect(this.courseInfoEdit.$modalCover.hide).toHaveBeenCalled();
|
||||
expect(model.save).not.toHaveBeenCalled();
|
||||
const previewContents = this.courseInfoEdit.$el.find('.update-contents').html();
|
||||
return expect(previewContents).toEqual('existing update');
|
||||
};
|
||||
|
||||
@testInvalidDateValue = (value) ->
|
||||
@courseInfoEdit.onNew(@event)
|
||||
expect(@courseInfoEdit.$el.find('.save-button').hasClass("is-disabled")).toEqual(false)
|
||||
@courseInfoEdit.$el.find('input.date').val(value).trigger("change")
|
||||
expect(@courseInfoEdit.$el.find('.save-button').hasClass("is-disabled")).toEqual(true)
|
||||
@courseInfoEdit.$el.find('input.date').val("01/01/16").trigger("change")
|
||||
expect(@courseInfoEdit.$el.find('.save-button').hasClass("is-disabled")).toEqual(false)
|
||||
this.testInvalidDateValue = function(value) {
|
||||
this.courseInfoEdit.onNew(this.event);
|
||||
expect(this.courseInfoEdit.$el.find('.save-button').hasClass("is-disabled")).toEqual(false);
|
||||
this.courseInfoEdit.$el.find('input.date').val(value).trigger("change");
|
||||
expect(this.courseInfoEdit.$el.find('.save-button').hasClass("is-disabled")).toEqual(true);
|
||||
this.courseInfoEdit.$el.find('input.date').val("01/01/16").trigger("change");
|
||||
return expect(this.courseInfoEdit.$el.find('.save-button').hasClass("is-disabled")).toEqual(false);
|
||||
};
|
||||
|
||||
cancelEditingUpdate = (update, modalCover, useCancelButton) ->
|
||||
if useCancelButton
|
||||
update.$el.find('.cancel-button').click()
|
||||
else
|
||||
modalCover.click()
|
||||
return cancelEditingUpdate = function(update, modalCover, useCancelButton) {
|
||||
if (useCancelButton) {
|
||||
return update.$el.find('.cancel-button').click();
|
||||
} else {
|
||||
return modalCover.click();
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
it "does send expected data on save", ->
|
||||
requests = AjaxHelpers["requests"](this)
|
||||
it("does send expected data on save", function() {
|
||||
const requests = AjaxHelpers["requests"](this);
|
||||
|
||||
# Create a new update, verifying that the model is created
|
||||
# in the collection and save is called.
|
||||
expect(@collection.isEmpty()).toBeTruthy()
|
||||
@courseInfoEdit.onNew(@event)
|
||||
expect(@collection.length).toEqual(1)
|
||||
model = @collection.at(0)
|
||||
spyOn(model, "save").and.callThrough()
|
||||
spyOn(@courseInfoEdit.$codeMirror, 'getValue').and.returnValue('/static/image.jpg')
|
||||
// Create a new update, verifying that the model is created
|
||||
// in the collection and save is called.
|
||||
expect(this.collection.isEmpty()).toBeTruthy();
|
||||
this.courseInfoEdit.onNew(this.event);
|
||||
expect(this.collection.length).toEqual(1);
|
||||
const model = this.collection.at(0);
|
||||
spyOn(model, "save").and.callThrough();
|
||||
spyOn(this.courseInfoEdit.$codeMirror, 'getValue').and.returnValue('/static/image.jpg');
|
||||
|
||||
# Click the "Save button."
|
||||
@courseInfoEdit.$el.find('.save-button').click()
|
||||
expect(model.save).toHaveBeenCalled()
|
||||
// Click the "Save button."
|
||||
this.courseInfoEdit.$el.find('.save-button').click();
|
||||
expect(model.save).toHaveBeenCalled();
|
||||
|
||||
# Verify push_notification_selected is set to false.
|
||||
requestSent = JSON.parse(requests[requests.length - 1].requestBody)
|
||||
expect(requestSent.push_notification_selected).toEqual(false)
|
||||
// Verify push_notification_selected is set to false.
|
||||
const requestSent = JSON.parse(requests[requests.length - 1].requestBody);
|
||||
expect(requestSent.push_notification_selected).toEqual(false);
|
||||
|
||||
# Verify the link is not rewritten when saved.
|
||||
expect(requestSent.content).toEqual('/static/image.jpg')
|
||||
// Verify the link is not rewritten when saved.
|
||||
expect(requestSent.content).toEqual('/static/image.jpg');
|
||||
|
||||
# Verify that analytics are sent
|
||||
expect(window.analytics.track).toHaveBeenCalled()
|
||||
// Verify that analytics are sent
|
||||
return expect(window.analytics.track).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it "does rewrite links for preview", ->
|
||||
# Create a new update.
|
||||
@createNewUpdate('/static/image.jpg')
|
||||
it("does rewrite links for preview", function() {
|
||||
// Create a new update.
|
||||
this.createNewUpdate('/static/image.jpg');
|
||||
|
||||
# Verify the link is rewritten for preview purposes.
|
||||
previewContents = @courseInfoEdit.$el.find('.update-contents').html()
|
||||
expect(previewContents).toEqual('base-asset-url/image.jpg')
|
||||
// Verify the link is rewritten for preview purposes.
|
||||
const previewContents = this.courseInfoEdit.$el.find('.update-contents').html();
|
||||
return expect(previewContents).toEqual('base-asset-url/image.jpg');
|
||||
});
|
||||
|
||||
it "shows static links in edit mode", ->
|
||||
@createNewUpdate('/static/image.jpg')
|
||||
it("shows static links in edit mode", function() {
|
||||
this.createNewUpdate('/static/image.jpg');
|
||||
|
||||
# Click edit and verify CodeMirror contents.
|
||||
@courseInfoEdit.$el.find('.edit-button').click()
|
||||
expect(@courseInfoEdit.$codeMirror.getValue()).toEqual('/static/image.jpg')
|
||||
// Click edit and verify CodeMirror contents.
|
||||
this.courseInfoEdit.$el.find('.edit-button').click();
|
||||
return expect(this.courseInfoEdit.$codeMirror.getValue()).toEqual('/static/image.jpg');
|
||||
});
|
||||
|
||||
it "removes newly created course info on cancel", ->
|
||||
@cancelNewCourseInfo(true)
|
||||
it("removes newly created course info on cancel", function() {
|
||||
return this.cancelNewCourseInfo(true);
|
||||
});
|
||||
|
||||
it "do not close new course info on click outside modal", ->
|
||||
@doNotCloseNewCourseInfo()
|
||||
it("do not close new course info on click outside modal", function() {
|
||||
return this.doNotCloseNewCourseInfo();
|
||||
});
|
||||
|
||||
it "does not remove existing course info on cancel", ->
|
||||
@cancelExistingCourseInfo(true)
|
||||
it("does not remove existing course info on cancel", function() {
|
||||
return this.cancelExistingCourseInfo(true);
|
||||
});
|
||||
|
||||
it "does not remove existing course info on click outside modal", ->
|
||||
@cancelExistingCourseInfo(false)
|
||||
it("does not remove existing course info on click outside modal", function() {
|
||||
return this.cancelExistingCourseInfo(false);
|
||||
});
|
||||
|
||||
it "does not allow updates to be saved with an invalid date", ->
|
||||
@testInvalidDateValue("Marchtober 40, 2048")
|
||||
it("does not allow updates to be saved with an invalid date", function() {
|
||||
return this.testInvalidDateValue("Marchtober 40, 2048");
|
||||
});
|
||||
|
||||
it "does not allow updates to be saved with a blank date", ->
|
||||
@testInvalidDateValue("")
|
||||
return it("does not allow updates to be saved with a blank date", function() {
|
||||
return this.testInvalidDateValue("");
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
describe "Course Updates WITH Push notification", ->
|
||||
courseInfoTemplate = readFixtures('course_info_update.underscore')
|
||||
describe("Course Updates WITH Push notification", function() {
|
||||
const courseInfoTemplate = readFixtures('course_info_update.underscore');
|
||||
|
||||
beforeEach ->
|
||||
setFixtures($("<script>", {id: "course_info_update-tpl", type: "text/template"}).text(courseInfoTemplate))
|
||||
appendSetFixtures courseInfoPage
|
||||
@collection = new CourseUpdateCollection()
|
||||
@collection.url = 'course_info_update/'
|
||||
@courseInfoEdit = new CourseInfoUpdateView({
|
||||
beforeEach(function() {
|
||||
setFixtures($("<script>", {id: "course_info_update-tpl", type: "text/template"}).text(courseInfoTemplate));
|
||||
appendSetFixtures(courseInfoPage);
|
||||
this.collection = new CourseUpdateCollection();
|
||||
this.collection.url = 'course_info_update/';
|
||||
this.courseInfoEdit = new CourseInfoUpdateView({
|
||||
el: $('.course-updates'),
|
||||
collection: @collection,
|
||||
collection: this.collection,
|
||||
base_asset_url : 'base-asset-url/',
|
||||
push_notification_enabled : true
|
||||
})
|
||||
@courseInfoEdit.render()
|
||||
@event = {preventDefault : () -> 'no op'}
|
||||
@courseInfoEdit.onNew(@event)
|
||||
});
|
||||
this.courseInfoEdit.render();
|
||||
this.event = {preventDefault() { return 'no op'; }};
|
||||
return this.courseInfoEdit.onNew(this.event);
|
||||
});
|
||||
|
||||
it "shows push notification checkbox as selected by default", ->
|
||||
expect(@courseInfoEdit.$el.find('.toggle-checkbox')).toBeChecked()
|
||||
it("shows push notification checkbox as selected by default", function() {
|
||||
return expect(this.courseInfoEdit.$el.find('.toggle-checkbox')).toBeChecked();
|
||||
});
|
||||
|
||||
it "sends correct default value for push_notification_selected", ->
|
||||
requests = AjaxHelpers.requests(this);
|
||||
@courseInfoEdit.$el.find('.save-button').click()
|
||||
requestSent = JSON.parse(requests[requests.length - 1].requestBody)
|
||||
expect(requestSent.push_notification_selected).toEqual(true)
|
||||
it("sends correct default value for push_notification_selected", function() {
|
||||
const requests = AjaxHelpers.requests(this);
|
||||
this.courseInfoEdit.$el.find('.save-button').click();
|
||||
const requestSent = JSON.parse(requests[requests.length - 1].requestBody);
|
||||
expect(requestSent.push_notification_selected).toEqual(true);
|
||||
|
||||
# Check that analytics send push_notification info
|
||||
analytics_payload = window.analytics.track.calls.first().args[1]
|
||||
expect(analytics_payload).toEqual(jasmine.objectContaining({'push_notification_selected': true}))
|
||||
// Check that analytics send push_notification info
|
||||
const analytics_payload = window.analytics.track.calls.first().args[1];
|
||||
return expect(analytics_payload).toEqual(jasmine.objectContaining({'push_notification_selected': true}));
|
||||
});
|
||||
|
||||
it "sends correct value for push_notification_selected when it is unselected", ->
|
||||
requests = AjaxHelpers.requests(this);
|
||||
# unselect push notification
|
||||
@courseInfoEdit.$el.find('.toggle-checkbox').attr('checked', false);
|
||||
@courseInfoEdit.$el.find('.save-button').click()
|
||||
requestSent = JSON.parse(requests[requests.length - 1].requestBody)
|
||||
expect(requestSent.push_notification_selected).toEqual(false)
|
||||
return it("sends correct value for push_notification_selected when it is unselected", function() {
|
||||
const requests = AjaxHelpers.requests(this);
|
||||
// unselect push notification
|
||||
this.courseInfoEdit.$el.find('.toggle-checkbox').attr('checked', false);
|
||||
this.courseInfoEdit.$el.find('.save-button').click();
|
||||
const requestSent = JSON.parse(requests[requests.length - 1].requestBody);
|
||||
expect(requestSent.push_notification_selected).toEqual(false);
|
||||
|
||||
# Check that analytics send push_notification info
|
||||
analytics_payload = window.analytics.track.calls.first().args[1]
|
||||
expect(analytics_payload).toEqual(jasmine.objectContaining({'push_notification_selected': false}))
|
||||
// Check that analytics send push_notification info
|
||||
const analytics_payload = window.analytics.track.calls.first().args[1];
|
||||
return expect(analytics_payload).toEqual(jasmine.objectContaining({'push_notification_selected': false}));
|
||||
});
|
||||
});
|
||||
|
||||
describe "Course Handouts", ->
|
||||
handoutsTemplate = readFixtures('course_info_handouts.underscore')
|
||||
return describe("Course Handouts", function() {
|
||||
const handoutsTemplate = readFixtures('course_info_handouts.underscore');
|
||||
|
||||
beforeEach ->
|
||||
setFixtures($("<script>", {id: "course_info_handouts-tpl", type: "text/template"}).text(handoutsTemplate))
|
||||
appendSetFixtures courseInfoPage
|
||||
beforeEach(function() {
|
||||
setFixtures($("<script>", {id: "course_info_handouts-tpl", type: "text/template"}).text(handoutsTemplate));
|
||||
appendSetFixtures(courseInfoPage);
|
||||
|
||||
@model = new ModuleInfo({
|
||||
this.model = new ModuleInfo({
|
||||
id: 'handouts-id',
|
||||
data: '/static/fromServer.jpg'
|
||||
})
|
||||
});
|
||||
|
||||
@handoutsEdit = new CourseInfoHandoutsView({
|
||||
this.handoutsEdit = new CourseInfoHandoutsView({
|
||||
el: $('#course-handouts-view'),
|
||||
model: @model,
|
||||
model: this.model,
|
||||
base_asset_url: 'base-asset-url/'
|
||||
});
|
||||
|
||||
@handoutsEdit.render()
|
||||
return this.handoutsEdit.render();
|
||||
});
|
||||
|
||||
it "saves <ol></ol> when content left empty", ->
|
||||
requests = AjaxHelpers["requests"](this)
|
||||
it("saves <ol></ol> when content left empty", function() {
|
||||
const requests = AjaxHelpers["requests"](this);
|
||||
|
||||
# Enter empty string in the handouts section, verifying that the model
|
||||
# is saved with '<ol></ol>' instead of the empty string
|
||||
@handoutsEdit.$el.find('.edit-button').click()
|
||||
spyOn(@handoutsEdit.$codeMirror, 'getValue').and.returnValue('')
|
||||
spyOn(@model, "save").and.callThrough()
|
||||
@handoutsEdit.$el.find('.save-button').click()
|
||||
expect(@model.save).toHaveBeenCalled()
|
||||
// Enter empty string in the handouts section, verifying that the model
|
||||
// is saved with '<ol></ol>' instead of the empty string
|
||||
this.handoutsEdit.$el.find('.edit-button').click();
|
||||
spyOn(this.handoutsEdit.$codeMirror, 'getValue').and.returnValue('');
|
||||
spyOn(this.model, "save").and.callThrough();
|
||||
this.handoutsEdit.$el.find('.save-button').click();
|
||||
expect(this.model.save).toHaveBeenCalled();
|
||||
|
||||
contentSaved = JSON.parse(requests[requests.length - 1].requestBody).data
|
||||
expect(contentSaved).toEqual('<ol></ol>')
|
||||
const contentSaved = JSON.parse(requests[requests.length - 1].requestBody).data;
|
||||
return expect(contentSaved).toEqual('<ol></ol>');
|
||||
});
|
||||
|
||||
it "does not rewrite links on save", ->
|
||||
requests = AjaxHelpers["requests"](this)
|
||||
it("does not rewrite links on save", function() {
|
||||
const requests = AjaxHelpers["requests"](this);
|
||||
|
||||
# Enter something in the handouts section, verifying that the model is saved
|
||||
# when "Save" is clicked.
|
||||
@handoutsEdit.$el.find('.edit-button').click()
|
||||
spyOn(@handoutsEdit.$codeMirror, 'getValue').and.returnValue('/static/image.jpg')
|
||||
spyOn(@model, "save").and.callThrough()
|
||||
@handoutsEdit.$el.find('.save-button').click()
|
||||
expect(@model.save).toHaveBeenCalled()
|
||||
// Enter something in the handouts section, verifying that the model is saved
|
||||
// when "Save" is clicked.
|
||||
this.handoutsEdit.$el.find('.edit-button').click();
|
||||
spyOn(this.handoutsEdit.$codeMirror, 'getValue').and.returnValue('/static/image.jpg');
|
||||
spyOn(this.model, "save").and.callThrough();
|
||||
this.handoutsEdit.$el.find('.save-button').click();
|
||||
expect(this.model.save).toHaveBeenCalled();
|
||||
|
||||
contentSaved = JSON.parse(requests[requests.length - 1].requestBody).data
|
||||
expect(contentSaved).toEqual('/static/image.jpg')
|
||||
const contentSaved = JSON.parse(requests[requests.length - 1].requestBody).data;
|
||||
return expect(contentSaved).toEqual('/static/image.jpg');
|
||||
});
|
||||
|
||||
it "does rewrite links in initial content", ->
|
||||
expect(@handoutsEdit.$preview.html().trim()).toBe('base-asset-url/fromServer.jpg')
|
||||
it("does rewrite links in initial content", function() {
|
||||
return expect(this.handoutsEdit.$preview.html().trim()).toBe('base-asset-url/fromServer.jpg');
|
||||
});
|
||||
|
||||
it "does rewrite links after edit", ->
|
||||
# Edit handouts and save.
|
||||
@handoutsEdit.$el.find('.edit-button').click()
|
||||
spyOn(@handoutsEdit.$codeMirror, 'getValue').and.returnValue('/static/image.jpg')
|
||||
@handoutsEdit.$el.find('.save-button').click()
|
||||
it("does rewrite links after edit", function() {
|
||||
// Edit handouts and save.
|
||||
this.handoutsEdit.$el.find('.edit-button').click();
|
||||
spyOn(this.handoutsEdit.$codeMirror, 'getValue').and.returnValue('/static/image.jpg');
|
||||
this.handoutsEdit.$el.find('.save-button').click();
|
||||
|
||||
# Verify preview text.
|
||||
expect(@handoutsEdit.$preview.html().trim()).toBe('base-asset-url/image.jpg')
|
||||
// Verify preview text.
|
||||
return expect(this.handoutsEdit.$preview.html().trim()).toBe('base-asset-url/image.jpg');
|
||||
});
|
||||
|
||||
it "shows static links in edit mode", ->
|
||||
# Click edit and verify CodeMirror contents.
|
||||
@handoutsEdit.$el.find('.edit-button').click()
|
||||
expect(@handoutsEdit.$codeMirror.getValue().trim()).toEqual('/static/fromServer.jpg')
|
||||
it("shows static links in edit mode", function() {
|
||||
// Click edit and verify CodeMirror contents.
|
||||
this.handoutsEdit.$el.find('.edit-button').click();
|
||||
return expect(this.handoutsEdit.$codeMirror.getValue().trim()).toEqual('/static/fromServer.jpg');
|
||||
});
|
||||
|
||||
it "can open course handouts with bad html on edit", ->
|
||||
# Enter some bad html in handouts section, verifying that the
|
||||
# model/handoutform opens when "Edit" is clicked
|
||||
return it("can open course handouts with bad html on edit", function() {
|
||||
// Enter some bad html in handouts section, verifying that the
|
||||
// model/handoutform opens when "Edit" is clicked
|
||||
|
||||
@model = new ModuleInfo({
|
||||
this.model = new ModuleInfo({
|
||||
id: 'handouts-id',
|
||||
data: '<p><a href="[URL OF FILE]>[LINK TEXT]</a></p>'
|
||||
})
|
||||
@handoutsEdit = new CourseInfoHandoutsView({
|
||||
});
|
||||
this.handoutsEdit = new CourseInfoHandoutsView({
|
||||
el: $('#course-handouts-view'),
|
||||
model: @model,
|
||||
model: this.model,
|
||||
base_asset_url: 'base-asset-url/'
|
||||
});
|
||||
@handoutsEdit.render()
|
||||
this.handoutsEdit.render();
|
||||
|
||||
expect($('.edit-handouts-form').is(':hidden')).toEqual(true)
|
||||
@handoutsEdit.$el.find('.edit-button').click()
|
||||
expect(@handoutsEdit.$codeMirror.getValue()).toEqual('<p><a href="[URL OF FILE]>[LINK TEXT]</a></p>')
|
||||
expect($('.edit-handouts-form').is(':hidden')).toEqual(false)
|
||||
expect($('.edit-handouts-form').is(':hidden')).toEqual(true);
|
||||
this.handoutsEdit.$el.find('.edit-button').click();
|
||||
expect(this.handoutsEdit.$codeMirror.getValue()).toEqual('<p><a href="[URL OF FILE]>[LINK TEXT]</a></p>');
|
||||
return expect($('.edit-handouts-form').is(':hidden')).toEqual(false);
|
||||
});
|
||||
});
|
||||
})
|
||||
);
|
||||
|
||||
@@ -1,30 +1,39 @@
|
||||
define ["js/models/metadata", "js/collections/metadata", "js/views/metadata", "cms/js/main"],
|
||||
(MetadataModel, MetadataCollection, MetadataView, main) ->
|
||||
verifyInputType = (input, expectedType) ->
|
||||
# Some browsers (e.g. FireFox) do not support the "number"
|
||||
# input type. We can accept a "text" input instead
|
||||
# and still get acceptable behavior in the UI.
|
||||
if expectedType == 'number' and input.type != 'number'
|
||||
expectedType = 'text'
|
||||
expect(input.type).toBe(expectedType)
|
||||
/*
|
||||
* decaffeinate suggestions:
|
||||
* DS102: Remove unnecessary code created because of implicit returns
|
||||
* DS207: Consider shorter variations of null checks
|
||||
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
|
||||
*/
|
||||
define(["js/models/metadata", "js/collections/metadata", "js/views/metadata", "cms/js/main"],
|
||||
function(MetadataModel, MetadataCollection, MetadataView, main) {
|
||||
const verifyInputType = function(input, expectedType) {
|
||||
// Some browsers (e.g. FireFox) do not support the "number"
|
||||
// input type. We can accept a "text" input instead
|
||||
// and still get acceptable behavior in the UI.
|
||||
if ((expectedType === 'number') && (input.type !== 'number')) {
|
||||
expectedType = 'text';
|
||||
}
|
||||
return expect(input.type).toBe(expectedType);
|
||||
};
|
||||
|
||||
describe "Test Metadata Editor", ->
|
||||
editorTemplate = readFixtures('metadata-editor.underscore')
|
||||
numberEntryTemplate = readFixtures('metadata-number-entry.underscore')
|
||||
stringEntryTemplate = readFixtures('metadata-string-entry.underscore')
|
||||
optionEntryTemplate = readFixtures('metadata-option-entry.underscore')
|
||||
listEntryTemplate = readFixtures('metadata-list-entry.underscore')
|
||||
dictEntryTemplate = readFixtures('metadata-dict-entry.underscore')
|
||||
return describe("Test Metadata Editor", function() {
|
||||
const editorTemplate = readFixtures('metadata-editor.underscore');
|
||||
const numberEntryTemplate = readFixtures('metadata-number-entry.underscore');
|
||||
const stringEntryTemplate = readFixtures('metadata-string-entry.underscore');
|
||||
const optionEntryTemplate = readFixtures('metadata-option-entry.underscore');
|
||||
const listEntryTemplate = readFixtures('metadata-list-entry.underscore');
|
||||
const dictEntryTemplate = readFixtures('metadata-dict-entry.underscore');
|
||||
|
||||
beforeEach ->
|
||||
setFixtures($("<script>", {id: "metadata-editor-tpl", type: "text/template"}).text(editorTemplate))
|
||||
appendSetFixtures($("<script>", {id: "metadata-number-entry", type: "text/template"}).text(numberEntryTemplate))
|
||||
appendSetFixtures($("<script>", {id: "metadata-string-entry", type: "text/template"}).text(stringEntryTemplate))
|
||||
appendSetFixtures($("<script>", {id: "metadata-option-entry", type: "text/template"}).text(optionEntryTemplate))
|
||||
appendSetFixtures($("<script>", {id: "metadata-list-entry", type: "text/template"}).text(listEntryTemplate))
|
||||
appendSetFixtures($("<script>", {id: "metadata-dict-entry", type: "text/template"}).text(dictEntryTemplate))
|
||||
beforeEach(function() {
|
||||
setFixtures($("<script>", {id: "metadata-editor-tpl", type: "text/template"}).text(editorTemplate));
|
||||
appendSetFixtures($("<script>", {id: "metadata-number-entry", type: "text/template"}).text(numberEntryTemplate));
|
||||
appendSetFixtures($("<script>", {id: "metadata-string-entry", type: "text/template"}).text(stringEntryTemplate));
|
||||
appendSetFixtures($("<script>", {id: "metadata-option-entry", type: "text/template"}).text(optionEntryTemplate));
|
||||
appendSetFixtures($("<script>", {id: "metadata-list-entry", type: "text/template"}).text(listEntryTemplate));
|
||||
return appendSetFixtures($("<script>", {id: "metadata-dict-entry", type: "text/template"}).text(dictEntryTemplate));
|
||||
});
|
||||
|
||||
genericEntry = {
|
||||
const genericEntry = {
|
||||
default_value: 'default value',
|
||||
display_name: "Display Name",
|
||||
explicitly_set: true,
|
||||
@@ -33,9 +42,9 @@ define ["js/models/metadata", "js/collections/metadata", "js/views/metadata", "c
|
||||
options: [],
|
||||
type: MetadataModel.GENERIC_TYPE,
|
||||
value: "Word cloud"
|
||||
}
|
||||
};
|
||||
|
||||
selectEntry = {
|
||||
const selectEntry = {
|
||||
default_value: "answered",
|
||||
display_name: "Show Answer",
|
||||
explicitly_set: false,
|
||||
@@ -48,9 +57,9 @@ define ["js/models/metadata", "js/collections/metadata", "js/views/metadata", "c
|
||||
],
|
||||
type: MetadataModel.SELECT_TYPE,
|
||||
value: "always"
|
||||
}
|
||||
};
|
||||
|
||||
integerEntry = {
|
||||
const integerEntry = {
|
||||
default_value: 6,
|
||||
display_name: "Inputs",
|
||||
explicitly_set: false,
|
||||
@@ -59,9 +68,9 @@ define ["js/models/metadata", "js/collections/metadata", "js/views/metadata", "c
|
||||
options: {min: 1},
|
||||
type: MetadataModel.INTEGER_TYPE,
|
||||
value: 5
|
||||
}
|
||||
};
|
||||
|
||||
floatEntry = {
|
||||
const floatEntry = {
|
||||
default_value: 2.7,
|
||||
display_name: "Weight",
|
||||
explicitly_set: true,
|
||||
@@ -70,9 +79,9 @@ define ["js/models/metadata", "js/collections/metadata", "js/views/metadata", "c
|
||||
options: {min: 1.3, max:100.2, step:0.1},
|
||||
type: MetadataModel.FLOAT_TYPE,
|
||||
value: 10.2
|
||||
}
|
||||
};
|
||||
|
||||
listEntry = {
|
||||
const listEntry = {
|
||||
default_value: ["a thing", "another thing"],
|
||||
display_name: "List",
|
||||
explicitly_set: false,
|
||||
@@ -81,9 +90,9 @@ define ["js/models/metadata", "js/collections/metadata", "js/views/metadata", "c
|
||||
options: [],
|
||||
type: MetadataModel.LIST_TYPE,
|
||||
value: ["the first display value", "the second"]
|
||||
}
|
||||
};
|
||||
|
||||
timeEntry = {
|
||||
const timeEntry = {
|
||||
default_value: "00:00:00",
|
||||
display_name: "Time",
|
||||
explicitly_set: true,
|
||||
@@ -92,9 +101,9 @@ define ["js/models/metadata", "js/collections/metadata", "js/views/metadata", "c
|
||||
options: [],
|
||||
type: MetadataModel.RELATIVE_TIME_TYPE,
|
||||
value: "12:12:12"
|
||||
}
|
||||
};
|
||||
|
||||
dictEntry = {
|
||||
const dictEntry = {
|
||||
default_value: {
|
||||
'en': 'English',
|
||||
'ru': 'Русский'
|
||||
@@ -110,13 +119,13 @@ define ["js/models/metadata", "js/collections/metadata", "js/views/metadata", "c
|
||||
'ua': 'Українська',
|
||||
'fr': 'Français'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
# Test for the editor that creates the individual views.
|
||||
describe "MetadataView.Editor creates editors for each field", ->
|
||||
beforeEach ->
|
||||
@model = new MetadataCollection(
|
||||
// Test for the editor that creates the individual views.
|
||||
describe("MetadataView.Editor creates editors for each field", function() {
|
||||
beforeEach(function() {
|
||||
return this.model = new MetadataCollection(
|
||||
[
|
||||
integerEntry,
|
||||
floatEntry,
|
||||
@@ -139,36 +148,40 @@ define ["js/models/metadata", "js/collections/metadata", "js/views/metadata", "c
|
||||
timeEntry,
|
||||
dictEntry
|
||||
]
|
||||
)
|
||||
);
|
||||
});
|
||||
|
||||
it "creates child views on initialize, and sorts them alphabetically", ->
|
||||
view = new MetadataView.Editor({collection: @model})
|
||||
childModels = view.collection.models
|
||||
expect(childModels.length).toBe(8)
|
||||
# Be sure to check list view as well as other input types
|
||||
childViews = view.$el.find('.setting-input, .list-settings')
|
||||
expect(childViews.length).toBe(8)
|
||||
it("creates child views on initialize, and sorts them alphabetically", function() {
|
||||
const view = new MetadataView.Editor({collection: this.model});
|
||||
const childModels = view.collection.models;
|
||||
expect(childModels.length).toBe(8);
|
||||
// Be sure to check list view as well as other input types
|
||||
const childViews = view.$el.find('.setting-input, .list-settings');
|
||||
expect(childViews.length).toBe(8);
|
||||
|
||||
verifyEntry = (index, display_name, type) ->
|
||||
expect(childModels[index].get('display_name')).toBe(display_name)
|
||||
verifyInputType(childViews[index], type)
|
||||
const verifyEntry = function(index, display_name, type) {
|
||||
expect(childModels[index].get('display_name')).toBe(display_name);
|
||||
return verifyInputType(childViews[index], type);
|
||||
};
|
||||
|
||||
verifyEntry(0, 'Display Name', 'text')
|
||||
verifyEntry(1, 'Inputs', 'number')
|
||||
verifyEntry(2, 'List', '')
|
||||
verifyEntry(3, 'New Dict', '')
|
||||
verifyEntry(4, 'Show Answer', 'select-one')
|
||||
verifyEntry(5, 'Time', 'text')
|
||||
verifyEntry(6, 'Unknown', 'text')
|
||||
verifyEntry(7, 'Weight', 'number')
|
||||
verifyEntry(0, 'Display Name', 'text');
|
||||
verifyEntry(1, 'Inputs', 'number');
|
||||
verifyEntry(2, 'List', '');
|
||||
verifyEntry(3, 'New Dict', '');
|
||||
verifyEntry(4, 'Show Answer', 'select-one');
|
||||
verifyEntry(5, 'Time', 'text');
|
||||
verifyEntry(6, 'Unknown', 'text');
|
||||
return verifyEntry(7, 'Weight', 'number');
|
||||
});
|
||||
|
||||
it "returns its display name", ->
|
||||
view = new MetadataView.Editor({collection: @model})
|
||||
expect(view.getDisplayName()).toBe("Word cloud")
|
||||
it("returns its display name", function() {
|
||||
const view = new MetadataView.Editor({collection: this.model});
|
||||
return expect(view.getDisplayName()).toBe("Word cloud");
|
||||
});
|
||||
|
||||
it "returns an empty string if there is no display name property with a valid value", ->
|
||||
view = new MetadataView.Editor({collection: new MetadataCollection()})
|
||||
expect(view.getDisplayName()).toBe("")
|
||||
it("returns an empty string if there is no display name property with a valid value", function() {
|
||||
let view = new MetadataView.Editor({collection: new MetadataCollection()});
|
||||
expect(view.getDisplayName()).toBe("");
|
||||
|
||||
view = new MetadataView.Editor({collection: new MetadataCollection([
|
||||
{
|
||||
@@ -182,391 +195,457 @@ define ["js/models/metadata", "js/collections/metadata", "js/views/metadata", "c
|
||||
value: null
|
||||
|
||||
}])
|
||||
})
|
||||
expect(view.getDisplayName()).toBe("")
|
||||
});
|
||||
return expect(view.getDisplayName()).toBe("");
|
||||
});
|
||||
|
||||
it "has no modified values by default", ->
|
||||
view = new MetadataView.Editor({collection: @model})
|
||||
expect(view.getModifiedMetadataValues()).toEqual({})
|
||||
it("has no modified values by default", function() {
|
||||
const view = new MetadataView.Editor({collection: this.model});
|
||||
return expect(view.getModifiedMetadataValues()).toEqual({});
|
||||
});
|
||||
|
||||
it "returns modified values only", ->
|
||||
view = new MetadataView.Editor({collection: @model})
|
||||
childModels = view.collection.models
|
||||
childModels[0].setValue('updated display name')
|
||||
childModels[1].setValue(20)
|
||||
expect(view.getModifiedMetadataValues()).toEqual({
|
||||
return it("returns modified values only", function() {
|
||||
const view = new MetadataView.Editor({collection: this.model});
|
||||
const childModels = view.collection.models;
|
||||
childModels[0].setValue('updated display name');
|
||||
childModels[1].setValue(20);
|
||||
return expect(view.getModifiedMetadataValues()).toEqual({
|
||||
display_name : 'updated display name',
|
||||
num_inputs: 20
|
||||
})
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
# Tests for individual views.
|
||||
assertInputType = (view, expectedType) ->
|
||||
input = view.$el.find('.setting-input')
|
||||
expect(input.length).toEqual(1)
|
||||
verifyInputType(input[0], expectedType)
|
||||
// Tests for individual views.
|
||||
const assertInputType = function(view, expectedType) {
|
||||
const input = view.$el.find('.setting-input');
|
||||
expect(input.length).toEqual(1);
|
||||
return verifyInputType(input[0], expectedType);
|
||||
};
|
||||
|
||||
assertValueInView = (view, expectedValue) ->
|
||||
expect(view.getValueFromEditor()).toEqual(expectedValue)
|
||||
const assertValueInView = (view, expectedValue) => expect(view.getValueFromEditor()).toEqual(expectedValue);
|
||||
|
||||
assertCanUpdateView = (view, newValue) ->
|
||||
view.setValueInEditor(newValue)
|
||||
expect(view.getValueFromEditor()).toEqual(newValue)
|
||||
const assertCanUpdateView = function(view, newValue) {
|
||||
view.setValueInEditor(newValue);
|
||||
return expect(view.getValueFromEditor()).toEqual(newValue);
|
||||
};
|
||||
|
||||
assertClear = (view, modelValue, editorValue=modelValue) ->
|
||||
view.clear()
|
||||
expect(view.model.getValue()).toBe(null)
|
||||
expect(view.model.getDisplayValue()).toEqual(modelValue)
|
||||
expect(view.getValueFromEditor()).toEqual(editorValue)
|
||||
const assertClear = function(view, modelValue, editorValue) {
|
||||
if (editorValue == null) { editorValue = modelValue; }
|
||||
view.clear();
|
||||
expect(view.model.getValue()).toBe(null);
|
||||
expect(view.model.getDisplayValue()).toEqual(modelValue);
|
||||
return expect(view.getValueFromEditor()).toEqual(editorValue);
|
||||
};
|
||||
|
||||
assertUpdateModel = (view, originalValue, newValue) ->
|
||||
view.setValueInEditor(newValue)
|
||||
expect(view.model.getValue()).toEqual(originalValue)
|
||||
view.updateModel()
|
||||
expect(view.model.getValue()).toEqual(newValue)
|
||||
const assertUpdateModel = function(view, originalValue, newValue) {
|
||||
view.setValueInEditor(newValue);
|
||||
expect(view.model.getValue()).toEqual(originalValue);
|
||||
view.updateModel();
|
||||
return expect(view.model.getValue()).toEqual(newValue);
|
||||
};
|
||||
|
||||
describe "MetadataView.String is a basic string input with clear functionality", ->
|
||||
beforeEach ->
|
||||
model = new MetadataModel(genericEntry)
|
||||
@view = new MetadataView.String({model: model})
|
||||
describe("MetadataView.String is a basic string input with clear functionality", function() {
|
||||
beforeEach(function() {
|
||||
const model = new MetadataModel(genericEntry);
|
||||
return this.view = new MetadataView.String({model});
|
||||
});
|
||||
|
||||
it "uses a text input type", ->
|
||||
assertInputType(@view, 'text')
|
||||
it("uses a text input type", function() {
|
||||
return assertInputType(this.view, 'text');
|
||||
});
|
||||
|
||||
it "returns the intial value upon initialization", ->
|
||||
assertValueInView(@view, 'Word cloud')
|
||||
it("returns the intial value upon initialization", function() {
|
||||
return assertValueInView(this.view, 'Word cloud');
|
||||
});
|
||||
|
||||
it "can update its value in the view", ->
|
||||
assertCanUpdateView(@view, "updated ' \" &")
|
||||
it("can update its value in the view", function() {
|
||||
return assertCanUpdateView(this.view, "updated ' \" &");
|
||||
});
|
||||
|
||||
it "has a clear method to revert to the model default", ->
|
||||
assertClear(@view, 'default value')
|
||||
it("has a clear method to revert to the model default", function() {
|
||||
return assertClear(this.view, 'default value');
|
||||
});
|
||||
|
||||
it "has an update model method", ->
|
||||
assertUpdateModel(@view, 'Word cloud', 'updated')
|
||||
return it("has an update model method", function() {
|
||||
return assertUpdateModel(this.view, 'Word cloud', 'updated');
|
||||
});
|
||||
});
|
||||
|
||||
describe "MetadataView.Option is an option input type with clear functionality", ->
|
||||
beforeEach ->
|
||||
model = new MetadataModel(selectEntry)
|
||||
@view = new MetadataView.Option({model: model})
|
||||
describe("MetadataView.Option is an option input type with clear functionality", function() {
|
||||
beforeEach(function() {
|
||||
const model = new MetadataModel(selectEntry);
|
||||
return this.view = new MetadataView.Option({model});
|
||||
});
|
||||
|
||||
it "uses a select input type", ->
|
||||
assertInputType(@view, 'select-one')
|
||||
it("uses a select input type", function() {
|
||||
return assertInputType(this.view, 'select-one');
|
||||
});
|
||||
|
||||
it "returns the intial value upon initialization", ->
|
||||
assertValueInView(@view, 'always')
|
||||
it("returns the intial value upon initialization", function() {
|
||||
return assertValueInView(this.view, 'always');
|
||||
});
|
||||
|
||||
it "can update its value in the view", ->
|
||||
assertCanUpdateView(@view, "never")
|
||||
it("can update its value in the view", function() {
|
||||
return assertCanUpdateView(this.view, "never");
|
||||
});
|
||||
|
||||
it "has a clear method to revert to the model default", ->
|
||||
assertClear(@view, 'answered')
|
||||
it("has a clear method to revert to the model default", function() {
|
||||
return assertClear(this.view, 'answered');
|
||||
});
|
||||
|
||||
it "has an update model method", ->
|
||||
assertUpdateModel(@view, null, 'never')
|
||||
it("has an update model method", function() {
|
||||
return assertUpdateModel(this.view, null, 'never');
|
||||
});
|
||||
|
||||
it "does not update to a value that is not an option", ->
|
||||
@view.setValueInEditor("not an option")
|
||||
expect(@view.getValueFromEditor()).toBe('always')
|
||||
return it("does not update to a value that is not an option", function() {
|
||||
this.view.setValueInEditor("not an option");
|
||||
return expect(this.view.getValueFromEditor()).toBe('always');
|
||||
});
|
||||
});
|
||||
|
||||
describe "MetadataView.Number supports integer or float type and has clear functionality", ->
|
||||
verifyValueAfterChanged = (view, value, expectedResult) ->
|
||||
view.setValueInEditor(value)
|
||||
view.changed()
|
||||
expect(view.getValueFromEditor()).toBe(expectedResult)
|
||||
describe("MetadataView.Number supports integer or float type and has clear functionality", function() {
|
||||
const verifyValueAfterChanged = function(view, value, expectedResult) {
|
||||
view.setValueInEditor(value);
|
||||
view.changed();
|
||||
return expect(view.getValueFromEditor()).toBe(expectedResult);
|
||||
};
|
||||
|
||||
beforeEach ->
|
||||
integerModel = new MetadataModel(integerEntry)
|
||||
@integerView = new MetadataView.Number({model: integerModel})
|
||||
beforeEach(function() {
|
||||
const integerModel = new MetadataModel(integerEntry);
|
||||
this.integerView = new MetadataView.Number({model: integerModel});
|
||||
|
||||
floatModel = new MetadataModel(floatEntry)
|
||||
@floatView = new MetadataView.Number({model: floatModel})
|
||||
const floatModel = new MetadataModel(floatEntry);
|
||||
return this.floatView = new MetadataView.Number({model: floatModel});
|
||||
});
|
||||
|
||||
it "uses a number input type", ->
|
||||
assertInputType(@integerView, 'number')
|
||||
assertInputType(@floatView, 'number')
|
||||
it("uses a number input type", function() {
|
||||
assertInputType(this.integerView, 'number');
|
||||
return assertInputType(this.floatView, 'number');
|
||||
});
|
||||
|
||||
it "returns the intial value upon initialization", ->
|
||||
assertValueInView(@integerView, '5')
|
||||
assertValueInView(@floatView, '10.2')
|
||||
it("returns the intial value upon initialization", function() {
|
||||
assertValueInView(this.integerView, '5');
|
||||
return assertValueInView(this.floatView, '10.2');
|
||||
});
|
||||
|
||||
it "can update its value in the view", ->
|
||||
assertCanUpdateView(@integerView, "12")
|
||||
assertCanUpdateView(@floatView, "-2.4")
|
||||
it("can update its value in the view", function() {
|
||||
assertCanUpdateView(this.integerView, "12");
|
||||
return assertCanUpdateView(this.floatView, "-2.4");
|
||||
});
|
||||
|
||||
it "has a clear method to revert to the model default", ->
|
||||
assertClear(@integerView, 6, '6')
|
||||
assertClear(@floatView, 2.7, '2.7')
|
||||
it("has a clear method to revert to the model default", function() {
|
||||
assertClear(this.integerView, 6, '6');
|
||||
return assertClear(this.floatView, 2.7, '2.7');
|
||||
});
|
||||
|
||||
it "has an update model method", ->
|
||||
assertUpdateModel(@integerView, null, '90')
|
||||
assertUpdateModel(@floatView, 10.2, '-9.5')
|
||||
it("has an update model method", function() {
|
||||
assertUpdateModel(this.integerView, null, '90');
|
||||
return assertUpdateModel(this.floatView, 10.2, '-9.5');
|
||||
});
|
||||
|
||||
it "knows the difference between integer and float", ->
|
||||
expect(@integerView.isIntegerField()).toBeTruthy()
|
||||
expect(@floatView.isIntegerField()).toBeFalsy()
|
||||
it("knows the difference between integer and float", function() {
|
||||
expect(this.integerView.isIntegerField()).toBeTruthy();
|
||||
return expect(this.floatView.isIntegerField()).toBeFalsy();
|
||||
});
|
||||
|
||||
it "sets attribtues related to min, max, and step", ->
|
||||
verifyAttributes = (view, min, step, max=null) ->
|
||||
inputEntry = view.$el.find('input')
|
||||
expect(Number(inputEntry.attr('min'))).toEqual(min)
|
||||
expect(Number(inputEntry.attr('step'))).toEqual(step)
|
||||
if max is not null
|
||||
expect(Number(inputEntry.attr('max'))).toEqual(max)
|
||||
it("sets attribtues related to min, max, and step", function() {
|
||||
const verifyAttributes = function(view, min, step, max=null) {
|
||||
const inputEntry = view.$el.find('input');
|
||||
expect(Number(inputEntry.attr('min'))).toEqual(min);
|
||||
expect(Number(inputEntry.attr('step'))).toEqual(step);
|
||||
if (max === !null) {
|
||||
return expect(Number(inputEntry.attr('max'))).toEqual(max);
|
||||
}
|
||||
};
|
||||
|
||||
verifyAttributes(@integerView, 1, 1)
|
||||
verifyAttributes(@floatView, 1.3, .1, 100.2)
|
||||
verifyAttributes(this.integerView, 1, 1);
|
||||
return verifyAttributes(this.floatView, 1.3, .1, 100.2);
|
||||
});
|
||||
|
||||
it "corrects values that are out of range", ->
|
||||
verifyValueAfterChanged(@integerView, '-4', '1')
|
||||
verifyValueAfterChanged(@integerView, '1', '1')
|
||||
verifyValueAfterChanged(@integerView, '0', '1')
|
||||
verifyValueAfterChanged(@integerView, '3001', '3001')
|
||||
it("corrects values that are out of range", function() {
|
||||
verifyValueAfterChanged(this.integerView, '-4', '1');
|
||||
verifyValueAfterChanged(this.integerView, '1', '1');
|
||||
verifyValueAfterChanged(this.integerView, '0', '1');
|
||||
verifyValueAfterChanged(this.integerView, '3001', '3001');
|
||||
|
||||
verifyValueAfterChanged(@floatView, '-4', '1.3')
|
||||
verifyValueAfterChanged(@floatView, '1.3', '1.3')
|
||||
verifyValueAfterChanged(@floatView, '1.2', '1.3')
|
||||
verifyValueAfterChanged(@floatView, '100.2', '100.2')
|
||||
verifyValueAfterChanged(@floatView, '100.3', '100.2')
|
||||
verifyValueAfterChanged(this.floatView, '-4', '1.3');
|
||||
verifyValueAfterChanged(this.floatView, '1.3', '1.3');
|
||||
verifyValueAfterChanged(this.floatView, '1.2', '1.3');
|
||||
verifyValueAfterChanged(this.floatView, '100.2', '100.2');
|
||||
return verifyValueAfterChanged(this.floatView, '100.3', '100.2');
|
||||
});
|
||||
|
||||
it "sets default values for integer and float fields that are empty", ->
|
||||
verifyValueAfterChanged(@integerView, '', '6')
|
||||
verifyValueAfterChanged(@floatView, '', '2.7')
|
||||
it("sets default values for integer and float fields that are empty", function() {
|
||||
verifyValueAfterChanged(this.integerView, '', '6');
|
||||
return verifyValueAfterChanged(this.floatView, '', '2.7');
|
||||
});
|
||||
|
||||
it "disallows invalid characters", ->
|
||||
verifyValueAfterKeyPressed = (view, character, reject) ->
|
||||
event = {
|
||||
return it("disallows invalid characters", function() {
|
||||
const verifyValueAfterKeyPressed = function(view, character, reject) {
|
||||
const event = {
|
||||
type : 'keypress',
|
||||
which : character.charCodeAt(0),
|
||||
keyCode: character.charCodeAt(0),
|
||||
preventDefault : () -> 'no op'
|
||||
preventDefault() { return 'no op'; }
|
||||
};
|
||||
spyOn(event, 'preventDefault');
|
||||
view.$el.find('input').trigger(event);
|
||||
if (reject) {
|
||||
return expect(event.preventDefault).toHaveBeenCalled();
|
||||
} else {
|
||||
return expect(event.preventDefault).not.toHaveBeenCalled();
|
||||
}
|
||||
spyOn(event, 'preventDefault')
|
||||
view.$el.find('input').trigger(event)
|
||||
if (reject)
|
||||
expect(event.preventDefault).toHaveBeenCalled()
|
||||
else
|
||||
expect(event.preventDefault).not.toHaveBeenCalled()
|
||||
};
|
||||
|
||||
verifyDisallowedChars = (view) ->
|
||||
verifyValueAfterKeyPressed(view, 'a', true)
|
||||
verifyValueAfterKeyPressed(view, '.', view.isIntegerField())
|
||||
verifyValueAfterKeyPressed(view, '[', true)
|
||||
verifyValueAfterKeyPressed(view, '@', true)
|
||||
const verifyDisallowedChars = function(view) {
|
||||
verifyValueAfterKeyPressed(view, 'a', true);
|
||||
verifyValueAfterKeyPressed(view, '.', view.isIntegerField());
|
||||
verifyValueAfterKeyPressed(view, '[', true);
|
||||
verifyValueAfterKeyPressed(view, '@', true);
|
||||
|
||||
for i in [0...9]
|
||||
verifyValueAfterKeyPressed(view, String(i), false)
|
||||
return [0, 1, 2, 3, 4, 5, 6, 7, 8].map((i) =>
|
||||
verifyValueAfterKeyPressed(view, String(i), false));
|
||||
};
|
||||
|
||||
verifyDisallowedChars(@integerView)
|
||||
verifyDisallowedChars(@floatView)
|
||||
verifyDisallowedChars(this.integerView);
|
||||
return verifyDisallowedChars(this.floatView);
|
||||
});
|
||||
});
|
||||
|
||||
describe "MetadataView.List allows the user to enter an ordered list of strings", ->
|
||||
beforeEach ->
|
||||
listModel = new MetadataModel(listEntry)
|
||||
@listView = new MetadataView.List({model: listModel})
|
||||
@el = @listView.$el
|
||||
main()
|
||||
describe("MetadataView.List allows the user to enter an ordered list of strings", function() {
|
||||
beforeEach(function() {
|
||||
const listModel = new MetadataModel(listEntry);
|
||||
this.listView = new MetadataView.List({model: listModel});
|
||||
this.el = this.listView.$el;
|
||||
return main();
|
||||
});
|
||||
|
||||
it "returns the initial value upon initialization", ->
|
||||
assertValueInView(@listView, ['the first display value', 'the second'])
|
||||
it("returns the initial value upon initialization", function() {
|
||||
return assertValueInView(this.listView, ['the first display value', 'the second']);
|
||||
});
|
||||
|
||||
it "updates its value correctly", ->
|
||||
assertCanUpdateView(@listView, ['a new item', 'another new item', 'a third'])
|
||||
it("updates its value correctly", function() {
|
||||
return assertCanUpdateView(this.listView, ['a new item', 'another new item', 'a third']);
|
||||
});
|
||||
|
||||
it "has a clear method to revert to the model default", ->
|
||||
@el.find('.create-setting').click()
|
||||
assertClear(@listView, ['a thing', 'another thing'])
|
||||
expect(@el.find('.create-setting')).not.toHaveClass('is-disabled')
|
||||
it("has a clear method to revert to the model default", function() {
|
||||
this.el.find('.create-setting').click();
|
||||
assertClear(this.listView, ['a thing', 'another thing']);
|
||||
return expect(this.el.find('.create-setting')).not.toHaveClass('is-disabled');
|
||||
});
|
||||
|
||||
it "has an update model method", ->
|
||||
assertUpdateModel(@listView, null, ['a new value'])
|
||||
it("has an update model method", function() {
|
||||
return assertUpdateModel(this.listView, null, ['a new value']);
|
||||
});
|
||||
|
||||
it "can add an entry", ->
|
||||
expect(@listView.model.get('value').length).toEqual(2)
|
||||
@el.find('.create-setting').click()
|
||||
expect(@el.find('input.input').length).toEqual(3)
|
||||
it("can add an entry", function() {
|
||||
expect(this.listView.model.get('value').length).toEqual(2);
|
||||
this.el.find('.create-setting').click();
|
||||
return expect(this.el.find('input.input').length).toEqual(3);
|
||||
});
|
||||
|
||||
it "can remove an entry", ->
|
||||
expect(@listView.model.get('value').length).toEqual(2)
|
||||
@el.find('.remove-setting').first().click()
|
||||
expect(@listView.model.get('value').length).toEqual(1)
|
||||
it("can remove an entry", function() {
|
||||
expect(this.listView.model.get('value').length).toEqual(2);
|
||||
this.el.find('.remove-setting').first().click();
|
||||
return expect(this.listView.model.get('value').length).toEqual(1);
|
||||
});
|
||||
|
||||
it "only allows one blank entry at a time", ->
|
||||
expect(@el.find('input').length).toEqual(2)
|
||||
@el.find('.create-setting').click()
|
||||
@el.find('.create-setting').click()
|
||||
expect(@el.find('input').length).toEqual(3)
|
||||
it("only allows one blank entry at a time", function() {
|
||||
expect(this.el.find('input').length).toEqual(2);
|
||||
this.el.find('.create-setting').click();
|
||||
this.el.find('.create-setting').click();
|
||||
return expect(this.el.find('input').length).toEqual(3);
|
||||
});
|
||||
|
||||
it "re-enables the add setting button after entering a new value", ->
|
||||
expect(@el.find('input').length).toEqual(2)
|
||||
@el.find('.create-setting').click()
|
||||
expect(@el.find('.create-setting')).toHaveClass('is-disabled')
|
||||
@el.find('input').last().val('third setting')
|
||||
@el.find('input').last().trigger('input')
|
||||
expect(@el.find('.create-setting')).not.toHaveClass('is-disabled')
|
||||
return it("re-enables the add setting button after entering a new value", function() {
|
||||
expect(this.el.find('input').length).toEqual(2);
|
||||
this.el.find('.create-setting').click();
|
||||
expect(this.el.find('.create-setting')).toHaveClass('is-disabled');
|
||||
this.el.find('input').last().val('third setting');
|
||||
this.el.find('input').last().trigger('input');
|
||||
return expect(this.el.find('.create-setting')).not.toHaveClass('is-disabled');
|
||||
});
|
||||
});
|
||||
|
||||
describe "MetadataView.RelativeTime allows the user to enter time string in HH:mm:ss format", ->
|
||||
beforeEach ->
|
||||
model = new MetadataModel(timeEntry)
|
||||
@view = new MetadataView.RelativeTime({model: model})
|
||||
describe("MetadataView.RelativeTime allows the user to enter time string in HH:mm:ss format", function() {
|
||||
beforeEach(function() {
|
||||
const model = new MetadataModel(timeEntry);
|
||||
return this.view = new MetadataView.RelativeTime({model});
|
||||
});
|
||||
|
||||
it "uses a text input type", ->
|
||||
assertInputType(@view, 'text')
|
||||
it("uses a text input type", function() {
|
||||
return assertInputType(this.view, 'text');
|
||||
});
|
||||
|
||||
it "returns the intial value upon initialization", ->
|
||||
assertValueInView(@view, '12:12:12')
|
||||
it("returns the intial value upon initialization", function() {
|
||||
return assertValueInView(this.view, '12:12:12');
|
||||
});
|
||||
|
||||
it "value is converted correctly", ->
|
||||
view = @view
|
||||
it("value is converted correctly", function() {
|
||||
const { view } = this;
|
||||
|
||||
cases = [
|
||||
const cases = [
|
||||
{
|
||||
input: '23:100:0'
|
||||
input: '23:100:0',
|
||||
output: '23:59:59'
|
||||
},
|
||||
{
|
||||
input: '100000000000000000'
|
||||
input: '100000000000000000',
|
||||
output: '23:59:59'
|
||||
},
|
||||
{
|
||||
input: '80000'
|
||||
input: '80000',
|
||||
output: '22:13:20'
|
||||
},
|
||||
{
|
||||
input: '-100'
|
||||
input: '-100',
|
||||
output: '00:00:00'
|
||||
},
|
||||
{
|
||||
input: '-100:-10'
|
||||
input: '-100:-10',
|
||||
output: '00:00:00'
|
||||
},
|
||||
{
|
||||
input: '99:99'
|
||||
input: '99:99',
|
||||
output: '01:40:39'
|
||||
},
|
||||
{
|
||||
input: '2'
|
||||
input: '2',
|
||||
output: '00:00:02'
|
||||
},
|
||||
{
|
||||
input: '1:2'
|
||||
input: '1:2',
|
||||
output: '00:01:02'
|
||||
},
|
||||
{
|
||||
input: '1:25'
|
||||
input: '1:25',
|
||||
output: '00:01:25'
|
||||
},
|
||||
{
|
||||
input: '3:1:25'
|
||||
input: '3:1:25',
|
||||
output: '03:01:25'
|
||||
},
|
||||
{
|
||||
input: ' 2 3 : 5 9 : 5 9 '
|
||||
input: ' 2 3 : 5 9 : 5 9 ',
|
||||
output: '23:59:59'
|
||||
},
|
||||
{
|
||||
input: '9:1:25'
|
||||
input: '9:1:25',
|
||||
output: '09:01:25'
|
||||
},
|
||||
{
|
||||
input: '77:72:77'
|
||||
input: '77:72:77',
|
||||
output: '23:59:59'
|
||||
},
|
||||
{
|
||||
input: '22:100:100'
|
||||
input: '22:100:100',
|
||||
output: '23:41:40'
|
||||
},
|
||||
# negative value
|
||||
// negative value
|
||||
{
|
||||
input: '-22:22:22'
|
||||
input: '-22:22:22',
|
||||
output: '00:22:22'
|
||||
},
|
||||
# simple string
|
||||
// simple string
|
||||
{
|
||||
input: 'simple text'
|
||||
input: 'simple text',
|
||||
output: '00:00:00'
|
||||
},
|
||||
{
|
||||
input: 'a10a:a10a:a10a'
|
||||
input: 'a10a:a10a:a10a',
|
||||
output: '00:00:00'
|
||||
},
|
||||
# empty string
|
||||
// empty string
|
||||
{
|
||||
input: ''
|
||||
input: '',
|
||||
output: '00:00:00'
|
||||
}
|
||||
]
|
||||
];
|
||||
|
||||
$.each cases, (index, data) ->
|
||||
expect(view.parseRelativeTime(data.input)).toBe(data.output)
|
||||
return $.each(cases, (index, data) => expect(view.parseRelativeTime(data.input)).toBe(data.output));
|
||||
});
|
||||
|
||||
it "can update its value in the view", ->
|
||||
assertCanUpdateView(@view, "23:59:59")
|
||||
@view.setValueInEditor("33:59:59")
|
||||
@view.updateModel()
|
||||
assertValueInView(@view, "23:59:59")
|
||||
it("can update its value in the view", function() {
|
||||
assertCanUpdateView(this.view, "23:59:59");
|
||||
this.view.setValueInEditor("33:59:59");
|
||||
this.view.updateModel();
|
||||
return assertValueInView(this.view, "23:59:59");
|
||||
});
|
||||
|
||||
it "has a clear method to revert to the model default", ->
|
||||
assertClear(@view, '00:00:00')
|
||||
it("has a clear method to revert to the model default", function() {
|
||||
return assertClear(this.view, '00:00:00');
|
||||
});
|
||||
|
||||
it "has an update model method", ->
|
||||
assertUpdateModel(@view, '12:12:12', '23:59:59')
|
||||
return it("has an update model method", function() {
|
||||
return assertUpdateModel(this.view, '12:12:12', '23:59:59');
|
||||
});
|
||||
});
|
||||
|
||||
describe "MetadataView.Dict allows the user to enter key-value pairs of strings", ->
|
||||
beforeEach ->
|
||||
dictModel = new MetadataModel($.extend(true, {}, dictEntry))
|
||||
@dictView = new MetadataView.Dict({model: dictModel})
|
||||
@el = @dictView.$el
|
||||
main()
|
||||
return describe("MetadataView.Dict allows the user to enter key-value pairs of strings", function() {
|
||||
beforeEach(function() {
|
||||
const dictModel = new MetadataModel($.extend(true, {}, dictEntry));
|
||||
this.dictView = new MetadataView.Dict({model: dictModel});
|
||||
this.el = this.dictView.$el;
|
||||
return main();
|
||||
});
|
||||
|
||||
it "returns the initial value upon initialization", ->
|
||||
assertValueInView(@dictView, {
|
||||
it("returns the initial value upon initialization", function() {
|
||||
return assertValueInView(this.dictView, {
|
||||
'en': 'English',
|
||||
'ru': 'Русский',
|
||||
'ua': 'Українська',
|
||||
'fr': 'Français'
|
||||
})
|
||||
});
|
||||
});
|
||||
|
||||
it "updates its value correctly", ->
|
||||
assertCanUpdateView(@dictView, {
|
||||
it("updates its value correctly", function() {
|
||||
return assertCanUpdateView(this.dictView, {
|
||||
'ru': 'Русский',
|
||||
'ua': 'Українська',
|
||||
'fr': 'Français'
|
||||
})
|
||||
});
|
||||
});
|
||||
|
||||
it "has a clear method to revert to the model default", ->
|
||||
@el.find('.create-setting').click()
|
||||
assertClear(@dictView, {
|
||||
it("has a clear method to revert to the model default", function() {
|
||||
this.el.find('.create-setting').click();
|
||||
assertClear(this.dictView, {
|
||||
'en': 'English',
|
||||
'ru': 'Русский'
|
||||
})
|
||||
expect(@el.find('.create-setting')).not.toHaveClass('is-disabled')
|
||||
});
|
||||
return expect(this.el.find('.create-setting')).not.toHaveClass('is-disabled');
|
||||
});
|
||||
|
||||
it "has an update model method", ->
|
||||
assertUpdateModel(@dictView, null, {'fr': 'Français'})
|
||||
it("has an update model method", function() {
|
||||
return assertUpdateModel(this.dictView, null, {'fr': 'Français'});
|
||||
});
|
||||
|
||||
it "can add an entry", ->
|
||||
expect(_.keys(@dictView.model.get('value')).length).toEqual(4)
|
||||
@el.find('.create-setting').click()
|
||||
expect(@el.find('input.input-key').length).toEqual(5)
|
||||
it("can add an entry", function() {
|
||||
expect(_.keys(this.dictView.model.get('value')).length).toEqual(4);
|
||||
this.el.find('.create-setting').click();
|
||||
return expect(this.el.find('input.input-key').length).toEqual(5);
|
||||
});
|
||||
|
||||
it "can remove an entry", ->
|
||||
expect(_.keys(@dictView.model.get('value')).length).toEqual(4)
|
||||
@el.find('.remove-setting').first().click()
|
||||
expect(_.keys(@dictView.model.get('value')).length).toEqual(3)
|
||||
it("can remove an entry", function() {
|
||||
expect(_.keys(this.dictView.model.get('value')).length).toEqual(4);
|
||||
this.el.find('.remove-setting').first().click();
|
||||
return expect(_.keys(this.dictView.model.get('value')).length).toEqual(3);
|
||||
});
|
||||
|
||||
it "only allows one blank entry at a time", ->
|
||||
expect(@el.find('input.input-key').length).toEqual(4)
|
||||
@el.find('.create-setting').click()
|
||||
@el.find('.create-setting').click()
|
||||
expect(@el.find('input.input-key').length).toEqual(5)
|
||||
it("only allows one blank entry at a time", function() {
|
||||
expect(this.el.find('input.input-key').length).toEqual(4);
|
||||
this.el.find('.create-setting').click();
|
||||
this.el.find('.create-setting').click();
|
||||
return expect(this.el.find('input.input-key').length).toEqual(5);
|
||||
});
|
||||
|
||||
it "only allows unique keys", ->
|
||||
data = [
|
||||
it("only allows unique keys", function() {
|
||||
const data = [
|
||||
{
|
||||
expectedValue: {'ru': 'Русский'},
|
||||
initialValue: {'ru': 'Русский'},
|
||||
testValue: {
|
||||
'key': 'ru'
|
||||
'key': 'ru',
|
||||
'value': ''
|
||||
}
|
||||
},
|
||||
@@ -574,7 +653,7 @@ define ["js/models/metadata", "js/collections/metadata", "js/views/metadata", "c
|
||||
expectedValue: {'ru': 'Русский'},
|
||||
initialValue: {'ru': 'Some value'},
|
||||
testValue: {
|
||||
'key': 'ru'
|
||||
'key': 'ru',
|
||||
'value': 'Русский'
|
||||
}
|
||||
},
|
||||
@@ -582,27 +661,33 @@ define ["js/models/metadata", "js/collections/metadata", "js/views/metadata", "c
|
||||
expectedValue: {'ru': 'Русский'},
|
||||
initialValue: {'ru': 'Русский'},
|
||||
testValue: {
|
||||
'key': ''
|
||||
'key': '',
|
||||
'value': ''
|
||||
}
|
||||
}
|
||||
]
|
||||
];
|
||||
|
||||
_.each data, ((d, index) ->
|
||||
@dictView.setValueInEditor(d.initialValue)
|
||||
@dictView.updateModel();
|
||||
@el.find('.create-setting').click()
|
||||
item = @el.find('.list-settings-item').last()
|
||||
return _.each(data, ((d, index) => {
|
||||
this.dictView.setValueInEditor(d.initialValue);
|
||||
this.dictView.updateModel();
|
||||
this.el.find('.create-setting').click();
|
||||
const item = this.el.find('.list-settings-item').last();
|
||||
item.find('.input-key').val(d.testValue.key);
|
||||
item.find('.input-value').val(d.testValue.value);
|
||||
|
||||
expect(@dictView.getValueFromEditor()).toEqual(d.expectedValue)
|
||||
).bind(@)
|
||||
return expect(this.dictView.getValueFromEditor()).toEqual(d.expectedValue);
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it "re-enables the add setting button after entering a new value", ->
|
||||
expect(@el.find('input.input-key').length).toEqual(4)
|
||||
@el.find('.create-setting').click()
|
||||
expect(@el.find('.create-setting')).toHaveClass('is-disabled')
|
||||
@el.find('input.input-key').last().val('third setting')
|
||||
@el.find('input.input-key').last().trigger('input')
|
||||
expect(@el.find('.create-setting')).not.toHaveClass('is-disabled')
|
||||
return it("re-enables the add setting button after entering a new value", function() {
|
||||
expect(this.el.find('input.input-key').length).toEqual(4);
|
||||
this.el.find('.create-setting').click();
|
||||
expect(this.el.find('.create-setting')).toHaveClass('is-disabled');
|
||||
this.el.find('input.input-key').last().val('third setting');
|
||||
this.el.find('input.input-key').last().trigger('input');
|
||||
return expect(this.el.find('.create-setting')).not.toHaveClass('is-disabled');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,27 +1,32 @@
|
||||
define ["js/models/textbook", "js/models/chapter", "js/collections/chapter", "js/models/course",
|
||||
/*
|
||||
* decaffeinate suggestions:
|
||||
* DS102: Remove unnecessary code created because of implicit returns
|
||||
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
|
||||
*/
|
||||
define(["js/models/textbook", "js/models/chapter", "js/collections/chapter", "js/models/course",
|
||||
"js/collections/textbook", "js/views/show_textbook", "js/views/edit_textbook", "js/views/list_textbooks",
|
||||
"js/views/edit_chapter", "common/js/components/views/feedback_prompt",
|
||||
"common/js/components/views/feedback_notification", "common/js/components/utils/view_utils",
|
||||
"edx-ui-toolkit/js/utils/spec-helpers/ajax-helpers",
|
||||
"js/spec_helpers/modal_helpers"],
|
||||
(Textbook, Chapter, ChapterSet, Course, TextbookSet, ShowTextbook, EditTextbook, ListTextbooks, EditChapter,
|
||||
Prompt, Notification, ViewUtils, AjaxHelpers, modal_helpers) ->
|
||||
function(Textbook, Chapter, ChapterSet, Course, TextbookSet, ShowTextbook, EditTextbook, ListTextbooks, EditChapter,
|
||||
Prompt, Notification, ViewUtils, AjaxHelpers, modal_helpers) {
|
||||
|
||||
describe "ShowTextbook", ->
|
||||
tpl = readFixtures('show-textbook.underscore')
|
||||
describe("ShowTextbook", function() {
|
||||
const tpl = readFixtures('show-textbook.underscore');
|
||||
|
||||
beforeEach ->
|
||||
setFixtures($("<script>", {id: "show-textbook-tpl", type: "text/template"}).text(tpl))
|
||||
appendSetFixtures(sandbox({id: "page-notification"}))
|
||||
appendSetFixtures(sandbox({id: "page-prompt"}))
|
||||
@model = new Textbook({name: "Life Sciences", id: "0life-sciences"})
|
||||
spyOn(@model, "destroy").and.callThrough()
|
||||
@collection = new TextbookSet([@model])
|
||||
@view = new ShowTextbook({model: @model})
|
||||
beforeEach(function() {
|
||||
setFixtures($("<script>", {id: "show-textbook-tpl", type: "text/template"}).text(tpl));
|
||||
appendSetFixtures(sandbox({id: "page-notification"}));
|
||||
appendSetFixtures(sandbox({id: "page-prompt"}));
|
||||
this.model = new Textbook({name: "Life Sciences", id: "0life-sciences"});
|
||||
spyOn(this.model, "destroy").and.callThrough();
|
||||
this.collection = new TextbookSet([this.model]);
|
||||
this.view = new ShowTextbook({model: this.model});
|
||||
|
||||
@promptSpies = jasmine.stealth.spyOnConstructor(Prompt, "Warning", ["show", "hide"])
|
||||
@promptSpies.show.and.returnValue(@promptSpies)
|
||||
window.course = new Course({
|
||||
this.promptSpies = jasmine.stealth.spyOnConstructor(Prompt, "Warning", ["show", "hide"]);
|
||||
this.promptSpies.show.and.returnValue(this.promptSpies);
|
||||
return window.course = new Course({
|
||||
id: "5",
|
||||
name: "Course Name",
|
||||
url_name: "course_name",
|
||||
@@ -29,313 +34,347 @@ define ["js/models/textbook", "js/models/chapter", "js/collections/chapter", "js
|
||||
num: "course_num",
|
||||
revision: "course_rev"
|
||||
});
|
||||
});
|
||||
|
||||
afterEach ->
|
||||
delete window.course
|
||||
afterEach(() => delete window.course);
|
||||
|
||||
describe "Basic", ->
|
||||
it "should render properly", ->
|
||||
@view.render()
|
||||
expect(@view.$el).toContainText("Life Sciences")
|
||||
describe("Basic", function() {
|
||||
it("should render properly", function() {
|
||||
this.view.render();
|
||||
return expect(this.view.$el).toContainText("Life Sciences");
|
||||
});
|
||||
|
||||
it "should set the 'editing' property on the model when the edit button is clicked", ->
|
||||
@view.render().$(".edit").click()
|
||||
expect(@model.get("editing")).toBeTruthy()
|
||||
it("should set the 'editing' property on the model when the edit button is clicked", function() {
|
||||
this.view.render().$(".edit").click();
|
||||
return expect(this.model.get("editing")).toBeTruthy();
|
||||
});
|
||||
|
||||
it "should pop a delete confirmation when the delete button is clicked", ->
|
||||
@view.render().$(".delete").click()
|
||||
expect(@promptSpies.constructor).toHaveBeenCalled()
|
||||
ctorOptions = @promptSpies.constructor.calls.mostRecent().args[0]
|
||||
expect(ctorOptions.title).toMatch(/Life Sciences/)
|
||||
# hasn't actually been removed
|
||||
expect(@model.destroy).not.toHaveBeenCalled()
|
||||
expect(@collection).toContain(@model)
|
||||
it("should pop a delete confirmation when the delete button is clicked", function() {
|
||||
this.view.render().$(".delete").click();
|
||||
expect(this.promptSpies.constructor).toHaveBeenCalled();
|
||||
const ctorOptions = this.promptSpies.constructor.calls.mostRecent().args[0];
|
||||
expect(ctorOptions.title).toMatch(/Life Sciences/);
|
||||
// hasn't actually been removed
|
||||
expect(this.model.destroy).not.toHaveBeenCalled();
|
||||
return expect(this.collection).toContain(this.model);
|
||||
});
|
||||
|
||||
it "should show chapters appropriately", ->
|
||||
@model.get("chapters").add([{}, {}, {}])
|
||||
@model.set('showChapters', false)
|
||||
@view.render().$(".show-chapters").click()
|
||||
expect(@model.get('showChapters')).toBeTruthy()
|
||||
it("should show chapters appropriately", function() {
|
||||
this.model.get("chapters").add([{}, {}, {}]);
|
||||
this.model.set('showChapters', false);
|
||||
this.view.render().$(".show-chapters").click();
|
||||
return expect(this.model.get('showChapters')).toBeTruthy();
|
||||
});
|
||||
|
||||
it "should hide chapters appropriately", ->
|
||||
@model.get("chapters").add([{}, {}, {}])
|
||||
@model.set('showChapters', true)
|
||||
@view.render().$(".hide-chapters").click()
|
||||
expect(@model.get('showChapters')).toBeFalsy()
|
||||
return it("should hide chapters appropriately", function() {
|
||||
this.model.get("chapters").add([{}, {}, {}]);
|
||||
this.model.set('showChapters', true);
|
||||
this.view.render().$(".hide-chapters").click();
|
||||
return expect(this.model.get('showChapters')).toBeFalsy();
|
||||
});
|
||||
});
|
||||
|
||||
describe "AJAX", ->
|
||||
beforeEach ->
|
||||
@savingSpies = jasmine.stealth.spyOnConstructor(Notification, "Mini",
|
||||
["show", "hide"])
|
||||
@savingSpies.show.and.returnValue(@savingSpies)
|
||||
CMS.URL.TEXTBOOKS = "/textbooks"
|
||||
return describe("AJAX", function() {
|
||||
beforeEach(function() {
|
||||
this.savingSpies = jasmine.stealth.spyOnConstructor(Notification, "Mini",
|
||||
["show", "hide"]);
|
||||
this.savingSpies.show.and.returnValue(this.savingSpies);
|
||||
return CMS.URL.TEXTBOOKS = "/textbooks";
|
||||
});
|
||||
|
||||
afterEach ->
|
||||
delete CMS.URL.TEXTBOOKS
|
||||
afterEach(() => delete CMS.URL.TEXTBOOKS);
|
||||
|
||||
it "should destroy itself on confirmation", ->
|
||||
requests = AjaxHelpers["requests"](this)
|
||||
return it("should destroy itself on confirmation", function() {
|
||||
const requests = AjaxHelpers["requests"](this);
|
||||
|
||||
@view.render().$(".delete").click()
|
||||
ctorOptions = @promptSpies.constructor.calls.mostRecent().args[0]
|
||||
# run the primary function to indicate confirmation
|
||||
ctorOptions.actions.primary.click(@promptSpies)
|
||||
# AJAX request has been sent, but not yet returned
|
||||
expect(@model.destroy).toHaveBeenCalled()
|
||||
expect(requests.length).toEqual(1)
|
||||
expect(@savingSpies.constructor).toHaveBeenCalled()
|
||||
expect(@savingSpies.show).toHaveBeenCalled()
|
||||
expect(@savingSpies.hide).not.toHaveBeenCalled()
|
||||
savingOptions = @savingSpies.constructor.calls.mostRecent().args[0]
|
||||
expect(savingOptions.title).toMatch(/Deleting/)
|
||||
# return a success response
|
||||
requests[0].respond(204)
|
||||
expect(@savingSpies.hide).toHaveBeenCalled()
|
||||
expect(@collection.contains(@model)).toBeFalsy()
|
||||
this.view.render().$(".delete").click();
|
||||
const ctorOptions = this.promptSpies.constructor.calls.mostRecent().args[0];
|
||||
// run the primary function to indicate confirmation
|
||||
ctorOptions.actions.primary.click(this.promptSpies);
|
||||
// AJAX request has been sent, but not yet returned
|
||||
expect(this.model.destroy).toHaveBeenCalled();
|
||||
expect(requests.length).toEqual(1);
|
||||
expect(this.savingSpies.constructor).toHaveBeenCalled();
|
||||
expect(this.savingSpies.show).toHaveBeenCalled();
|
||||
expect(this.savingSpies.hide).not.toHaveBeenCalled();
|
||||
const savingOptions = this.savingSpies.constructor.calls.mostRecent().args[0];
|
||||
expect(savingOptions.title).toMatch(/Deleting/);
|
||||
// return a success response
|
||||
requests[0].respond(204);
|
||||
expect(this.savingSpies.hide).toHaveBeenCalled();
|
||||
return expect(this.collection.contains(this.model)).toBeFalsy();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe "EditTextbook", ->
|
||||
describe "Basic", ->
|
||||
tpl = readFixtures('edit-textbook.underscore')
|
||||
describe("EditTextbook", () =>
|
||||
describe("Basic", function() {
|
||||
const tpl = readFixtures('edit-textbook.underscore');
|
||||
|
||||
beforeEach ->
|
||||
setFixtures($("<script>", {id: "edit-textbook-tpl", type: "text/template"}).text(tpl))
|
||||
appendSetFixtures(sandbox({id: "page-notification"}))
|
||||
appendSetFixtures(sandbox({id: "page-prompt"}))
|
||||
@model = new Textbook({name: "Life Sciences", editing: true})
|
||||
spyOn(@model, 'save')
|
||||
@collection = new TextbookSet()
|
||||
@collection.add(@model)
|
||||
@view = new EditTextbook({model: @model})
|
||||
spyOn(@view, 'render').and.callThrough()
|
||||
beforeEach(function() {
|
||||
setFixtures($("<script>", {id: "edit-textbook-tpl", type: "text/template"}).text(tpl));
|
||||
appendSetFixtures(sandbox({id: "page-notification"}));
|
||||
appendSetFixtures(sandbox({id: "page-prompt"}));
|
||||
this.model = new Textbook({name: "Life Sciences", editing: true});
|
||||
spyOn(this.model, 'save');
|
||||
this.collection = new TextbookSet();
|
||||
this.collection.add(this.model);
|
||||
this.view = new EditTextbook({model: this.model});
|
||||
return spyOn(this.view, 'render').and.callThrough();
|
||||
});
|
||||
|
||||
it "should render properly", ->
|
||||
@view.render()
|
||||
expect(@view.$("input[name=textbook-name]").val()).toEqual("Life Sciences")
|
||||
it("should render properly", function() {
|
||||
this.view.render();
|
||||
return expect(this.view.$("input[name=textbook-name]").val()).toEqual("Life Sciences");
|
||||
});
|
||||
|
||||
it "should allow you to create new empty chapters", ->
|
||||
@view.render()
|
||||
numChapters = @model.get("chapters").length
|
||||
@view.$(".action-add-chapter").click()
|
||||
expect(@model.get("chapters").length).toEqual(numChapters+1)
|
||||
expect(@model.get("chapters").last().isEmpty()).toBeTruthy()
|
||||
it("should allow you to create new empty chapters", function() {
|
||||
this.view.render();
|
||||
const numChapters = this.model.get("chapters").length;
|
||||
this.view.$(".action-add-chapter").click();
|
||||
expect(this.model.get("chapters").length).toEqual(numChapters+1);
|
||||
return expect(this.model.get("chapters").last().isEmpty()).toBeTruthy();
|
||||
});
|
||||
|
||||
it "should save properly", ->
|
||||
@view.render()
|
||||
@view.$("input[name=textbook-name]").val("starfish")
|
||||
@view.$("input[name=chapter1-name]").val("wallflower")
|
||||
@view.$("input[name=chapter1-asset-path]").val("foobar")
|
||||
@view.$("form").submit()
|
||||
expect(@model.get("name")).toEqual("starfish")
|
||||
chapter = @model.get("chapters").first()
|
||||
expect(chapter.get("name")).toEqual("wallflower")
|
||||
expect(chapter.get("asset_path")).toEqual("foobar")
|
||||
expect(@model.save).toHaveBeenCalled()
|
||||
it("should save properly", function() {
|
||||
this.view.render();
|
||||
this.view.$("input[name=textbook-name]").val("starfish");
|
||||
this.view.$("input[name=chapter1-name]").val("wallflower");
|
||||
this.view.$("input[name=chapter1-asset-path]").val("foobar");
|
||||
this.view.$("form").submit();
|
||||
expect(this.model.get("name")).toEqual("starfish");
|
||||
const chapter = this.model.get("chapters").first();
|
||||
expect(chapter.get("name")).toEqual("wallflower");
|
||||
expect(chapter.get("asset_path")).toEqual("foobar");
|
||||
return expect(this.model.save).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it "should not save on invalid", ->
|
||||
@view.render()
|
||||
@view.$("input[name=textbook-name]").val("")
|
||||
@view.$("input[name=chapter1-asset-path]").val("foobar.pdf")
|
||||
@view.$("form").submit()
|
||||
expect(@model.validationError).toBeTruthy()
|
||||
expect(@model.save).not.toHaveBeenCalled()
|
||||
it("should not save on invalid", function() {
|
||||
this.view.render();
|
||||
this.view.$("input[name=textbook-name]").val("");
|
||||
this.view.$("input[name=chapter1-asset-path]").val("foobar.pdf");
|
||||
this.view.$("form").submit();
|
||||
expect(this.model.validationError).toBeTruthy();
|
||||
return expect(this.model.save).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it "does not save on cancel", ->
|
||||
@model.get("chapters").add([{name: "a", asset_path: "b"}])
|
||||
@view.render()
|
||||
@view.$("input[name=textbook-name]").val("starfish")
|
||||
@view.$("input[name=chapter1-asset-path]").val("foobar.pdf")
|
||||
@view.$(".action-cancel").click()
|
||||
expect(@model.get("name")).not.toEqual("starfish")
|
||||
chapter = @model.get("chapters").first()
|
||||
expect(chapter.get("asset_path")).not.toEqual("foobar")
|
||||
expect(@model.save).not.toHaveBeenCalled()
|
||||
it("does not save on cancel", function() {
|
||||
this.model.get("chapters").add([{name: "a", asset_path: "b"}]);
|
||||
this.view.render();
|
||||
this.view.$("input[name=textbook-name]").val("starfish");
|
||||
this.view.$("input[name=chapter1-asset-path]").val("foobar.pdf");
|
||||
this.view.$(".action-cancel").click();
|
||||
expect(this.model.get("name")).not.toEqual("starfish");
|
||||
const chapter = this.model.get("chapters").first();
|
||||
expect(chapter.get("asset_path")).not.toEqual("foobar");
|
||||
return expect(this.model.save).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it "should be possible to correct validation errors", ->
|
||||
@view.render()
|
||||
@view.$("input[name=textbook-name]").val("")
|
||||
@view.$("input[name=chapter1-asset-path]").val("foobar.pdf")
|
||||
@view.$("form").submit()
|
||||
expect(@model.validationError).toBeTruthy()
|
||||
expect(@model.save).not.toHaveBeenCalled()
|
||||
@view.$("input[name=textbook-name]").val("starfish")
|
||||
@view.$("input[name=chapter1-name]").val("foobar")
|
||||
@view.$("form").submit()
|
||||
expect(@model.validationError).toBeFalsy()
|
||||
expect(@model.save).toHaveBeenCalled()
|
||||
it("should be possible to correct validation errors", function() {
|
||||
this.view.render();
|
||||
this.view.$("input[name=textbook-name]").val("");
|
||||
this.view.$("input[name=chapter1-asset-path]").val("foobar.pdf");
|
||||
this.view.$("form").submit();
|
||||
expect(this.model.validationError).toBeTruthy();
|
||||
expect(this.model.save).not.toHaveBeenCalled();
|
||||
this.view.$("input[name=textbook-name]").val("starfish");
|
||||
this.view.$("input[name=chapter1-name]").val("foobar");
|
||||
this.view.$("form").submit();
|
||||
expect(this.model.validationError).toBeFalsy();
|
||||
return expect(this.model.save).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it "removes all empty chapters on cancel if the model has a non-empty chapter", ->
|
||||
chapters = @model.get("chapters")
|
||||
chapters.at(0).set("name", "non-empty")
|
||||
@model.setOriginalAttributes()
|
||||
@view.render()
|
||||
chapters.add([{}, {}, {}]) # add three empty chapters
|
||||
expect(chapters.length).toEqual(4)
|
||||
@view.$(".action-cancel").click()
|
||||
expect(chapters.length).toEqual(1)
|
||||
expect(chapters.first().get('name')).toEqual("non-empty")
|
||||
it("removes all empty chapters on cancel if the model has a non-empty chapter", function() {
|
||||
const chapters = this.model.get("chapters");
|
||||
chapters.at(0).set("name", "non-empty");
|
||||
this.model.setOriginalAttributes();
|
||||
this.view.render();
|
||||
chapters.add([{}, {}, {}]); // add three empty chapters
|
||||
expect(chapters.length).toEqual(4);
|
||||
this.view.$(".action-cancel").click();
|
||||
expect(chapters.length).toEqual(1);
|
||||
return expect(chapters.first().get('name')).toEqual("non-empty");
|
||||
});
|
||||
|
||||
it "removes all empty chapters on cancel except one if the model has no non-empty chapters", ->
|
||||
chapters = @model.get("chapters")
|
||||
@view.render()
|
||||
chapters.add([{}, {}, {}]) # add three empty chapters
|
||||
expect(chapters.length).toEqual(4)
|
||||
@view.$(".action-cancel").click()
|
||||
expect(chapters.length).toEqual(1)
|
||||
return it("removes all empty chapters on cancel except one if the model has no non-empty chapters", function() {
|
||||
const chapters = this.model.get("chapters");
|
||||
this.view.render();
|
||||
chapters.add([{}, {}, {}]); // add three empty chapters
|
||||
expect(chapters.length).toEqual(4);
|
||||
this.view.$(".action-cancel").click();
|
||||
return expect(chapters.length).toEqual(1);
|
||||
});
|
||||
})
|
||||
);
|
||||
|
||||
describe "ListTextbooks", ->
|
||||
noTextbooksTpl = readFixtures("no-textbooks.underscore")
|
||||
editTextbooktpl = readFixtures('edit-textbook.underscore')
|
||||
describe("ListTextbooks", function() {
|
||||
const noTextbooksTpl = readFixtures("no-textbooks.underscore");
|
||||
const editTextbooktpl = readFixtures('edit-textbook.underscore');
|
||||
|
||||
beforeEach ->
|
||||
appendSetFixtures($("<script>", {id: "no-textbooks-tpl", type: "text/template"}).text(noTextbooksTpl))
|
||||
appendSetFixtures($("<script>", {id: "edit-textbook-tpl", type: "text/template"}).text(editTextbooktpl))
|
||||
@collection = new TextbookSet
|
||||
@view = new ListTextbooks({collection: @collection})
|
||||
@view.render()
|
||||
beforeEach(function() {
|
||||
appendSetFixtures($("<script>", {id: "no-textbooks-tpl", type: "text/template"}).text(noTextbooksTpl));
|
||||
appendSetFixtures($("<script>", {id: "edit-textbook-tpl", type: "text/template"}).text(editTextbooktpl));
|
||||
this.collection = new TextbookSet;
|
||||
this.view = new ListTextbooks({collection: this.collection});
|
||||
return this.view.render();
|
||||
});
|
||||
|
||||
it "should scroll to newly added textbook", ->
|
||||
spyOn(ViewUtils, 'setScrollOffset')
|
||||
@view.$(".new-button").click()
|
||||
$sectionEl = @view.$el.find('section:last')
|
||||
expect($sectionEl.length).toEqual(1)
|
||||
expect(ViewUtils.setScrollOffset).toHaveBeenCalledWith($sectionEl, 0)
|
||||
it("should scroll to newly added textbook", function() {
|
||||
spyOn(ViewUtils, 'setScrollOffset');
|
||||
this.view.$(".new-button").click();
|
||||
const $sectionEl = this.view.$el.find('section:last');
|
||||
expect($sectionEl.length).toEqual(1);
|
||||
return expect(ViewUtils.setScrollOffset).toHaveBeenCalledWith($sectionEl, 0);
|
||||
});
|
||||
|
||||
it "should focus first input element of newly added textbook", ->
|
||||
spyOn(jQuery.fn, 'focus').and.callThrough()
|
||||
jasmine.addMatchers
|
||||
toHaveBeenCalledOnJQueryObject: () ->
|
||||
return it("should focus first input element of newly added textbook", function() {
|
||||
spyOn(jQuery.fn, 'focus').and.callThrough();
|
||||
jasmine.addMatchers({
|
||||
toHaveBeenCalledOnJQueryObject() {
|
||||
return {
|
||||
compare: (actual, expected) ->
|
||||
compare(actual, expected) {
|
||||
return {
|
||||
pass: actual.calls && actual.calls.mostRecent() &&
|
||||
actual.calls.mostRecent().object[0] == expected[0]
|
||||
}
|
||||
}
|
||||
@view.$(".new-button").click()
|
||||
$inputEl = @view.$el.find('section:last input:first')
|
||||
expect($inputEl.length).toEqual(1)
|
||||
# testing for element focused seems to be tricky
|
||||
# (see http://stackoverflow.com/questions/967096)
|
||||
# and the following doesn't seem to work
|
||||
# expect($inputEl).toBeFocused()
|
||||
# expect($inputEl.find(':focus').length).toEqual(1)
|
||||
expect(jQuery.fn.focus).toHaveBeenCalledOnJQueryObject($inputEl)
|
||||
(actual.calls.mostRecent().object[0] === expected[0])
|
||||
};
|
||||
}
|
||||
};
|
||||
}});
|
||||
this.view.$(".new-button").click();
|
||||
const $inputEl = this.view.$el.find('section:last input:first');
|
||||
expect($inputEl.length).toEqual(1);
|
||||
// testing for element focused seems to be tricky
|
||||
// (see http://stackoverflow.com/questions/967096)
|
||||
// and the following doesn't seem to work
|
||||
// expect($inputEl).toBeFocused()
|
||||
// expect($inputEl.find(':focus').length).toEqual(1)
|
||||
return expect(jQuery.fn.focus).toHaveBeenCalledOnJQueryObject($inputEl);
|
||||
});
|
||||
});
|
||||
|
||||
# describe "ListTextbooks", ->
|
||||
# noTextbooksTpl = readFixtures("no-textbooks.underscore")
|
||||
#
|
||||
# beforeEach ->
|
||||
# setFixtures($("<script>", {id: "no-textbooks-tpl", type: "text/template"}).text(noTextbooksTpl))
|
||||
# @showSpies = spyOnConstructor("ShowTextbook", ["render"])
|
||||
# @showSpies.render.and.returnValue(@showSpies) # equivalent of `return this`
|
||||
# showEl = $("<li>")
|
||||
# @showSpies.$el = showEl
|
||||
# @showSpies.el = showEl.get(0)
|
||||
# @editSpies = spyOnConstructor("EditTextbook", ["render"])
|
||||
# editEl = $("<li>")
|
||||
# @editSpies.render.and.returnValue(@editSpies)
|
||||
# @editSpies.$el = editEl
|
||||
# @editSpies.el= editEl.get(0)
|
||||
#
|
||||
# @collection = new TextbookSet
|
||||
# @view = new ListTextbooks({collection: @collection})
|
||||
# @view.render()
|
||||
#
|
||||
# it "should render the empty template if there are no textbooks", ->
|
||||
# expect(@view.$el).toContainText("You haven't added any textbooks to this course yet")
|
||||
# expect(@view.$el).toContain(".new-button")
|
||||
# expect(@showSpies.constructor).not.toHaveBeenCalled()
|
||||
# expect(@editSpies.constructor).not.toHaveBeenCalled()
|
||||
#
|
||||
# it "should render ShowTextbook views by default if no textbook is being edited", ->
|
||||
# # add three empty textbooks to the collection
|
||||
# @collection.add([{}, {}, {}])
|
||||
# # reset spies due to re-rendering on collection modification
|
||||
# @showSpies.constructor.reset()
|
||||
# @editSpies.constructor.reset()
|
||||
# # render once and test
|
||||
# @view.render()
|
||||
#
|
||||
# expect(@view.$el).not.toContainText(
|
||||
# "You haven't added any textbooks to this course yet")
|
||||
# expect(@showSpies.constructor).toHaveBeenCalled()
|
||||
# expect(@showSpies.constructor.calls.length).toEqual(3);
|
||||
# expect(@editSpies.constructor).not.toHaveBeenCalled()
|
||||
#
|
||||
# it "should render an EditTextbook view for a textbook being edited", ->
|
||||
# # add three empty textbooks to the collection: the first and third
|
||||
# # should be shown, and the second should be edited
|
||||
# @collection.add([{editing: false}, {editing: true}, {editing: false}])
|
||||
# editing = @collection.at(1)
|
||||
# expect(editing.get("editing")).toBeTruthy()
|
||||
# # reset spies
|
||||
# @showSpies.constructor.reset()
|
||||
# @editSpies.constructor.reset()
|
||||
# # render once and test
|
||||
# @view.render()
|
||||
#
|
||||
# expect(@showSpies.constructor).toHaveBeenCalled()
|
||||
# expect(@showSpies.constructor.calls.length).toEqual(2)
|
||||
# expect(@showSpies.constructor).not.toHaveBeenCalledWith({model: editing})
|
||||
# expect(@editSpies.constructor).toHaveBeenCalled()
|
||||
# expect(@editSpies.constructor.calls.length).toEqual(1)
|
||||
# expect(@editSpies.constructor).toHaveBeenCalledWith({model: editing})
|
||||
#
|
||||
# it "should add a new textbook when the new-button is clicked", ->
|
||||
# # reset spies
|
||||
# @showSpies.constructor.reset()
|
||||
# @editSpies.constructor.reset()
|
||||
# # test
|
||||
# @view.$(".new-button").click()
|
||||
#
|
||||
# expect(@collection.length).toEqual(1)
|
||||
# expect(@view.$el).toContain(@editSpies.$el)
|
||||
# expect(@view.$el).not.toContain(@showSpies.$el)
|
||||
// describe "ListTextbooks", ->
|
||||
// noTextbooksTpl = readFixtures("no-textbooks.underscore")
|
||||
//
|
||||
// beforeEach ->
|
||||
// setFixtures($("<script>", {id: "no-textbooks-tpl", type: "text/template"}).text(noTextbooksTpl))
|
||||
// @showSpies = spyOnConstructor("ShowTextbook", ["render"])
|
||||
// @showSpies.render.and.returnValue(@showSpies) # equivalent of `return this`
|
||||
// showEl = $("<li>")
|
||||
// @showSpies.$el = showEl
|
||||
// @showSpies.el = showEl.get(0)
|
||||
// @editSpies = spyOnConstructor("EditTextbook", ["render"])
|
||||
// editEl = $("<li>")
|
||||
// @editSpies.render.and.returnValue(@editSpies)
|
||||
// @editSpies.$el = editEl
|
||||
// @editSpies.el= editEl.get(0)
|
||||
//
|
||||
// @collection = new TextbookSet
|
||||
// @view = new ListTextbooks({collection: @collection})
|
||||
// @view.render()
|
||||
//
|
||||
// it "should render the empty template if there are no textbooks", ->
|
||||
// expect(@view.$el).toContainText("You haven't added any textbooks to this course yet")
|
||||
// expect(@view.$el).toContain(".new-button")
|
||||
// expect(@showSpies.constructor).not.toHaveBeenCalled()
|
||||
// expect(@editSpies.constructor).not.toHaveBeenCalled()
|
||||
//
|
||||
// it "should render ShowTextbook views by default if no textbook is being edited", ->
|
||||
// # add three empty textbooks to the collection
|
||||
// @collection.add([{}, {}, {}])
|
||||
// # reset spies due to re-rendering on collection modification
|
||||
// @showSpies.constructor.reset()
|
||||
// @editSpies.constructor.reset()
|
||||
// # render once and test
|
||||
// @view.render()
|
||||
//
|
||||
// expect(@view.$el).not.toContainText(
|
||||
// "You haven't added any textbooks to this course yet")
|
||||
// expect(@showSpies.constructor).toHaveBeenCalled()
|
||||
// expect(@showSpies.constructor.calls.length).toEqual(3);
|
||||
// expect(@editSpies.constructor).not.toHaveBeenCalled()
|
||||
//
|
||||
// it "should render an EditTextbook view for a textbook being edited", ->
|
||||
// # add three empty textbooks to the collection: the first and third
|
||||
// # should be shown, and the second should be edited
|
||||
// @collection.add([{editing: false}, {editing: true}, {editing: false}])
|
||||
// editing = @collection.at(1)
|
||||
// expect(editing.get("editing")).toBeTruthy()
|
||||
// # reset spies
|
||||
// @showSpies.constructor.reset()
|
||||
// @editSpies.constructor.reset()
|
||||
// # render once and test
|
||||
// @view.render()
|
||||
//
|
||||
// expect(@showSpies.constructor).toHaveBeenCalled()
|
||||
// expect(@showSpies.constructor.calls.length).toEqual(2)
|
||||
// expect(@showSpies.constructor).not.toHaveBeenCalledWith({model: editing})
|
||||
// expect(@editSpies.constructor).toHaveBeenCalled()
|
||||
// expect(@editSpies.constructor.calls.length).toEqual(1)
|
||||
// expect(@editSpies.constructor).toHaveBeenCalledWith({model: editing})
|
||||
//
|
||||
// it "should add a new textbook when the new-button is clicked", ->
|
||||
// # reset spies
|
||||
// @showSpies.constructor.reset()
|
||||
// @editSpies.constructor.reset()
|
||||
// # test
|
||||
// @view.$(".new-button").click()
|
||||
//
|
||||
// expect(@collection.length).toEqual(1)
|
||||
// expect(@view.$el).toContain(@editSpies.$el)
|
||||
// expect(@view.$el).not.toContain(@showSpies.$el)
|
||||
|
||||
|
||||
describe "EditChapter", ->
|
||||
beforeEach ->
|
||||
modal_helpers.installModalTemplates()
|
||||
@model = new Chapter
|
||||
name: "Chapter 1"
|
||||
return describe("EditChapter", function() {
|
||||
beforeEach(function() {
|
||||
modal_helpers.installModalTemplates();
|
||||
this.model = new Chapter({
|
||||
name: "Chapter 1",
|
||||
asset_path: "/ch1.pdf"
|
||||
@collection = new ChapterSet()
|
||||
@collection.add(@model)
|
||||
@view = new EditChapter({model: @model})
|
||||
spyOn(@view, "remove").and.callThrough()
|
||||
CMS.URL.UPLOAD_ASSET = "/upload"
|
||||
window.course = new Course({name: "abcde"})
|
||||
});
|
||||
this.collection = new ChapterSet();
|
||||
this.collection.add(this.model);
|
||||
this.view = new EditChapter({model: this.model});
|
||||
spyOn(this.view, "remove").and.callThrough();
|
||||
CMS.URL.UPLOAD_ASSET = "/upload";
|
||||
return window.course = new Course({name: "abcde"});
|
||||
});
|
||||
|
||||
afterEach ->
|
||||
delete CMS.URL.UPLOAD_ASSET
|
||||
delete window.course
|
||||
afterEach(function() {
|
||||
delete CMS.URL.UPLOAD_ASSET;
|
||||
return delete window.course;
|
||||
});
|
||||
|
||||
it "can render", ->
|
||||
@view.render()
|
||||
expect(@view.$("input.chapter-name").val()).toEqual("Chapter 1")
|
||||
expect(@view.$("input.chapter-asset-path").val()).toEqual("/ch1.pdf")
|
||||
it("can render", function() {
|
||||
this.view.render();
|
||||
expect(this.view.$("input.chapter-name").val()).toEqual("Chapter 1");
|
||||
return expect(this.view.$("input.chapter-asset-path").val()).toEqual("/ch1.pdf");
|
||||
});
|
||||
|
||||
it "can delete itself", ->
|
||||
@view.render().$(".action-close").click()
|
||||
expect(@collection.length).toEqual(0)
|
||||
expect(@view.remove).toHaveBeenCalled()
|
||||
it("can delete itself", function() {
|
||||
this.view.render().$(".action-close").click();
|
||||
expect(this.collection.length).toEqual(0);
|
||||
return expect(this.view.remove).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
# it "can open an upload dialog", ->
|
||||
# uploadSpies = spyOnConstructor("UploadDialog", ["show", "el"])
|
||||
# uploadSpies.show.and.returnValue(uploadSpies)
|
||||
#
|
||||
# @view.render().$(".action-upload").click()
|
||||
# ctorOptions = uploadSpies.constructor.calls.mostRecent().args[0]
|
||||
# expect(ctorOptions.model.get('title')).toMatch(/abcde/)
|
||||
# expect(typeof ctorOptions.onSuccess).toBe('function')
|
||||
# expect(uploadSpies.show).toHaveBeenCalled()
|
||||
// it "can open an upload dialog", ->
|
||||
// uploadSpies = spyOnConstructor("UploadDialog", ["show", "el"])
|
||||
// uploadSpies.show.and.returnValue(uploadSpies)
|
||||
//
|
||||
// @view.render().$(".action-upload").click()
|
||||
// ctorOptions = uploadSpies.constructor.calls.mostRecent().args[0]
|
||||
// expect(ctorOptions.model.get('title')).toMatch(/abcde/)
|
||||
// expect(typeof ctorOptions.onSuccess).toBe('function')
|
||||
// expect(uploadSpies.show).toHaveBeenCalled()
|
||||
|
||||
# Disabling because this test does not close the modal dialog. This can cause
|
||||
# tests that run after it to fail (see STUD-1963).
|
||||
xit "saves content when opening upload dialog", ->
|
||||
@view.render()
|
||||
@view.$("input.chapter-name").val("rainbows")
|
||||
@view.$("input.chapter-asset-path").val("unicorns")
|
||||
@view.$(".action-upload").click()
|
||||
expect(@model.get("name")).toEqual("rainbows")
|
||||
expect(@model.get("asset_path")).toEqual("unicorns")
|
||||
// Disabling because this test does not close the modal dialog. This can cause
|
||||
// tests that run after it to fail (see STUD-1963).
|
||||
return xit("saves content when opening upload dialog", function() {
|
||||
this.view.render();
|
||||
this.view.$("input.chapter-name").val("rainbows");
|
||||
this.view.$("input.chapter-asset-path").val("unicorns");
|
||||
this.view.$(".action-upload").click();
|
||||
expect(this.model.get("name")).toEqual("rainbows");
|
||||
return expect(this.model.get("asset_path")).toEqual("unicorns");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,135 +1,161 @@
|
||||
define ["sinon", "js/models/uploads", "js/views/uploads", "js/models/chapter",
|
||||
/*
|
||||
* decaffeinate suggestions:
|
||||
* DS102: Remove unnecessary code created because of implicit returns
|
||||
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
|
||||
*/
|
||||
define(["sinon", "js/models/uploads", "js/views/uploads", "js/models/chapter",
|
||||
"edx-ui-toolkit/js/utils/spec-helpers/ajax-helpers", "js/spec_helpers/modal_helpers"],
|
||||
(sinon, FileUpload, UploadDialog, Chapter, AjaxHelpers, modal_helpers) ->
|
||||
(sinon, FileUpload, UploadDialog, Chapter, AjaxHelpers, modal_helpers) =>
|
||||
|
||||
describe "UploadDialog", ->
|
||||
tpl = readFixtures("upload-dialog.underscore")
|
||||
describe("UploadDialog", function() {
|
||||
const tpl = readFixtures("upload-dialog.underscore");
|
||||
|
||||
beforeEach ->
|
||||
modal_helpers.installModalTemplates()
|
||||
appendSetFixtures($("<script>", {id: "upload-dialog-tpl", type: "text/template"}).text(tpl))
|
||||
CMS.URL.UPLOAD_ASSET = "/upload"
|
||||
@model = new FileUpload(
|
||||
beforeEach(function() {
|
||||
let dialogResponse;
|
||||
modal_helpers.installModalTemplates();
|
||||
appendSetFixtures($("<script>", {id: "upload-dialog-tpl", type: "text/template"}).text(tpl));
|
||||
CMS.URL.UPLOAD_ASSET = "/upload";
|
||||
this.model = new FileUpload({
|
||||
mimeTypes: ['application/pdf']
|
||||
)
|
||||
@dialogResponse = dialogResponse = []
|
||||
@mockFiles = []
|
||||
});
|
||||
this.dialogResponse = (dialogResponse = []);
|
||||
return this.mockFiles = [];});
|
||||
|
||||
afterEach ->
|
||||
delete CMS.URL.UPLOAD_ASSET
|
||||
modal_helpers.cancelModalIfShowing()
|
||||
afterEach(function() {
|
||||
delete CMS.URL.UPLOAD_ASSET;
|
||||
return modal_helpers.cancelModalIfShowing();
|
||||
});
|
||||
|
||||
createTestView = (test) ->
|
||||
view = new UploadDialog(
|
||||
const createTestView = function(test) {
|
||||
const view = new UploadDialog({
|
||||
model: test.model,
|
||||
url: CMS.URL.UPLOAD_ASSET,
|
||||
onSuccess: (response) =>
|
||||
test.dialogResponse.push(response.response)
|
||||
)
|
||||
spyOn(view, 'remove').and.callThrough()
|
||||
onSuccess: response => {
|
||||
return test.dialogResponse.push(response.response);
|
||||
}
|
||||
});
|
||||
spyOn(view, 'remove').and.callThrough();
|
||||
|
||||
# create mock file input, so that we aren't subject to browser restrictions
|
||||
mockFileInput = jasmine.createSpy('mockFileInput')
|
||||
mockFileInput.files = test.mockFiles
|
||||
jqMockFileInput = jasmine.createSpyObj('jqMockFileInput', ['get', 'replaceWith'])
|
||||
jqMockFileInput.get.and.returnValue(mockFileInput)
|
||||
originalView$ = view.$
|
||||
spyOn(view, "$").and.callFake (selector) ->
|
||||
if selector == "input[type=file]"
|
||||
jqMockFileInput
|
||||
else
|
||||
originalView$.apply(this, arguments)
|
||||
@lastView = view
|
||||
// create mock file input, so that we aren't subject to browser restrictions
|
||||
const mockFileInput = jasmine.createSpy('mockFileInput');
|
||||
mockFileInput.files = test.mockFiles;
|
||||
const jqMockFileInput = jasmine.createSpyObj('jqMockFileInput', ['get', 'replaceWith']);
|
||||
jqMockFileInput.get.and.returnValue(mockFileInput);
|
||||
const originalView$ = view.$;
|
||||
spyOn(view, "$").and.callFake(function(selector) {
|
||||
if (selector === "input[type=file]") {
|
||||
return jqMockFileInput;
|
||||
} else {
|
||||
return originalView$.apply(this, arguments);
|
||||
}
|
||||
});
|
||||
return this.lastView = view;
|
||||
};
|
||||
|
||||
describe "Basic", ->
|
||||
it "should render without a file selected", ->
|
||||
view = createTestView(this)
|
||||
view.render()
|
||||
expect(view.$el).toContainElement("input[type=file]")
|
||||
expect(view.$(".action-upload")).toHaveClass("disabled")
|
||||
describe("Basic", function() {
|
||||
it("should render without a file selected", function() {
|
||||
const view = createTestView(this);
|
||||
view.render();
|
||||
expect(view.$el).toContainElement("input[type=file]");
|
||||
return expect(view.$(".action-upload")).toHaveClass("disabled");
|
||||
});
|
||||
|
||||
it "should render with a PDF selected", ->
|
||||
view = createTestView(this)
|
||||
file = {name: "fake.pdf", "type": "application/pdf"}
|
||||
@mockFiles.push(file)
|
||||
@model.set("selectedFile", file)
|
||||
view.render()
|
||||
expect(view.$el).toContainElement("input[type=file]")
|
||||
expect(view.$el).not.toContainElement("#upload_error")
|
||||
expect(view.$(".action-upload")).not.toHaveClass("disabled")
|
||||
it("should render with a PDF selected", function() {
|
||||
const view = createTestView(this);
|
||||
const file = {name: "fake.pdf", "type": "application/pdf"};
|
||||
this.mockFiles.push(file);
|
||||
this.model.set("selectedFile", file);
|
||||
view.render();
|
||||
expect(view.$el).toContainElement("input[type=file]");
|
||||
expect(view.$el).not.toContainElement("#upload_error");
|
||||
return expect(view.$(".action-upload")).not.toHaveClass("disabled");
|
||||
});
|
||||
|
||||
it "should render an error with an invalid file type selected", ->
|
||||
view = createTestView(this)
|
||||
file = {name: "fake.png", "type": "image/png"}
|
||||
@mockFiles.push(file)
|
||||
@model.set("selectedFile", file)
|
||||
view.render()
|
||||
expect(view.$el).toContainElement("input[type=file]")
|
||||
expect(view.$el).toContainElement("#upload_error")
|
||||
expect(view.$(".action-upload")).toHaveClass("disabled")
|
||||
it("should render an error with an invalid file type selected", function() {
|
||||
const view = createTestView(this);
|
||||
const file = {name: "fake.png", "type": "image/png"};
|
||||
this.mockFiles.push(file);
|
||||
this.model.set("selectedFile", file);
|
||||
view.render();
|
||||
expect(view.$el).toContainElement("input[type=file]");
|
||||
expect(view.$el).toContainElement("#upload_error");
|
||||
return expect(view.$(".action-upload")).toHaveClass("disabled");
|
||||
});
|
||||
|
||||
it "should render an error with an invalid file type after a correct file type selected", ->
|
||||
view = createTestView(this)
|
||||
correctFile = {name: "fake.pdf", "type": "application/pdf"}
|
||||
inCorrectFile = {name: "fake.png", "type": "image/png"}
|
||||
event = {}
|
||||
view.render()
|
||||
return it("should render an error with an invalid file type after a correct file type selected", function() {
|
||||
const view = createTestView(this);
|
||||
const correctFile = {name: "fake.pdf", "type": "application/pdf"};
|
||||
const inCorrectFile = {name: "fake.png", "type": "image/png"};
|
||||
const event = {};
|
||||
view.render();
|
||||
|
||||
event.target = {"files": [correctFile]}
|
||||
view.selectFile(event)
|
||||
expect(view.$el).toContainElement("input[type=file]")
|
||||
expect(view.$el).not.toContainElement("#upload_error")
|
||||
expect(view.$(".action-upload")).not.toHaveClass("disabled")
|
||||
event.target = {"files": [correctFile]};
|
||||
view.selectFile(event);
|
||||
expect(view.$el).toContainElement("input[type=file]");
|
||||
expect(view.$el).not.toContainElement("#upload_error");
|
||||
expect(view.$(".action-upload")).not.toHaveClass("disabled");
|
||||
|
||||
realMethod = @model.set
|
||||
spyOn(@model, "set").and.callFake (data) ->
|
||||
if data.selectedFile != undefined
|
||||
this.attributes.selectedFile = data.selectedFile
|
||||
this.changed = {}
|
||||
else
|
||||
realMethod.apply(this, arguments)
|
||||
const realMethod = this.model.set;
|
||||
spyOn(this.model, "set").and.callFake(function(data) {
|
||||
if (data.selectedFile !== undefined) {
|
||||
this.attributes.selectedFile = data.selectedFile;
|
||||
return this.changed = {};
|
||||
} else {
|
||||
return realMethod.apply(this, arguments);
|
||||
}
|
||||
});
|
||||
|
||||
event.target = {"files": [inCorrectFile]}
|
||||
view.selectFile(event)
|
||||
expect(view.$el).toContainElement("input[type=file]")
|
||||
expect(view.$el).toContainElement("#upload_error")
|
||||
expect(view.$(".action-upload")).toHaveClass("disabled")
|
||||
event.target = {"files": [inCorrectFile]};
|
||||
view.selectFile(event);
|
||||
expect(view.$el).toContainElement("input[type=file]");
|
||||
expect(view.$el).toContainElement("#upload_error");
|
||||
return expect(view.$(".action-upload")).toHaveClass("disabled");
|
||||
});
|
||||
});
|
||||
|
||||
describe "Uploads", ->
|
||||
beforeEach ->
|
||||
@clock = sinon.useFakeTimers()
|
||||
return describe("Uploads", function() {
|
||||
beforeEach(function() {
|
||||
return this.clock = sinon.useFakeTimers();
|
||||
});
|
||||
|
||||
afterEach ->
|
||||
modal_helpers.cancelModalIfShowing()
|
||||
@clock.restore()
|
||||
afterEach(function() {
|
||||
modal_helpers.cancelModalIfShowing();
|
||||
return this.clock.restore();
|
||||
});
|
||||
|
||||
it "can upload correctly", ->
|
||||
requests = AjaxHelpers.requests(this);
|
||||
view = createTestView(this)
|
||||
view.render()
|
||||
view.upload()
|
||||
expect(@model.get("uploading")).toBeTruthy()
|
||||
AjaxHelpers.expectRequest(requests, "POST", "/upload")
|
||||
AjaxHelpers.respondWithJson(requests, { response: "dummy_response"})
|
||||
expect(@model.get("uploading")).toBeFalsy()
|
||||
expect(@model.get("finished")).toBeTruthy()
|
||||
expect(@dialogResponse.pop()).toEqual("dummy_response")
|
||||
it("can upload correctly", function() {
|
||||
const requests = AjaxHelpers.requests(this);
|
||||
const view = createTestView(this);
|
||||
view.render();
|
||||
view.upload();
|
||||
expect(this.model.get("uploading")).toBeTruthy();
|
||||
AjaxHelpers.expectRequest(requests, "POST", "/upload");
|
||||
AjaxHelpers.respondWithJson(requests, { response: "dummy_response"});
|
||||
expect(this.model.get("uploading")).toBeFalsy();
|
||||
expect(this.model.get("finished")).toBeTruthy();
|
||||
return expect(this.dialogResponse.pop()).toEqual("dummy_response");
|
||||
});
|
||||
|
||||
it "can handle upload errors", ->
|
||||
requests = AjaxHelpers.requests(this);
|
||||
view = createTestView(this)
|
||||
view.render()
|
||||
view.upload()
|
||||
AjaxHelpers.respondWithError(requests)
|
||||
expect(@model.get("title")).toMatch(/error/)
|
||||
expect(view.remove).not.toHaveBeenCalled()
|
||||
it("can handle upload errors", function() {
|
||||
const requests = AjaxHelpers.requests(this);
|
||||
const view = createTestView(this);
|
||||
view.render();
|
||||
view.upload();
|
||||
AjaxHelpers.respondWithError(requests);
|
||||
expect(this.model.get("title")).toMatch(/error/);
|
||||
return expect(view.remove).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it "removes itself after two seconds on successful upload", ->
|
||||
requests = AjaxHelpers.requests(this);
|
||||
view = createTestView(this)
|
||||
view.render()
|
||||
view.upload()
|
||||
AjaxHelpers.respondWithJson(requests, { response: "dummy_response"})
|
||||
return it("removes itself after two seconds on successful upload", function() {
|
||||
const requests = AjaxHelpers.requests(this);
|
||||
const view = createTestView(this);
|
||||
view.render();
|
||||
view.upload();
|
||||
AjaxHelpers.respondWithJson(requests, { response: "dummy_response"});
|
||||
expect(modal_helpers.isShowingModal(view)).toBeTruthy();
|
||||
@clock.tick(2001)
|
||||
expect(modal_helpers.isShowingModal(view)).toBeFalsy();
|
||||
this.clock.tick(2001);
|
||||
return expect(modal_helpers.isShowingModal(view)).toBeFalsy();
|
||||
});
|
||||
});
|
||||
})
|
||||
);
|
||||
|
||||
@@ -1,21 +1,55 @@
|
||||
class MinimaxProblemDisplay extends XProblemDisplay
|
||||
/*
|
||||
* 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={}) ->
|
||||
constructor(state, submission, evaluation, container, submissionField, parameters) {
|
||||
|
||||
super(@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: () ->
|
||||
render() {}
|
||||
|
||||
createSubmission: () ->
|
||||
createSubmission() {
|
||||
|
||||
@newSubmission = {}
|
||||
this.newSubmission = {};
|
||||
|
||||
if @submission?
|
||||
for id, value of @submission
|
||||
@newSubmission[id] = value
|
||||
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 @newSubmission
|
||||
getCurrentSubmission() {
|
||||
return this.newSubmission;
|
||||
}
|
||||
}
|
||||
|
||||
root = exports ? this
|
||||
root.TestProblemDisplay = TestProblemDisplay
|
||||
const root = typeof exports !== 'undefined' && exports !== null ? exports : this;
|
||||
root.TestProblemDisplay = TestProblemDisplay;
|
||||
|
||||
@@ -1,14 +1,33 @@
|
||||
class TestProblemGenerator extends XProblemGenerator
|
||||
/*
|
||||
* 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 = {}) ->
|
||||
constructor(seed, parameters) {
|
||||
|
||||
super(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: () ->
|
||||
generate() {
|
||||
|
||||
@problemState.value = @parameters.value
|
||||
this.problemState.value = this.parameters.value;
|
||||
|
||||
return @problemState
|
||||
return this.problemState;
|
||||
}
|
||||
}
|
||||
|
||||
root = exports ? this
|
||||
root.generatorClass = TestProblemGenerator
|
||||
const root = typeof exports !== 'undefined' && exports !== null ? exports : this;
|
||||
root.generatorClass = TestProblemGenerator;
|
||||
|
||||
@@ -1,27 +1,54 @@
|
||||
class TestProblemGrader extends XProblemGrader
|
||||
/*
|
||||
* 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={}) ->
|
||||
constructor(submission, problemState, parameters) {
|
||||
|
||||
super(@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: () ->
|
||||
solve() {
|
||||
|
||||
@solution = {0: @problemState.value}
|
||||
return this.solution = {0: this.problemState.value};
|
||||
}
|
||||
|
||||
grade: () ->
|
||||
grade() {
|
||||
|
||||
if not @solution?
|
||||
@solve()
|
||||
if ((this.solution == null)) {
|
||||
this.solve();
|
||||
}
|
||||
|
||||
allCorrect = true
|
||||
let allCorrect = true;
|
||||
|
||||
for id, value of @solution
|
||||
valueCorrect = if @submission? then (value == @submission[id]) else false
|
||||
@evaluation[id] = valueCorrect
|
||||
if not valueCorrect
|
||||
allCorrect = false
|
||||
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
|
||||
return allCorrect;
|
||||
}
|
||||
}
|
||||
|
||||
root = exports ? this
|
||||
root.graderClass = TestProblemGrader
|
||||
const root = typeof exports !== 'undefined' && exports !== null ? exports : this;
|
||||
root.graderClass = TestProblemGrader;
|
||||
|
||||
@@ -1,47 +1,79 @@
|
||||
class XProblemGenerator
|
||||
/*
|
||||
* decaffeinate suggestions:
|
||||
* 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 XProblemGenerator {
|
||||
|
||||
constructor: (seed, @parameters={}) ->
|
||||
constructor(seed, parameters) {
|
||||
|
||||
@random = new MersenneTwister(seed)
|
||||
if (parameters == null) { parameters = {}; }
|
||||
this.parameters = parameters;
|
||||
this.random = new MersenneTwister(seed);
|
||||
|
||||
@problemState = {}
|
||||
this.problemState = {};
|
||||
}
|
||||
|
||||
generate: () ->
|
||||
generate() {
|
||||
|
||||
console.error("Abstract method called: XProblemGenerator.generate")
|
||||
return console.error("Abstract method called: XProblemGenerator.generate");
|
||||
}
|
||||
}
|
||||
|
||||
class XProblemDisplay
|
||||
class XProblemDisplay {
|
||||
|
||||
constructor: (@state, @submission, @evaluation, @container, @submissionField, @parameters={}) ->
|
||||
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: () ->
|
||||
render() {
|
||||
|
||||
console.error("Abstract method called: XProblemDisplay.render")
|
||||
return console.error("Abstract method called: XProblemDisplay.render");
|
||||
}
|
||||
|
||||
updateSubmission: () ->
|
||||
updateSubmission() {
|
||||
|
||||
@submissionField.val(JSON.stringify(@getCurrentSubmission()))
|
||||
return this.submissionField.val(JSON.stringify(this.getCurrentSubmission()));
|
||||
}
|
||||
|
||||
getCurrentSubmission: () ->
|
||||
console.error("Abstract method called: XProblemDisplay.getCurrentSubmission")
|
||||
getCurrentSubmission() {
|
||||
return console.error("Abstract method called: XProblemDisplay.getCurrentSubmission");
|
||||
}
|
||||
}
|
||||
|
||||
class XProblemGrader
|
||||
class XProblemGrader {
|
||||
|
||||
constructor: (@submission, @problemState, @parameters={}) ->
|
||||
constructor(submission, problemState, parameters) {
|
||||
|
||||
@solution = null
|
||||
@evaluation = {}
|
||||
this.submission = submission;
|
||||
this.problemState = problemState;
|
||||
if (parameters == null) { parameters = {}; }
|
||||
this.parameters = parameters;
|
||||
this.solution = null;
|
||||
this.evaluation = {};
|
||||
}
|
||||
|
||||
solve: () ->
|
||||
solve() {
|
||||
|
||||
console.error("Abstract method called: XProblemGrader.solve")
|
||||
return console.error("Abstract method called: XProblemGrader.solve");
|
||||
}
|
||||
|
||||
grade: () ->
|
||||
grade() {
|
||||
|
||||
console.error("Abstract method called: XProblemGrader.grade")
|
||||
return console.error("Abstract method called: XProblemGrader.grade");
|
||||
}
|
||||
}
|
||||
|
||||
root = exports ? this
|
||||
const root = typeof exports !== 'undefined' && exports !== null ? exports : this;
|
||||
|
||||
root.XProblemGenerator = XProblemGenerator
|
||||
root.XProblemDisplay = XProblemDisplay
|
||||
root.XProblemGrader = XProblemGrader
|
||||
root.XProblemGenerator = XProblemGenerator;
|
||||
root.XProblemDisplay = XProblemDisplay;
|
||||
root.XProblemGrader = XProblemGrader;
|
||||
|
||||
@@ -1,9 +1,15 @@
|
||||
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)
|
||||
/*
|
||||
* decaffeinate suggestions:
|
||||
* DS102: Remove unnecessary code created because of implicit returns
|
||||
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
|
||||
*/
|
||||
describe('Annotatable', function() {
|
||||
beforeEach(() => loadFixtures('annotatable.html'));
|
||||
return describe('constructor', function() {
|
||||
const el = $('.xblock-student_view.xmodule_AnnotatableModule');
|
||||
beforeEach(function() {
|
||||
return this.annotatable = new Annotatable(el);
|
||||
});
|
||||
return it('works', () => expect(1).toBe(1));
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,455 +1,583 @@
|
||||
describe 'Problem', ->
|
||||
problem_content_default = readFixtures('problem_content.html')
|
||||
/*
|
||||
* decaffeinate suggestions:
|
||||
* DS101: Remove unnecessary use of Array.from
|
||||
* DS102: Remove unnecessary code created because of implicit returns
|
||||
* 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 ->
|
||||
# Stub MathJax
|
||||
window.MathJax =
|
||||
Hub: jasmine.createSpyObj('MathJax.Hub', ['getAllJax', 'Queue'])
|
||||
beforeEach(function() {
|
||||
// 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'
|
||||
};
|
||||
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()
|
||||
// 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'
|
||||
loadFixtures('problem.html');
|
||||
|
||||
spyOn Logger, 'log'
|
||||
spyOn($.fn, 'load').and.callFake (url, callback) ->
|
||||
$(@).html readFixtures('problem_content.html')
|
||||
callback()
|
||||
spyOn(Logger, 'log');
|
||||
return spyOn($.fn, 'load').and.callFake(function(url, callback) {
|
||||
$(this).html(readFixtures('problem_content.html'));
|
||||
return callback();
|
||||
});
|
||||
});
|
||||
|
||||
describe 'constructor', ->
|
||||
describe('constructor', function() {
|
||||
|
||||
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 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>\
|
||||
`)
|
||||
);
|
||||
return expect(this.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'
|
||||
return it('set the element from loadFixtures', function() {
|
||||
this.problem1 = new Problem($('.xblock-student_view'));
|
||||
return expect(this.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'))
|
||||
describe('bind', function() {
|
||||
beforeEach(function() {
|
||||
spyOn(window, 'update_schematics');
|
||||
MathJax.Hub.getAllJax.and.returnValue([this.stubbedJax]);
|
||||
return this.problem = new Problem($('.xblock-student_view'));
|
||||
});
|
||||
|
||||
it 'set mathjax typeset', ->
|
||||
expect(MathJax.Hub.Queue).toHaveBeenCalled()
|
||||
it('set mathjax typeset', () => expect(MathJax.Hub.Queue).toHaveBeenCalled());
|
||||
|
||||
it 'update schematics', ->
|
||||
expect(window.update_schematics).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 answer refresh on button click', function() {
|
||||
return expect($('div.action button')).toHandleWith('click', this.problem.refreshAnswers);
|
||||
});
|
||||
|
||||
it 'bind the submit button', ->
|
||||
expect($('.action .submit')).toHandleWith 'click', @problem.submit_fd
|
||||
it('bind the submit button', function() {
|
||||
return expect($('.action .submit')).toHandleWith('click', this.problem.submit_fd);
|
||||
});
|
||||
|
||||
it 'bind the reset button', ->
|
||||
expect($('div.action button.reset')).toHandleWith 'click', @problem.reset
|
||||
it('bind the reset button', function() {
|
||||
return expect($('div.action button.reset')).toHandleWith('click', this.problem.reset);
|
||||
});
|
||||
|
||||
it 'bind the show button', ->
|
||||
expect($('.action .show')).toHandleWith 'click', @problem.show
|
||||
it('bind the show button', function() {
|
||||
return expect($('.action .show')).toHandleWith('click', this.problem.show);
|
||||
});
|
||||
|
||||
it 'bind the save button', ->
|
||||
expect($('div.action button.save')).toHandleWith 'click', @problem.save
|
||||
it('bind the save button', function() {
|
||||
return expect($('div.action button.save')).toHandleWith('click', this.problem.save);
|
||||
});
|
||||
|
||||
it 'bind the math input', ->
|
||||
expect($('input.math')).toHandleWith 'keyup', @problem.refreshMath
|
||||
return it('bind the math input', function() {
|
||||
return expect($('input.math')).toHandleWith('keyup', this.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')
|
||||
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', ->
|
||||
expect($('.action .submit')).toHandleWith 'click', @problem.submit_fd
|
||||
it('bind the submit button', function() {
|
||||
return expect($('.action .submit')).toHandleWith('click', this.problem.submit_fd);
|
||||
});
|
||||
|
||||
it 'bind the show button', ->
|
||||
expect($('div.action button.show')).toHandleWith 'click', @problem.show
|
||||
return it('bind the show button', function() {
|
||||
return expect($('div.action button.show')).toHandleWith('click', this.problem.show);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
describe 'renderProgressState', ->
|
||||
beforeEach ->
|
||||
@problem = new Problem($('.xblock-student_view'))
|
||||
describe('renderProgressState', function() {
|
||||
beforeEach(function() {
|
||||
return this.problem = new Problem($('.xblock-student_view'));
|
||||
});
|
||||
|
||||
testProgessData = (problem, score, total_possible, attempts, graded, expected_progress_after_render) ->
|
||||
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
|
||||
problem.el.data('graded', graded);
|
||||
expect(problem.$('.problem-progress').html()).toEqual("");
|
||||
problem.renderProgressState();
|
||||
return 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)")
|
||||
describe('with a status of "none"', function() {
|
||||
it('reports the number of points possible and graded', function() {
|
||||
return testProgessData(this.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('displays the number of points possible when rendering happens with the content', function() {
|
||||
return testProgessData(this.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('reports the number of points possible and ungraded', function() {
|
||||
return testProgessData(this.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', function() {
|
||||
return 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', ->
|
||||
testProgessData(@problem, 0, 0, 0, "True", "0 points possible (ungraded)")
|
||||
it('displays ungraded if number of points possible is 0, even if graded value is True', function() {
|
||||
return testProgessData(this.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 status none and >0 attempts', function() {
|
||||
return testProgessData(this.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)")
|
||||
return it('reports the correct score with >1 weight, status none, and >0 attempts', function() {
|
||||
return testProgessData(this.problem, 0, 2, 2, "True", "0/2 points (graded)");
|
||||
});
|
||||
});
|
||||
|
||||
describe 'with any other valid status', ->
|
||||
describe('with any other valid status', function() {
|
||||
|
||||
it 'reports the current score', ->
|
||||
testProgessData(@problem, 1, 1, 1, "True", "1/1 point (graded)")
|
||||
it('reports the current score', function() {
|
||||
return testProgessData(this.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('shows current score when rendering happens with the content', function() {
|
||||
return testProgessData(this.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)")
|
||||
return it('reports the current score even if problem is ungraded', function() {
|
||||
return 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', ->
|
||||
testProgessData(@problem, 0, 0, 1, "False", "0 points possible (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() {
|
||||
return testProgessData(this.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)")
|
||||
return describe('with a score of null (show_correctness == false)', function() {
|
||||
it('reports the number of points possible and graded, results hidden', function() {
|
||||
return 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', ->
|
||||
testProgessData(@problem, null, 2, 0, "True", "2 points possible (graded, results hidden)")
|
||||
it('reports the number of points possible (plural) and graded, results hidden', function() {
|
||||
return testProgessData(this.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('reports the number of points possible and ungraded, results hidden', function() {
|
||||
return 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', ->
|
||||
testProgessData(@problem, null, 0, 0, "False", "0 points possible (ungraded, results hidden)")
|
||||
it('displays ungraded if number of points possible is 0, results hidden', function() {
|
||||
return 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', ->
|
||||
testProgessData(@problem, null, 0, 0, "True", "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() {
|
||||
return 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', ->
|
||||
testProgessData(@problem, null, 1, 1, "True", "1 point possible (graded, results hidden)")
|
||||
it('reports the correct score with status none and >0 attempts, results hidden', function() {
|
||||
return 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', ->
|
||||
testProgessData(@problem, null, 2, 2, "True", "2 points possible (graded, results hidden)")
|
||||
return it('reports the correct score with >1 weight, status none, and >0 attempts, results hidden', function() {
|
||||
return testProgessData(this.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('render', function() {
|
||||
beforeEach(function() {
|
||||
this.problem = new Problem($('.xblock-student_view'));
|
||||
this.bind = this.problem.bind;
|
||||
return spyOn(this.problem, 'bind');
|
||||
});
|
||||
|
||||
describe 'with content given', ->
|
||||
beforeEach ->
|
||||
@problem.render 'Hello World'
|
||||
describe('with content given', function() {
|
||||
beforeEach(function() {
|
||||
return this.problem.render('Hello World');
|
||||
});
|
||||
|
||||
it 'render the content', ->
|
||||
expect(@problem.el.html()).toEqual 'Hello World'
|
||||
it('render the content', function() {
|
||||
return expect(this.problem.el.html()).toEqual('Hello World');
|
||||
});
|
||||
|
||||
it 're-bind the content', ->
|
||||
expect(@problem.bind).toHaveBeenCalled()
|
||||
return it('re-bind the content', function() {
|
||||
return expect(this.problem.bind).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe 'with no content given', ->
|
||||
beforeEach ->
|
||||
spyOn($, 'postWithPrefix').and.callFake (url, callback) ->
|
||||
callback html: "Hello World"
|
||||
@problem.render()
|
||||
return describe('with no content given', function() {
|
||||
beforeEach(function() {
|
||||
spyOn($, 'postWithPrefix').and.callFake((url, callback) => callback({html: "Hello World"}));
|
||||
return this.problem.render();
|
||||
});
|
||||
|
||||
it 'load the content via ajax', ->
|
||||
expect(@problem.el.html()).toEqual 'Hello World'
|
||||
it('load the content via ajax', function() {
|
||||
return expect(this.problem.el.html()).toEqual('Hello World');
|
||||
});
|
||||
|
||||
it 're-bind the content', ->
|
||||
expect(@problem.bind).toHaveBeenCalled()
|
||||
return it('re-bind the content', function() {
|
||||
return expect(this.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')
|
||||
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'));
|
||||
return spyOn(this.problem, 'submit');
|
||||
});
|
||||
|
||||
it 'submit method is called if input of type file is not in problem', ->
|
||||
@problem.submit_fd()
|
||||
expect(@problem.submit).toHaveBeenCalled()
|
||||
return it('submit method is called if input of type file is not in problem', function() {
|
||||
this.problem.submit_fd();
|
||||
return expect(this.problem.submit).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe 'submit', ->
|
||||
beforeEach ->
|
||||
@problem = new Problem($('.xblock-student_view'))
|
||||
@problem.answers = 'foo=1&bar=2'
|
||||
describe('submit', function() {
|
||||
beforeEach(function() {
|
||||
this.problem = new Problem($('.xblock-student_view'));
|
||||
return this.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_check event', function() {
|
||||
spyOn($, 'postWithPrefix').and.callFake(function(url, answers, callback) {
|
||||
let promise;
|
||||
return promise = {
|
||||
always(callable) { return callable(); },
|
||||
done(callable) { return callable(); }
|
||||
};
|
||||
});
|
||||
this.problem.submit();
|
||||
return 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'
|
||||
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) -> callable()
|
||||
done: (callable) -> callable()
|
||||
@problem.submit()
|
||||
expect(Logger.log).toHaveBeenCalledWith 'problem_graded', ['foo=1&bar=2', 'mock grader response'], @problem.id
|
||||
};
|
||||
callback(response);
|
||||
return promise = {
|
||||
always(callable) { return callable(); },
|
||||
done(callable) { return callable(); }
|
||||
};
|
||||
});
|
||||
this.problem.submit();
|
||||
return expect(Logger.log).toHaveBeenCalledWith('problem_graded', ['foo=1&bar=2', 'mock grader response'], this.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)
|
||||
it('submit the answer for submit', function() {
|
||||
spyOn($, 'postWithPrefix').and.callFake(function(url, answers, callback) {
|
||||
let promise;
|
||||
return promise = {
|
||||
always(callable) { return callable(); },
|
||||
done(callable) { return callable(); }
|
||||
};
|
||||
});
|
||||
this.problem.submit();
|
||||
return 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 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});
|
||||
return promise = {
|
||||
always(callable) { return callable(); },
|
||||
done(callable) { return callable(); }
|
||||
};
|
||||
});
|
||||
this.problem.submit();
|
||||
expect(this.problem.el).toHaveHtml(contents);
|
||||
return 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']
|
||||
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});
|
||||
return promise = {
|
||||
always(callable) { return callable(); },
|
||||
done(callable) { return callable(); }
|
||||
};
|
||||
});
|
||||
this.problem.submit();
|
||||
expect(this.problem.el).toHaveHtml(contents);
|
||||
return 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'
|
||||
return 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
|
||||
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')
|
||||
});
|
||||
return promise = {
|
||||
always(callable) { return callable(); },
|
||||
done(callable) { return callable(); }
|
||||
};
|
||||
});
|
||||
// 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');
|
||||
return expect(this.problem.submitButton).not.toHaveAttr('disabled');
|
||||
});
|
||||
});
|
||||
|
||||
describe 'submit button on problems', ->
|
||||
describe('submit button on problems', function() {
|
||||
|
||||
beforeEach ->
|
||||
@problem = new Problem($('.xblock-student_view'))
|
||||
@submitDisabled = (disabled) =>
|
||||
if disabled
|
||||
expect(@problem.submitButton).toHaveAttr('disabled')
|
||||
else
|
||||
expect(@problem.submitButton).not.toHaveAttr('disabled')
|
||||
beforeEach(function() {
|
||||
this.problem = new Problem($('.xblock-student_view'));
|
||||
return this.submitDisabled = disabled => {
|
||||
if (disabled) {
|
||||
return expect(this.problem.submitButton).toHaveAttr('disabled');
|
||||
} else {
|
||||
return 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', ->
|
||||
$('#input_example_1').val('test').trigger('input')
|
||||
@submitDisabled false
|
||||
$('#input_example_1').val('').trigger('input')
|
||||
@submitDisabled true
|
||||
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');
|
||||
return this.submitDisabled(true);
|
||||
})
|
||||
);
|
||||
|
||||
describe 'some advanced tests for submit button', ->
|
||||
radioButtonProblemHtml = readFixtures('radiobutton_problem.html')
|
||||
checkboxProblemHtml = readFixtures('checkbox_problem.html')
|
||||
return 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', ->
|
||||
$('#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 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();
|
||||
return this.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 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');
|
||||
return this.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 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');
|
||||
return this.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 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');
|
||||
return this.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
|
||||
return 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);
|
||||
return this.submitDisabled(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe 'reset', ->
|
||||
beforeEach ->
|
||||
@problem = new Problem($('.xblock-student_view'))
|
||||
describe('reset', function() {
|
||||
beforeEach(function() {
|
||||
return this.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('log the problem_reset event', function() {
|
||||
spyOn($, 'postWithPrefix').and.callFake(function(url, answers, callback) {
|
||||
let promise;
|
||||
return promise =
|
||||
{always(callable) { return callable(); }};
|
||||
});
|
||||
this.problem.answers = 'foo=1&bar=2';
|
||||
this.problem.reset();
|
||||
return expect(Logger.log).toHaveBeenCalledWith('problem_reset', 'foo=1&bar=2');
|
||||
});
|
||||
|
||||
it 'POST to the problem reset page', ->
|
||||
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('POST to the problem reset page', function() {
|
||||
spyOn($, 'postWithPrefix').and.callFake(function(url, answers, callback) {
|
||||
let promise;
|
||||
return promise =
|
||||
{always(callable) { return callable(); }};
|
||||
});
|
||||
this.problem.reset();
|
||||
return 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('render the returned content', function() {
|
||||
spyOn($, 'postWithPrefix').and.callFake(function(url, answers, callback) {
|
||||
let promise;
|
||||
callback({html: "Reset", success: true});
|
||||
return promise =
|
||||
{always(callable) { return callable(); }};
|
||||
});
|
||||
this.problem.reset();
|
||||
return expect(this.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('sends a message to the window SR element', function() {
|
||||
spyOn($, 'postWithPrefix').and.callFake(function(url, answers, callback) {
|
||||
let promise;
|
||||
callback({html: "Reset", success: true});
|
||||
return promise =
|
||||
{always(callable) { return callable(); }};
|
||||
});
|
||||
this.problem.reset();
|
||||
return 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('shows a notification on error', function() {
|
||||
spyOn($, 'postWithPrefix').and.callFake(function(url, answers, callback) {
|
||||
let promise;
|
||||
callback({msg: "Error on reset.", success: false});
|
||||
return promise =
|
||||
{always(callable) { return callable(); }};
|
||||
});
|
||||
this.problem.reset();
|
||||
return 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')
|
||||
return 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});
|
||||
return promise =
|
||||
{always(callable) { return callable(); }};
|
||||
});
|
||||
// Submit should be disabled
|
||||
expect(this.problem.submitButton).toHaveAttr('disabled');
|
||||
this.problem.reset();
|
||||
// Submit should remain disabled
|
||||
expect(self.problem.submitButton).toHaveAttr('disabled');
|
||||
return 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('show', function() {
|
||||
beforeEach(function() {
|
||||
this.problem = new Problem($('.xblock-student_view'));
|
||||
return this.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')
|
||||
return describe('when the answer has not yet shown', function() {
|
||||
beforeEach(function() {
|
||||
return expect(this.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('log the problem_show event', function() {
|
||||
this.problem.show();
|
||||
return 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('fetch the answers', function() {
|
||||
spyOn($, 'postWithPrefix');
|
||||
this.problem.show();
|
||||
return 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('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');
|
||||
return 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')
|
||||
it('disables the show answer button', function() {
|
||||
spyOn($, 'postWithPrefix').and.callFake((url, callback) => callback({answers: {}}));
|
||||
this.problem.show();
|
||||
return expect(this.problem.el.find('.show').attr('disabled')).toEqual('disabled');
|
||||
});
|
||||
|
||||
describe 'radio text question', ->
|
||||
radio_text_xml='''
|
||||
describe('radio text question', function() {
|
||||
const radio_text_xml=`\
|
||||
<section class="problem">
|
||||
<div><p></p><span><section id="choicetextinput_1_2_1" class="choicetextinput">
|
||||
|
||||
@@ -474,373 +602,454 @@ describe 'Problem', ->
|
||||
<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)
|
||||
</section>\
|
||||
`;
|
||||
beforeEach(function() {
|
||||
// Append a radiotextresponse problem to the problem, so we can check it's javascript functionality
|
||||
return this.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()
|
||||
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('')
|
||||
'choicetextgroup_show_correct');
|
||||
expect($('#answer_1_2_1_choiceinput_0bc').text()).toEqual('3');
|
||||
expect($('#answer_1_2_1_choiceinput_1bc').text()).toEqual('');
|
||||
return 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')
|
||||
return 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');
|
||||
return expect($('input#1_2_1').attr('disabled')).not.toEqual('disabled');
|
||||
});
|
||||
});
|
||||
|
||||
describe 'imageinput', ->
|
||||
imageinput_html = readFixtures('imageinput.underscore')
|
||||
return describe('imageinput', function() {
|
||||
let el, height, width;
|
||||
const imageinput_html = readFixtures('imageinput.underscore');
|
||||
|
||||
DEFAULTS =
|
||||
id: '12345'
|
||||
width: '300'
|
||||
const DEFAULTS = {
|
||||
id: '12345',
|
||||
width: '300',
|
||||
height: '400'
|
||||
};
|
||||
|
||||
beforeEach ->
|
||||
@problem = new Problem($('.xblock-student_view'))
|
||||
@problem.el.prepend _.template(imageinput_html)(DEFAULTS)
|
||||
beforeEach(function() {
|
||||
this.problem = new Problem($('.xblock-student_view'));
|
||||
return this.problem.el.prepend(_.template(imageinput_html)(DEFAULTS));
|
||||
});
|
||||
|
||||
assertAnswer = (problem, data) =>
|
||||
stubRequest(data)
|
||||
problem.show()
|
||||
const 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])
|
||||
return $.each(data['answers'], (id, answer) => {
|
||||
const img = getImage(answer);
|
||||
el = $(`#inputtype_${id}`);
|
||||
return expect(img).toImageDiffEqual(el.find('canvas')[0]);
|
||||
});
|
||||
};
|
||||
|
||||
stubRequest = (data) =>
|
||||
spyOn($, 'postWithPrefix').and.callFake (url, callback) ->
|
||||
callback data
|
||||
var stubRequest = data => {
|
||||
return 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(/;/)
|
||||
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) =>
|
||||
abs = Math.abs
|
||||
points = reg.exec(rect)
|
||||
if points
|
||||
width = abs(points[3] - points[1])
|
||||
height = abs(points[4] - points[2])
|
||||
$.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]);
|
||||
|
||||
ctx.rect(points[1], points[2], width, height)
|
||||
return ctx.rect(points[1], points[2], width, height);
|
||||
}
|
||||
});
|
||||
|
||||
ctx.stroke()
|
||||
ctx.fill()
|
||||
ctx.stroke();
|
||||
return ctx.fill();
|
||||
},
|
||||
|
||||
regions: (coords) =>
|
||||
parseCoords = (coords) =>
|
||||
reg = JSON.parse(coords)
|
||||
regions: coords => {
|
||||
const parseCoords = coords => {
|
||||
reg = JSON.parse(coords);
|
||||
|
||||
if typeof reg[0][0][0] == "undefined"
|
||||
reg = [reg]
|
||||
if (typeof reg[0][0][0] === "undefined") {
|
||||
reg = [reg];
|
||||
}
|
||||
|
||||
return 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]);
|
||||
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()
|
||||
ctx.closePath();
|
||||
ctx.stroke();
|
||||
return ctx.fill();
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
canvas = document.createElement('canvas')
|
||||
canvas.width = c_width or 100
|
||||
canvas.height = c_height or 100
|
||||
const canvas = document.createElement('canvas');
|
||||
canvas.width = c_width || 100;
|
||||
canvas.height = c_height || 100;
|
||||
|
||||
if canvas.getContext
|
||||
ctx = canvas.getContext('2d')
|
||||
else
|
||||
return console.log 'Canvas is not supported.'
|
||||
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
|
||||
$.each(coords, (key, value) => {
|
||||
if ((types[key] != null) && value) { return types[key](value); }
|
||||
});
|
||||
|
||||
return canvas
|
||||
return canvas;
|
||||
};
|
||||
|
||||
it 'rectangle is drawn correctly', ->
|
||||
assertAnswer(@problem, {
|
||||
'answers':
|
||||
'12345':
|
||||
it('rectangle is drawn correctly', function() {
|
||||
return assertAnswer(this.problem, {
|
||||
'answers': {
|
||||
'12345': {
|
||||
'rectangle': '(10,10)-(30,30)',
|
||||
'regions': null
|
||||
})
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it 'region is drawn correctly', ->
|
||||
assertAnswer(@problem, {
|
||||
'answers':
|
||||
'12345':
|
||||
it('region is drawn correctly', function() {
|
||||
return assertAnswer(this.problem, {
|
||||
'answers': {
|
||||
'12345': {
|
||||
'rectangle': null,
|
||||
'regions': '[[10,10],[30,30],[70,30],[20,30]]'
|
||||
})
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it 'mixed shapes are drawn correctly', ->
|
||||
assertAnswer(@problem, {
|
||||
'answers':'12345':
|
||||
it('mixed shapes are drawn correctly', function() {
|
||||
return 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]]
|
||||
]'''
|
||||
})
|
||||
'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'
|
||||
it('multiple image inputs draw answers on separate canvases', function() {
|
||||
const data = {
|
||||
id: '67890',
|
||||
width: '400',
|
||||
height: '300'
|
||||
};
|
||||
|
||||
@problem.el.prepend _.template(imageinput_html)(data)
|
||||
assertAnswer(@problem, {
|
||||
'answers':
|
||||
'12345':
|
||||
this.problem.el.prepend(_.template(imageinput_html)(data));
|
||||
return assertAnswer(this.problem, {
|
||||
'answers': {
|
||||
'12345': {
|
||||
'rectangle': null,
|
||||
'regions': '[[10,10],[30,30],[70,30],[20,30]]'
|
||||
'67890':
|
||||
},
|
||||
'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')
|
||||
return 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();
|
||||
return 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'
|
||||
describe('save', function() {
|
||||
beforeEach(function() {
|
||||
this.problem = new Problem($('.xblock-student_view'));
|
||||
return this.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('log the problem_save event', function() {
|
||||
spyOn($, 'postWithPrefix').and.callFake(function(url, answers, callback) {
|
||||
let promise;
|
||||
return promise =
|
||||
{always(callable) { return callable(); }};
|
||||
});
|
||||
this.problem.save();
|
||||
return 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('POST to save problem', function() {
|
||||
spyOn($, 'postWithPrefix').and.callFake(function(url, answers, callback) {
|
||||
let promise;
|
||||
return promise =
|
||||
{always(callable) { return callable(); }};
|
||||
});
|
||||
this.problem.save();
|
||||
return 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 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});
|
||||
return promise =
|
||||
{always(callable) { return callable(); }};
|
||||
});
|
||||
// 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');
|
||||
return 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', ->
|
||||
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')
|
||||
return 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});
|
||||
return promise =
|
||||
{always(callable) { return callable(); }};
|
||||
});
|
||||
// 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');
|
||||
return expect(this.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)
|
||||
describe('refreshMath', function() {
|
||||
beforeEach(function() {
|
||||
this.problem = new Problem($('.xblock-student_view'));
|
||||
$('#input_example_1').val('E=mc^2');
|
||||
return this.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)]
|
||||
return it('should queue the conversion and MathML element update', function() {
|
||||
return expect(MathJax.Hub.Queue).toHaveBeenCalledWith(['Text', this.stubbedJax, 'E=mc^2'],
|
||||
[this.problem.updateMathML, this.stubbedJax, $('#input_example_1').get(0)]);
|
||||
});
|
||||
});
|
||||
|
||||
describe 'updateMathML', ->
|
||||
beforeEach ->
|
||||
@problem = new Problem($('.xblock-student_view'))
|
||||
@stubbedJax.root.toMathML.and.returnValue '<MathML>'
|
||||
describe('updateMathML', function() {
|
||||
beforeEach(function() {
|
||||
this.problem = new Problem($('.xblock-student_view'));
|
||||
return this.stubbedJax.root.toMathML.and.returnValue('<MathML>');
|
||||
});
|
||||
|
||||
describe 'when there is no exception', ->
|
||||
beforeEach ->
|
||||
@problem.updateMathML @stubbedJax, $('#input_example_1').get(0)
|
||||
describe('when there is no exception', function() {
|
||||
beforeEach(function() {
|
||||
return this.problem.updateMathML(this.stubbedJax, $('#input_example_1').get(0));
|
||||
});
|
||||
|
||||
it 'convert jax to MathML', ->
|
||||
expect($('#input_example_1_dynamath')).toHaveValue '<MathML>'
|
||||
return 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)
|
||||
return describe('when there is an exception', function() {
|
||||
beforeEach(function() {
|
||||
const error = new Error();
|
||||
error.restart = true;
|
||||
this.stubbedJax.root.toMathML.and.throwError(error);
|
||||
return this.problem.updateMathML(this.stubbedJax, $('#input_example_1').get(0));
|
||||
});
|
||||
|
||||
it 'should queue up the exception', ->
|
||||
expect(MathJax.Callback.After).toHaveBeenCalledWith [@problem.refreshMath, @stubbedJax], true
|
||||
return it('should queue up the exception', function() {
|
||||
return expect(MathJax.Callback.After).toHaveBeenCalledWith([this.problem.refreshMath, this.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
|
||||
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;
|
||||
return $('textarea.CodeMirror').get(0).CodeMirror = this.stubCodeMirror;
|
||||
});
|
||||
|
||||
it 'update each schematic', ->
|
||||
@problem.refreshAnswers()
|
||||
expect(@stubSchematic.update_value).toHaveBeenCalled()
|
||||
it('update each schematic', function() {
|
||||
this.problem.refreshAnswers();
|
||||
return expect(this.stubSchematic.update_value).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it 'update each code block', ->
|
||||
@problem.refreshAnswers()
|
||||
expect(@stubCodeMirror.save).toHaveBeenCalled()
|
||||
return it('update each code block', function() {
|
||||
this.problem.refreshAnswers();
|
||||
return expect(this.stubCodeMirror.save).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe 'multiple JsInput in single problem', ->
|
||||
jsinput_html = readFixtures('jsinput_problem.html')
|
||||
describe('multiple JsInput in single problem', function() {
|
||||
const jsinput_html = readFixtures('jsinput_problem.html');
|
||||
|
||||
beforeEach ->
|
||||
@problem = new Problem($('.xblock-student_view'))
|
||||
@problem.render(jsinput_html)
|
||||
beforeEach(function() {
|
||||
this.problem = new Problem($('.xblock-student_view'));
|
||||
return this.problem.render(jsinput_html);
|
||||
});
|
||||
|
||||
it 'submit_save_waitfor should return false', ->
|
||||
$(@problem.inputs[0]).data('waitfor', ->)
|
||||
expect(@problem.submit_save_waitfor()).toEqual(false)
|
||||
return it('submit_save_waitfor should return false', function() {
|
||||
$(this.problem.inputs[0]).data('waitfor', function() {});
|
||||
return expect(this.problem.submit_save_waitfor()).toEqual(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe 'Submitting an xqueue-graded problem', ->
|
||||
matlabinput_html = readFixtures('matlabinput_problem.html')
|
||||
describe('Submitting an xqueue-graded problem', function() {
|
||||
const 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)
|
||||
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();
|
||||
return this.problem.render(matlabinput_html);
|
||||
});
|
||||
|
||||
afterEach ->
|
||||
jasmine.clock().uninstall()
|
||||
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
|
||||
return 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);
|
||||
return num_calls += 1;
|
||||
})(time_step);
|
||||
}
|
||||
|
||||
# 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)
|
||||
// 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.")
|
||||
return 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')
|
||||
describe('codeinput problem', function() {
|
||||
const codeinputProblemHtml = readFixtures('codeinput_problem.html');
|
||||
|
||||
beforeEach ->
|
||||
spyOn($, 'postWithPrefix').and.callFake (url, callback) ->
|
||||
callback html: codeinputProblemHtml
|
||||
@problem = new Problem($('.xblock-student_view'))
|
||||
@problem.render(codeinputProblemHtml)
|
||||
beforeEach(function() {
|
||||
spyOn($, 'postWithPrefix').and.callFake((url, callback) => callback({html: codeinputProblemHtml}));
|
||||
this.problem = new Problem($('.xblock-student_view'));
|
||||
return this.problem.render(codeinputProblemHtml);
|
||||
});
|
||||
|
||||
it 'has rendered with correct a11y info', ->
|
||||
CodeMirrorTextArea = $('textarea')[1]
|
||||
CodeMirrorTextAreaId = 'cm-textarea-101'
|
||||
return 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 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 `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')
|
||||
// verify that codemirror textarea has correct `aria-describedby` attribute value
|
||||
return expect($(CodeMirrorTextArea).attr('aria-describedby')).toEqual('cm-editor-exit-message-101 status_101');
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
describe 'show answer button', ->
|
||||
return describe('show answer button', function() {
|
||||
|
||||
radioButtonProblemHtml = readFixtures('radiobutton_problem.html')
|
||||
checkboxProblemHtml = readFixtures('checkbox_problem.html')
|
||||
const radioButtonProblemHtml = readFixtures('radiobutton_problem.html');
|
||||
const checkboxProblemHtml = readFixtures('checkbox_problem.html');
|
||||
|
||||
beforeEach ->
|
||||
@problem = new Problem($('.xblock-student_view'))
|
||||
beforeEach(function() {
|
||||
this.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')
|
||||
return 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(@problem.el.find('div.choicegroup')).not.toHaveClass('choicegroup_correct')
|
||||
// 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(@problem.el.find('div.choicegroup')).not.toHaveAttr('span.status.correct')
|
||||
// verify that radio/checkbox label has no span having class '.status.correct'
|
||||
return expect(this.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 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');
|
||||
return this.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()
|
||||
return 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();
|
||||
return this.checkAssertionsAfterClickingAnotherOption();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,49 +1,59 @@
|
||||
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
|
||||
/*
|
||||
* decaffeinate suggestions:
|
||||
* DS102: Remove unnecessary code created because of implicit returns
|
||||
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
|
||||
*/
|
||||
describe('HTMLEditingDescriptor', function() {
|
||||
beforeEach(() => window.baseUrl = "/static/deadbeef");
|
||||
afterEach(() => delete window.baseUrl);
|
||||
describe('Visual HTML Editor', function() {
|
||||
beforeEach(function() {
|
||||
loadFixtures('html-edit-visual.html');
|
||||
return 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();
|
||||
return 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();
|
||||
return 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();
|
||||
return 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) { return this.content = x; },
|
||||
getContent() { return this.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')
|
||||
this.descriptor.initInstanceCallback(visualEditorStub);
|
||||
return expect(visualEditorStub.getContent()).toEqual('text /c4x/foo/bar/asset/image.jpg');
|
||||
});
|
||||
return it('Enables spellcheck', () => expect($('.html-editor iframe')[0].contentDocument.body.spellcheck).toBe(true));
|
||||
});
|
||||
return describe('Raw HTML Editor', function() {
|
||||
beforeEach(function() {
|
||||
loadFixtures('html-editor-raw.html');
|
||||
return this.descriptor = new HTMLEditingDescriptor($('.test-component'));
|
||||
});
|
||||
return it('Returns data from raw editor', function() {
|
||||
const { data } = this.descriptor.save();
|
||||
return expect(data).toEqual('raw text');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,985 +1,1048 @@
|
||||
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')
|
||||
/*
|
||||
* decaffeinate suggestions:
|
||||
* DS102: Remove unnecessary code created because of implicit returns
|
||||
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
|
||||
*/
|
||||
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');
|
||||
return 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']);
|
||||
return expect(saveResult.data).toEqual('replace with markdown');
|
||||
});
|
||||
return 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']);
|
||||
return 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('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();
|
||||
return 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('insertMultipleChoice', function() {
|
||||
it('inserts the template if selection is empty', function() {
|
||||
const revisedSelection = MarkdownEditingDescriptor.insertMultipleChoice('');
|
||||
return expect(revisedSelection).toEqual(MarkdownEditingDescriptor.multipleChoiceTemplate);
|
||||
});
|
||||
it('wraps existing text', function() {
|
||||
const revisedSelection = MarkdownEditingDescriptor.insertMultipleChoice('foo\nbar');
|
||||
return 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');
|
||||
return 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');
|
||||
return expect(revisedSelection).toEqual('(x) correct\n( ) x \n( ) ex post facto\n( ) b x c\n(x) c\n( ) xxp\n');
|
||||
});
|
||||
return it('removes multiple newlines but not last one', function() {
|
||||
const revisedSelection = MarkdownEditingDescriptor.insertMultipleChoice('a\nx b\n\n\nc\n');
|
||||
return 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('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('');
|
||||
return expect(revisedSelection).toEqual(MarkdownEditingDescriptor.checkboxChoiceTemplate);
|
||||
});
|
||||
return it('wraps existing text', function() {
|
||||
const revisedSelection = MarkdownEditingDescriptor.insertCheckboxChoice('foo\nbar');
|
||||
return 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('insertStringInput', function() {
|
||||
it('inserts the template if selection is empty', function() {
|
||||
const revisedSelection = MarkdownEditingDescriptor.insertStringInput('');
|
||||
return expect(revisedSelection).toEqual(MarkdownEditingDescriptor.stringInputTemplate);
|
||||
});
|
||||
return it('wraps existing text', function() {
|
||||
const revisedSelection = MarkdownEditingDescriptor.insertStringInput('my text');
|
||||
return 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('insertNumberInput', function() {
|
||||
it('inserts the template if selection is empty', function() {
|
||||
const revisedSelection = MarkdownEditingDescriptor.insertNumberInput('');
|
||||
return expect(revisedSelection).toEqual(MarkdownEditingDescriptor.numberInputTemplate);
|
||||
});
|
||||
return it('wraps existing text', function() {
|
||||
const revisedSelection = MarkdownEditingDescriptor.insertNumberInput('my text');
|
||||
return 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('insertSelect', function() {
|
||||
it('inserts the template if selection is empty', function() {
|
||||
const revisedSelection = MarkdownEditingDescriptor.insertSelect('');
|
||||
return expect(revisedSelection).toEqual(MarkdownEditingDescriptor.selectTemplate);
|
||||
});
|
||||
return it('wraps existing text', function() {
|
||||
const revisedSelection = MarkdownEditingDescriptor.insertSelect('my text');
|
||||
return 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('insertHeader', function() {
|
||||
it('inserts the template if selection is empty', function() {
|
||||
const revisedSelection = MarkdownEditingDescriptor.insertHeader('');
|
||||
return expect(revisedSelection).toEqual(MarkdownEditingDescriptor.headerTemplate);
|
||||
});
|
||||
return it('wraps existing text', function() {
|
||||
const revisedSelection = MarkdownEditingDescriptor.insertHeader('my text');
|
||||
return 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('insertExplanation', function() {
|
||||
it('inserts the template if selection is empty', function() {
|
||||
const revisedSelection = MarkdownEditingDescriptor.insertExplanation('');
|
||||
return expect(revisedSelection).toEqual(MarkdownEditingDescriptor.explanationTemplate);
|
||||
});
|
||||
return it('wraps existing text', function() {
|
||||
const revisedSelection = MarkdownEditingDescriptor.insertExplanation('my text');
|
||||
return 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.
|
||||
return describe('markdownToXml', function() {
|
||||
it('converts raw text to paragraph', function() {
|
||||
const data = MarkdownEditingDescriptor.markdownToXml('foo');
|
||||
return 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.
|
||||
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 numerical value of Pi:
|
||||
= 3.14159 +- .02
|
||||
|
||||
Enter the approximate value of 502*9:
|
||||
= 502*9 +- 15%
|
||||
Enter the approximate value of 502*9:
|
||||
= 502*9 +- 15%
|
||||
|
||||
Enter the number of fingers on a human hand:
|
||||
= 5
|
||||
Enter the number of fingers on a human hand:
|
||||
= 5
|
||||
|
||||
Range tolerance case
|
||||
= [6, 7]
|
||||
= (1, 2)
|
||||
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
|
||||
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.
|
||||
[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.
|
||||
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>
|
||||
If you look at your hand, you can count that you have five fingers.
|
||||
[Explanation]\
|
||||
`);
|
||||
return 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\
|
||||
`);
|
||||
return 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\
|
||||
`);
|
||||
return 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\
|
||||
`);
|
||||
return 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\
|
||||
`);
|
||||
return 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 }}\
|
||||
`);
|
||||
return 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]\
|
||||
`);
|
||||
return 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>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>
|
||||
<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>
|
||||
</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>
|
||||
</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.
|
||||
|
||||
</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>
|
||||
What Apple device competed with the portable CD player?
|
||||
(!x@) The iPad
|
||||
(@) Napster
|
||||
() The iPod
|
||||
( ) The vegetable peeler
|
||||
( ) Android
|
||||
(@) The Beatles
|
||||
|
||||
</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>
|
||||
[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]\
|
||||
`);
|
||||
return 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>When the student is ready, the explanation appears.</p>
|
||||
<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>
|
||||
</problem>""")
|
||||
</solution>
|
||||
</multiplechoiceresponse>
|
||||
</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.
|
||||
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]\
|
||||
`);
|
||||
return 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>`);
|
||||
});
|
||||
|
||||
The answer options and the identification of the correct answer is defined in the <b>optioninput</b> tag.
|
||||
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.
|
||||
|
||||
Translation between Option Response and __________ is extremely straightforward:
|
||||
[[(Multiple Choice), String Response, Numerical Response, External Response, Image Response]]
|
||||
The answer options and the identification of the correct answer is defined in the <b>optioninput</b> tag.
|
||||
|
||||
[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.
|
||||
Translation between Option Response and __________ is extremely straightforward:
|
||||
[[(Multiple Choice), String Response, Numerical Response, External Response, Image Response]]
|
||||
|
||||
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.
|
||||
[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]\
|
||||
`);
|
||||
return 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.
|
||||
|
||||
Which US state has Lansing as its capital?
|
||||
= Michigan
|
||||
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.
|
||||
|
||||
[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*.*
|
||||
Which US state has Lansing as its capital?
|
||||
= Michigan
|
||||
|
||||
[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]
|
||||
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]\
|
||||
`);
|
||||
return 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="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]\
|
||||
`);
|
||||
return 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="^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]\
|
||||
`);
|
||||
return 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="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.
|
||||
[Explanation]
|
||||
Test Explanation.
|
||||
[Explanation]\
|
||||
`);
|
||||
return 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*.*
|
||||
|
||||
>>What is the capital of France?<<
|
||||
= Paris
|
||||
[Explanation]
|
||||
Test Explanation.
|
||||
[Explanation]\
|
||||
`);
|
||||
return 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.
|
||||
|
||||
Germany is a country in Europe, too.
|
||||
>>What is the capital of France?<<
|
||||
= Paris
|
||||
|
||||
>>What is the capital of Germany?<<
|
||||
( ) Bonn
|
||||
( ) Hamburg
|
||||
(x) Berlin
|
||||
( ) Donut
|
||||
""")
|
||||
expect(data).toXMLEqual("""
|
||||
<problem>
|
||||
<p>France is a country in Europe.</p>
|
||||
Germany is a country in Europe, too.
|
||||
|
||||
<label>What is the capital of France?</label>
|
||||
<stringresponse answer="Paris" type="ci" >
|
||||
<textline size="20"/>
|
||||
</stringresponse>
|
||||
>>What is the capital of Germany?<<
|
||||
( ) Bonn
|
||||
( ) Hamburg
|
||||
(x) Berlin
|
||||
( ) Donut\
|
||||
`);
|
||||
return expect(data).toXMLEqual(`\
|
||||
<problem>
|
||||
<p>France is a country in Europe.</p>
|
||||
|
||||
<p>Germany is a country in Europe, too.</p>
|
||||
<label>What is the capital of France?</label>
|
||||
<stringresponse answer="Paris" type="ci" >
|
||||
<textline size="20"/>
|
||||
</stringresponse>
|
||||
|
||||
<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.
|
||||
<p>Germany is a country in Europe, too.</p>
|
||||
|
||||
>>What is the capital of France?<<
|
||||
= Paris
|
||||
<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.
|
||||
|
||||
Germany is a country in Europe, too.
|
||||
>>What is the capital of France?<<
|
||||
= Paris
|
||||
|
||||
What is the capital of Germany?
|
||||
( ) Bonn
|
||||
( ) Hamburg
|
||||
(x) Berlin
|
||||
( ) Donut
|
||||
""")
|
||||
expect(data).toXMLEqual("""
|
||||
<problem>
|
||||
<p>France is a country in Europe.</p>
|
||||
Germany is a country in Europe, too.
|
||||
|
||||
<label>What is the capital of France?</label>
|
||||
<stringresponse answer="Paris" type="ci" >
|
||||
<textline size="20"/>
|
||||
</stringresponse>
|
||||
What is the capital of Germany?
|
||||
( ) Bonn
|
||||
( ) Hamburg
|
||||
(x) Berlin
|
||||
( ) Donut\
|
||||
`);
|
||||
return expect(data).toXMLEqual(`\
|
||||
<problem>
|
||||
<p>France is a country in Europe.</p>
|
||||
|
||||
<p>Germany is a country in Europe, too.</p>
|
||||
<label>What is the capital of France?</label>
|
||||
<stringresponse answer="Paris" type="ci" >
|
||||
<textline size="20"/>
|
||||
</stringresponse>
|
||||
|
||||
<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>""")
|
||||
<p>Germany is a country in Europe, too.</p>
|
||||
|
||||
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>
|
||||
<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\
|
||||
`);
|
||||
return expect(data).toXMLEqual(`<problem>
|
||||
<numericalresponse answer="3.14159">
|
||||
<label>Enter the numerical value of Pi:</label>
|
||||
<responseparam type="tolerance" default=".02"/>
|
||||
<formulaequationinput/>
|
||||
</numericalresponse>
|
||||
|
||||
|
||||
</problem>""")
|
||||
</problem>`);
|
||||
});
|
||||
|
||||
# test oddities
|
||||
it 'converts headers and oddities to xml', ->
|
||||
data = MarkdownEditingDescriptor.markdownToXml("""Not a header
|
||||
A header
|
||||
==============
|
||||
// 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
|
||||
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
|
||||
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 multiple correct ones
|
||||
[[one option, (correct one), (should not be correct)]]
|
||||
|
||||
Option with embedded parens
|
||||
[[My (heart), another, (correct)]]
|
||||
Option with embedded parens
|
||||
[[My (heart), another, (correct)]]
|
||||
|
||||
What happens w/ empty correct options?
|
||||
[[()]]
|
||||
What happens w/ empty correct options?
|
||||
[[()]]
|
||||
|
||||
[Explanation]see[/expLanation]
|
||||
[Explanation]see[/expLanation]
|
||||
|
||||
[explanation]
|
||||
orphaned start
|
||||
[explanation]
|
||||
orphaned start
|
||||
|
||||
No p tags in the below
|
||||
<script type='javascript'>
|
||||
var two = 2;
|
||||
No p tags in the below
|
||||
<script type='javascript'>
|
||||
var two = 2;
|
||||
|
||||
console.log(two * 2);
|
||||
</script>
|
||||
console.log(two * 2);
|
||||
</script>
|
||||
|
||||
But in this there should be
|
||||
<div>
|
||||
Great ideas require offsetting.
|
||||
But in this there should be
|
||||
<div>
|
||||
Great ideas require offsetting.
|
||||
|
||||
bad tests require drivel
|
||||
</div>
|
||||
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>
|
||||
[code]
|
||||
Code should be nicely monospaced.
|
||||
[/code]\
|
||||
`);
|
||||
return 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]
|
||||
\
|
||||
`);
|
||||
return 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>see</p>
|
||||
<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>
|
||||
<p>[explanation]</p>
|
||||
<p>orphaned start</p>
|
||||
<p>No p tags in the below</p>
|
||||
<script type='javascript'>
|
||||
var two = 2;
|
||||
</choiceresponse>
|
||||
</problem>\
|
||||
`);
|
||||
});
|
||||
|
||||
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 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.
|
||||
|
||||
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
|
||||
>>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]
|
||||
[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]\
|
||||
`);
|
||||
return 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
|
||||
|
||||
---
|
||||
|
||||
Checkbox problems allow learners to select multiple options. Learners can see all the options along with the problem text.
|
||||
>>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\
|
||||
`);
|
||||
return 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>
|
||||
|
||||
>>The following languages are in the Indo-European family:<<
|
||||
[x] Urdu
|
||||
[ ] Finnish
|
||||
[x] Marathi
|
||||
[x] French
|
||||
[ ] Hungarian
|
||||
<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>\
|
||||
`);
|
||||
});
|
||||
|
||||
Note: Make sure you select all of the correct options—there may be more than one!
|
||||
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\
|
||||
`);
|
||||
return 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>\
|
||||
`);
|
||||
});
|
||||
|
||||
[explanation]
|
||||
Urdu, Marathi, and French are all Indo-European languages, while Finnish and Hungarian are in the Uralic family.
|
||||
[explanation]
|
||||
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\
|
||||
`);
|
||||
return 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>\
|
||||
`);
|
||||
});
|
||||
|
||||
""")
|
||||
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>
|
||||
""")
|
||||
return it('will not add empty description', function() {
|
||||
const data = MarkdownEditingDescriptor.markdownToXml(`\
|
||||
>>The following languages are in the Indo-European family:||<<
|
||||
[x] Urdu
|
||||
[ ] Finnish\
|
||||
`);
|
||||
return 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 +1,1036 @@
|
||||
# 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.
|
||||
/*
|
||||
* decaffeinate suggestions:
|
||||
* DS102: Remove unnecessary code created because of implicit returns
|
||||
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
|
||||
*/
|
||||
// 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. }}
|
||||
]]
|
||||
[[
|
||||
(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.
|
||||
Clowns have funny _________ to make people laugh.
|
||||
|
||||
[[
|
||||
dogs {{ NOPE::Not dogs, not cats, not toads }}
|
||||
(FACES) {{ With lots of makeup, doncha know?}}
|
||||
[[
|
||||
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>
|
||||
money {{ Clowns don't have any money, of course }}
|
||||
donkeys {{don't be an ass.}}
|
||||
-no hint-
|
||||
]]
|
||||
\
|
||||
`);
|
||||
return expect(data).toXMLEqual(`\
|
||||
<problem>
|
||||
<p>Translation between Dropdown and ________ is straightforward.</p>
|
||||
<optionresponse>
|
||||
<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>
|
||||
<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="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>
|
||||
<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 ||\
|
||||
`);
|
||||
return 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 ||\
|
||||
`);
|
||||
return 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 }} ]]\
|
||||
`);
|
||||
return 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>\
|
||||
`);
|
||||
});
|
||||
|
||||
return it('produces xml even with lots of whitespace', function() {
|
||||
const data = MarkdownEditingDescriptor.markdownToXml(`\
|
||||
>>q1<<
|
||||
[[
|
||||
|
||||
|
||||
aa {{ hint1 }}
|
||||
|
||||
bb {{ hint2 }}
|
||||
(cc)
|
||||
|
||||
]]\
|
||||
`);
|
||||
return 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? }}\
|
||||
`);
|
||||
return expect(data).toXMLEqual(`\
|
||||
<problem>
|
||||
<label>Select all the fruits from the list</label>
|
||||
<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>
|
||||
<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>\
|
||||
`);
|
||||
});
|
||||
|
||||
</problem>
|
||||
""")
|
||||
return it('produces xml also with demand hints', function() {
|
||||
const data = MarkdownEditingDescriptor.markdownToXml(`\
|
||||
>>Select all the fruits from the list<<
|
||||
|
||||
# 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] 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?}}
|
||||
|
||||
[[
|
||||
(x) {{ hintx
|
||||
these
|
||||
span
|
||||
}}
|
||||
{{ ((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.}}
|
||||
|
||||
yy {{ meh::hinty }}
|
||||
zzz {{ hintz }}
|
||||
]]
|
||||
"""
|
||||
markdown = markdown.replace(/\n/g, '\r\n') # make DOS line endings
|
||||
data = MarkdownEditingDescriptor.markdownToXml(markdown)
|
||||
expect(data).toXMLEqual("""
|
||||
<problem>
|
||||
>>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. ||\
|
||||
`);
|
||||
return 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.}}\
|
||||
`);
|
||||
return 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>\
|
||||
`);
|
||||
});
|
||||
|
||||
return 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? ||
|
||||
\
|
||||
`);
|
||||
return 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! }}
|
||||
\
|
||||
`);
|
||||
return 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 }}
|
||||
\
|
||||
`);
|
||||
return 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}}
|
||||
\
|
||||
`);
|
||||
return 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}}
|
||||
\
|
||||
`);
|
||||
return 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
|
||||
\
|
||||
`);
|
||||
return 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
|
||||
\
|
||||
`);
|
||||
return 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\
|
||||
`);
|
||||
return 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
|
||||
\
|
||||
`);
|
||||
return 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
|
||||
\
|
||||
`);
|
||||
return 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}}
|
||||
\
|
||||
`);
|
||||
return 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>\
|
||||
`);
|
||||
});
|
||||
|
||||
return 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. ||
|
||||
\
|
||||
`);
|
||||
return 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
|
||||
\
|
||||
`);
|
||||
return 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.
|
||||
return it('numeric input with hints and demand hints', function() {
|
||||
const data = MarkdownEditingDescriptor.markdownToXml(`\
|
||||
>>text1<<
|
||||
= 1 {{ hint1 }}
|
||||
|| hintA ||
|
||||
>>text2<<
|
||||
= 2 {{ hint2 }}
|
||||
|
||||
|| hintB ||
|
||||
\
|
||||
`);
|
||||
return 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 ||
|
||||
\
|
||||
`);
|
||||
return 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>
|
||||
<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>
|
||||
<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>\
|
||||
`);
|
||||
})
|
||||
);
|
||||
|
||||
</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
|
||||
|
||||
|| Ø ||
|
||||
\
|
||||
`);
|
||||
return 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" }}
|
||||
\
|
||||
`);
|
||||
return 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)\
|
||||
`);
|
||||
return 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 []\
|
||||
`);
|
||||
return 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.
|
||||
return 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);
|
||||
return 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 +1,111 @@
|
||||
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)
|
||||
/*
|
||||
* decaffeinate suggestions:
|
||||
* DS102: Remove unnecessary code created because of implicit returns
|
||||
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
|
||||
*/
|
||||
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')
|
||||
spyOn($.fn, 'hide').and.callThrough();
|
||||
spyOn($.fn, 'show').and.callThrough();
|
||||
spyOn(TabsEditingDescriptor.Model, 'initialize');
|
||||
return spyOn(TabsEditingDescriptor.Model, 'updateValue');
|
||||
});
|
||||
|
||||
afterEach ->
|
||||
TabsEditingDescriptor.Model.modules= {}
|
||||
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("constructor", () =>
|
||||
it("first tab should be visible", function() {
|
||||
expect(this.descriptor.$tabs.first()).toHaveClass(this.isCurrent);
|
||||
return expect(this.descriptor.$content.first()).not.toHaveClass(this.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()
|
||||
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);
|
||||
return expect(this.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("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);
|
||||
return 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()
|
||||
return it("onSwitch function call", function() {
|
||||
this.descriptor.$tabs.eq(1).trigger("click");
|
||||
expect(TabsEditingDescriptor.Model.updateValue).toHaveBeenCalled();
|
||||
return expect(this.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()
|
||||
return describe("save", function() {
|
||||
it("function for current tab should be called", function() {
|
||||
this.descriptor.$tabs.eq(1).trigger("click");
|
||||
const { data } = this.descriptor.save();
|
||||
return expect(this.tab_1_modelUpdate).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it "detach click event", ->
|
||||
spyOn($.fn, "off")
|
||||
@descriptor.save()
|
||||
expect($.fn.off).toHaveBeenCalledWith(
|
||||
return it("detach click event", function() {
|
||||
spyOn($.fn, "off");
|
||||
this.descriptor.save();
|
||||
return expect($.fn.off).toHaveBeenCalledWith(
|
||||
'click',
|
||||
'.editor-tabs .tab',
|
||||
@descriptor.onSwitchEditor
|
||||
)
|
||||
this.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("TabsEditingDescriptor special save cases", function() {
|
||||
beforeEach(function() {
|
||||
this.isInactiveClass = "is-inactive";
|
||||
this.isCurrent = "current";
|
||||
loadFixtures('tabs-edit.html');
|
||||
this.descriptor = new window.TabsEditingDescriptor($('.xblock'));
|
||||
return this.html_id = 'test_id';
|
||||
});
|
||||
|
||||
describe "save", ->
|
||||
it "case: no init", ->
|
||||
data = @descriptor.save().data
|
||||
expect(data).toEqual(null)
|
||||
return describe("save", function() {
|
||||
it("case: no init", function() {
|
||||
const { data } = this.descriptor.save();
|
||||
return 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", function() {
|
||||
TabsEditingDescriptor.Model.initialize(this.html_id);
|
||||
const { data } = this.descriptor.save();
|
||||
return 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)
|
||||
return 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();
|
||||
return expect(data).toEqual(1);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,26 +1,38 @@
|
||||
describe "$.immediateDescendents", ->
|
||||
beforeEach ->
|
||||
setFixtures """
|
||||
<div>
|
||||
<div class='xblock' id='child'>
|
||||
<div class='xblock' id='nested'/>
|
||||
</div>
|
||||
<div>
|
||||
<div class='xblock' id='grandchild'/>
|
||||
</div>
|
||||
</div>
|
||||
"""
|
||||
/*
|
||||
* decaffeinate suggestions:
|
||||
* DS102: Remove unnecessary code created because of implicit returns
|
||||
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
|
||||
*/
|
||||
describe("$.immediateDescendents", function() {
|
||||
beforeEach(function() {
|
||||
setFixtures(`\
|
||||
<div>
|
||||
<div class='xblock' id='child'>
|
||||
<div class='xblock' id='nested'/>
|
||||
</div>
|
||||
<div>
|
||||
<div class='xblock' id='grandchild'/>
|
||||
</div>
|
||||
</div>\
|
||||
`
|
||||
);
|
||||
|
||||
@descendents = $('#jasmine-fixtures').immediateDescendents(".xblock").get()
|
||||
return this.descendents = $('#jasmine-fixtures').immediateDescendents(".xblock").get();
|
||||
});
|
||||
|
||||
it "finds non-immediate children", ->
|
||||
expect(@descendents).toContain($('#grandchild').get(0))
|
||||
it("finds non-immediate children", function() {
|
||||
return expect(this.descendents).toContain($('#grandchild').get(0));
|
||||
});
|
||||
|
||||
it "finds immediate children", ->
|
||||
expect(@descendents).toContain($('#child').get(0))
|
||||
it("finds immediate children", function() {
|
||||
return expect(this.descendents).toContain($('#child').get(0));
|
||||
});
|
||||
|
||||
it "skips nested descendents", ->
|
||||
expect(@descendents).not.toContain($('#nested').get(0))
|
||||
it("skips nested descendents", function() {
|
||||
return expect(this.descendents).not.toContain($('#nested').get(0));
|
||||
});
|
||||
|
||||
it "finds 2 children", ->
|
||||
expect(@descendents.length).toBe(2)
|
||||
return it("finds 2 children", function() {
|
||||
return expect(this.descendents.length).toBe(2);
|
||||
});
|
||||
});
|
||||
@@ -1,353 +1,435 @@
|
||||
describe 'Calculator', ->
|
||||
/*
|
||||
* decaffeinate suggestions:
|
||||
* DS102: Remove unnecessary code created because of implicit returns
|
||||
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
|
||||
*/
|
||||
describe('Calculator', function() {
|
||||
|
||||
KEY =
|
||||
TAB : 9
|
||||
ENTER : 13
|
||||
ALT : 18
|
||||
ESC : 27
|
||||
SPACE : 32
|
||||
LEFT : 37
|
||||
UP : 38
|
||||
RIGHT : 39
|
||||
const KEY = {
|
||||
TAB : 9,
|
||||
ENTER : 13,
|
||||
ALT : 18,
|
||||
ESC : 27,
|
||||
SPACE : 32,
|
||||
LEFT : 37,
|
||||
UP : 38,
|
||||
RIGHT : 39,
|
||||
DOWN : 40
|
||||
};
|
||||
|
||||
beforeEach ->
|
||||
loadFixtures 'coffee/fixtures/calculator.html'
|
||||
@calculator = new Calculator
|
||||
beforeEach(function() {
|
||||
loadFixtures('coffee/fixtures/calculator.html');
|
||||
return this.calculator = new Calculator;
|
||||
});
|
||||
|
||||
describe 'bind', ->
|
||||
it 'bind the calculator button', ->
|
||||
expect($('.calc')).toHandleWith 'click', @calculator.toggle
|
||||
describe('bind', function() {
|
||||
it('bind the calculator button', function() {
|
||||
return expect($('.calc')).toHandleWith('click', this.calculator.toggle);
|
||||
});
|
||||
|
||||
it 'bind key up on calculator', ->
|
||||
expect($('#calculator_wrapper')).toHandle 'keyup', @calculator.handleKeyUpOnHint
|
||||
it('bind key up on calculator', function() {
|
||||
return expect($('#calculator_wrapper')).toHandle('keyup', this.calculator.handleKeyUpOnHint);
|
||||
});
|
||||
|
||||
it 'bind the help button', ->
|
||||
# This events is bind by $.click()
|
||||
expect($('#calculator_hint')).toHandle 'click'
|
||||
it('bind the help button', () =>
|
||||
// This events is bind by $.click()
|
||||
expect($('#calculator_hint')).toHandle('click')
|
||||
);
|
||||
|
||||
it 'bind the calculator submit', ->
|
||||
expect($('form#calculator')).toHandleWith 'submit', @calculator.calculate
|
||||
it('bind the calculator submit', function() {
|
||||
return expect($('form#calculator')).toHandleWith('submit', this.calculator.calculate);
|
||||
});
|
||||
|
||||
xit 'prevent default behavior on form submit', ->
|
||||
jasmine.stubRequests()
|
||||
$('form#calculator').submit (e) ->
|
||||
expect(e.isDefaultPrevented()).toBeTruthy()
|
||||
e.preventDefault()
|
||||
$('form#calculator').submit()
|
||||
return xit('prevent default behavior on form submit', function() {
|
||||
jasmine.stubRequests();
|
||||
$('form#calculator').submit(function(e) {
|
||||
expect(e.isDefaultPrevented()).toBeTruthy();
|
||||
return e.preventDefault();
|
||||
});
|
||||
return $('form#calculator').submit();
|
||||
});
|
||||
});
|
||||
|
||||
describe 'toggle', ->
|
||||
it 'focuses the input when toggled', (done)->
|
||||
describe('toggle', function() {
|
||||
it('focuses the input when toggled', function(done){
|
||||
|
||||
self = this
|
||||
focus = ()->
|
||||
deferred = $.Deferred()
|
||||
const self = this;
|
||||
const focus = function(){
|
||||
const deferred = $.Deferred();
|
||||
|
||||
# Since the focus is called asynchronously, we need to
|
||||
# wait until focus() is called.
|
||||
spyOn($.fn, 'focus').and.callFake (elementName) ->
|
||||
deferred.resolve()
|
||||
// Since the focus is called asynchronously, we need to
|
||||
// wait until focus() is called.
|
||||
spyOn($.fn, 'focus').and.callFake(elementName => deferred.resolve());
|
||||
|
||||
self.calculator.toggle(jQuery.Event("click"))
|
||||
self.calculator.toggle(jQuery.Event("click"));
|
||||
|
||||
deferred.promise()
|
||||
return deferred.promise();
|
||||
};
|
||||
|
||||
focus().then(
|
||||
->
|
||||
expect($('#calculator_wrapper #calculator_input').focus).toHaveBeenCalled()
|
||||
).always(done)
|
||||
return focus().then(
|
||||
() => expect($('#calculator_wrapper #calculator_input').focus).toHaveBeenCalled()).always(done);
|
||||
});
|
||||
|
||||
it 'toggle the close button on the calculator button', ->
|
||||
@calculator.toggle(jQuery.Event("click"))
|
||||
expect($('.calc')).toHaveClass('closed')
|
||||
return it('toggle the close button on the calculator button', function() {
|
||||
this.calculator.toggle(jQuery.Event("click"));
|
||||
expect($('.calc')).toHaveClass('closed');
|
||||
|
||||
@calculator.toggle(jQuery.Event("click"))
|
||||
expect($('.calc')).not.toHaveClass('closed')
|
||||
this.calculator.toggle(jQuery.Event("click"));
|
||||
return expect($('.calc')).not.toHaveClass('closed');
|
||||
});
|
||||
});
|
||||
|
||||
describe 'showHint', ->
|
||||
it 'show the help overlay', ->
|
||||
@calculator.showHint()
|
||||
expect($('.help')).toHaveClass('shown')
|
||||
expect($('.help')).toHaveAttr('aria-hidden', 'false')
|
||||
describe('showHint', () =>
|
||||
it('show the help overlay', function() {
|
||||
this.calculator.showHint();
|
||||
expect($('.help')).toHaveClass('shown');
|
||||
return expect($('.help')).toHaveAttr('aria-hidden', 'false');
|
||||
})
|
||||
);
|
||||
|
||||
|
||||
describe 'hideHint', ->
|
||||
it 'show the help overlay', ->
|
||||
@calculator.hideHint()
|
||||
expect($('.help')).not.toHaveClass('shown')
|
||||
expect($('.help')).toHaveAttr('aria-hidden', 'true')
|
||||
describe('hideHint', () =>
|
||||
it('show the help overlay', function() {
|
||||
this.calculator.hideHint();
|
||||
expect($('.help')).not.toHaveClass('shown');
|
||||
return expect($('.help')).toHaveAttr('aria-hidden', 'true');
|
||||
})
|
||||
);
|
||||
|
||||
describe 'handleClickOnHintButton', ->
|
||||
it 'on click hint button hint popup becomes visible ', ->
|
||||
e = jQuery.Event('click');
|
||||
describe('handleClickOnHintButton', () =>
|
||||
it('on click hint button hint popup becomes visible ', function() {
|
||||
const e = jQuery.Event('click');
|
||||
$('#calculator_hint').trigger(e);
|
||||
expect($('.help')).toHaveClass 'shown'
|
||||
return expect($('.help')).toHaveClass('shown');
|
||||
})
|
||||
);
|
||||
|
||||
describe 'handleClickOnDocument', ->
|
||||
it 'on click out of the hint popup it becomes hidden', ->
|
||||
@calculator.showHint()
|
||||
e = jQuery.Event('click');
|
||||
describe('handleClickOnDocument', () =>
|
||||
it('on click out of the hint popup it becomes hidden', function() {
|
||||
this.calculator.showHint();
|
||||
const e = jQuery.Event('click');
|
||||
$(document).trigger(e);
|
||||
expect($('.help')).not.toHaveClass 'shown'
|
||||
return expect($('.help')).not.toHaveClass('shown');
|
||||
})
|
||||
);
|
||||
|
||||
describe 'handleClickOnHintPopup', ->
|
||||
it 'on click of hint popup it remains visible', ->
|
||||
@calculator.showHint()
|
||||
e = jQuery.Event('click');
|
||||
describe('handleClickOnHintPopup', () =>
|
||||
it('on click of hint popup it remains visible', function() {
|
||||
this.calculator.showHint();
|
||||
const e = jQuery.Event('click');
|
||||
$('#calculator_input_help').trigger(e);
|
||||
expect($('.help')).toHaveClass 'shown'
|
||||
return expect($('.help')).toHaveClass('shown');
|
||||
})
|
||||
);
|
||||
|
||||
describe 'selectHint', ->
|
||||
it 'select correct hint item', ->
|
||||
spyOn($.fn, 'focus')
|
||||
element = $('.hint-item').eq(1)
|
||||
@calculator.selectHint(element)
|
||||
describe('selectHint', function() {
|
||||
it('select correct hint item', function() {
|
||||
spyOn($.fn, 'focus');
|
||||
const element = $('.hint-item').eq(1);
|
||||
this.calculator.selectHint(element);
|
||||
|
||||
expect(element.focus).toHaveBeenCalled()
|
||||
expect(@calculator.activeHint).toEqual(element)
|
||||
expect(@calculator.hintPopup).toHaveAttr('data-calculator-hint', element.attr('id'))
|
||||
expect(element.focus).toHaveBeenCalled();
|
||||
expect(this.calculator.activeHint).toEqual(element);
|
||||
return expect(this.calculator.hintPopup).toHaveAttr('data-calculator-hint', element.attr('id'));
|
||||
});
|
||||
|
||||
it 'select the first hint if argument element is not passed', ->
|
||||
@calculator.selectHint()
|
||||
expect(@calculator.activeHint.attr('id')).toEqual($('.hint-item').first().attr('id'))
|
||||
it('select the first hint if argument element is not passed', function() {
|
||||
this.calculator.selectHint();
|
||||
return expect(this.calculator.activeHint.attr('id')).toEqual($('.hint-item').first().attr('id'));
|
||||
});
|
||||
|
||||
it 'select the first hint if argument element is empty', ->
|
||||
@calculator.selectHint([])
|
||||
expect(@calculator.activeHint.attr('id')).toBe($('.hint-item').first().attr('id'))
|
||||
return it('select the first hint if argument element is empty', function() {
|
||||
this.calculator.selectHint([]);
|
||||
return expect(this.calculator.activeHint.attr('id')).toBe($('.hint-item').first().attr('id'));
|
||||
});
|
||||
});
|
||||
|
||||
describe 'prevHint', ->
|
||||
describe('prevHint', function() {
|
||||
|
||||
it 'Prev hint item is selected', ->
|
||||
@calculator.activeHint = $('.hint-item').eq(1)
|
||||
@calculator.prevHint()
|
||||
it('Prev hint item is selected', function() {
|
||||
this.calculator.activeHint = $('.hint-item').eq(1);
|
||||
this.calculator.prevHint();
|
||||
|
||||
expect(@calculator.activeHint.attr('id')).toBe($('.hint-item').eq(0).attr('id'))
|
||||
return expect(this.calculator.activeHint.attr('id')).toBe($('.hint-item').eq(0).attr('id'));
|
||||
});
|
||||
|
||||
it 'if this was the second item, select the first one', ->
|
||||
@calculator.activeHint = $('.hint-item').eq(1)
|
||||
@calculator.prevHint()
|
||||
it('if this was the second item, select the first one', function() {
|
||||
this.calculator.activeHint = $('.hint-item').eq(1);
|
||||
this.calculator.prevHint();
|
||||
|
||||
expect(@calculator.activeHint.attr('id')).toBe($('.hint-item').eq(0).attr('id'))
|
||||
return expect(this.calculator.activeHint.attr('id')).toBe($('.hint-item').eq(0).attr('id'));
|
||||
});
|
||||
|
||||
it 'if this was the first item, select the last one', ->
|
||||
@calculator.activeHint = $('.hint-item').eq(0)
|
||||
@calculator.prevHint()
|
||||
it('if this was the first item, select the last one', function() {
|
||||
this.calculator.activeHint = $('.hint-item').eq(0);
|
||||
this.calculator.prevHint();
|
||||
|
||||
expect(@calculator.activeHint.attr('id')).toBe($('.hint-item').eq(2).attr('id'))
|
||||
return expect(this.calculator.activeHint.attr('id')).toBe($('.hint-item').eq(2).attr('id'));
|
||||
});
|
||||
|
||||
it 'if this was the last item, select the second last', ->
|
||||
@calculator.activeHint = $('.hint-item').eq(2)
|
||||
@calculator.prevHint()
|
||||
return it('if this was the last item, select the second last', function() {
|
||||
this.calculator.activeHint = $('.hint-item').eq(2);
|
||||
this.calculator.prevHint();
|
||||
|
||||
expect(@calculator.activeHint.attr('id')).toBe($('.hint-item').eq(1).attr('id'))
|
||||
return expect(this.calculator.activeHint.attr('id')).toBe($('.hint-item').eq(1).attr('id'));
|
||||
});
|
||||
});
|
||||
|
||||
describe 'nextHint', ->
|
||||
describe('nextHint', function() {
|
||||
|
||||
it 'if this was the first item, select the second one', ->
|
||||
@calculator.activeHint = $('.hint-item').eq(0)
|
||||
@calculator.nextHint()
|
||||
it('if this was the first item, select the second one', function() {
|
||||
this.calculator.activeHint = $('.hint-item').eq(0);
|
||||
this.calculator.nextHint();
|
||||
|
||||
expect(@calculator.activeHint.attr('id')).toBe($('.hint-item').eq(1).attr('id'))
|
||||
return expect(this.calculator.activeHint.attr('id')).toBe($('.hint-item').eq(1).attr('id'));
|
||||
});
|
||||
|
||||
it 'If this was the second item, select the last one', ->
|
||||
@calculator.activeHint = $('.hint-item').eq(1)
|
||||
@calculator.nextHint()
|
||||
it('If this was the second item, select the last one', function() {
|
||||
this.calculator.activeHint = $('.hint-item').eq(1);
|
||||
this.calculator.nextHint();
|
||||
|
||||
expect(@calculator.activeHint.attr('id')).toBe($('.hint-item').eq(2).attr('id'))
|
||||
return expect(this.calculator.activeHint.attr('id')).toBe($('.hint-item').eq(2).attr('id'));
|
||||
});
|
||||
|
||||
it 'If this was the last item, select the first one', ->
|
||||
@calculator.activeHint = $('.hint-item').eq(2)
|
||||
@calculator.nextHint()
|
||||
return it('If this was the last item, select the first one', function() {
|
||||
this.calculator.activeHint = $('.hint-item').eq(2);
|
||||
this.calculator.nextHint();
|
||||
|
||||
expect(@calculator.activeHint.attr('id')).toBe($('.hint-item').eq(0).attr('id'))
|
||||
return expect(this.calculator.activeHint.attr('id')).toBe($('.hint-item').eq(0).attr('id'));
|
||||
});
|
||||
});
|
||||
|
||||
describe 'handleKeyDown', ->
|
||||
assertHintIsHidden = (calc, key) ->
|
||||
spyOn(calc, 'hideHint')
|
||||
calc.showHint()
|
||||
e = jQuery.Event('keydown', { keyCode: key });
|
||||
value = calc.handleKeyDown(e)
|
||||
describe('handleKeyDown', function() {
|
||||
const assertHintIsHidden = function(calc, key) {
|
||||
spyOn(calc, 'hideHint');
|
||||
calc.showHint();
|
||||
const e = jQuery.Event('keydown', { keyCode: key });
|
||||
const value = calc.handleKeyDown(e);
|
||||
|
||||
expect(calc.hideHint).toHaveBeenCalled
|
||||
expect(value).toBeFalsy()
|
||||
expect(e.isDefaultPrevented()).toBeTruthy()
|
||||
expect(calc.hideHint).toHaveBeenCalled;
|
||||
expect(value).toBeFalsy();
|
||||
return expect(e.isDefaultPrevented()).toBeTruthy();
|
||||
};
|
||||
|
||||
assertHintIsVisible = (calc, key) ->
|
||||
spyOn(calc, 'showHint')
|
||||
spyOn($.fn, 'focus')
|
||||
e = jQuery.Event('keydown', { keyCode: key });
|
||||
value = calc.handleKeyDown(e)
|
||||
const assertHintIsVisible = function(calc, key) {
|
||||
spyOn(calc, 'showHint');
|
||||
spyOn($.fn, 'focus');
|
||||
const e = jQuery.Event('keydown', { keyCode: key });
|
||||
const value = calc.handleKeyDown(e);
|
||||
|
||||
expect(calc.showHint).toHaveBeenCalled
|
||||
expect(value).toBeFalsy()
|
||||
expect(e.isDefaultPrevented()).toBeTruthy()
|
||||
expect(calc.activeHint.focus).toHaveBeenCalled()
|
||||
expect(calc.showHint).toHaveBeenCalled;
|
||||
expect(value).toBeFalsy();
|
||||
expect(e.isDefaultPrevented()).toBeTruthy();
|
||||
return expect(calc.activeHint.focus).toHaveBeenCalled();
|
||||
};
|
||||
|
||||
assertNothingHappens = (calc, key) ->
|
||||
spyOn(calc, 'showHint')
|
||||
e = jQuery.Event('keydown', { keyCode: key });
|
||||
value = calc.handleKeyDown(e)
|
||||
const assertNothingHappens = function(calc, key) {
|
||||
spyOn(calc, 'showHint');
|
||||
const e = jQuery.Event('keydown', { keyCode: key });
|
||||
const value = calc.handleKeyDown(e);
|
||||
|
||||
expect(calc.showHint).not.toHaveBeenCalled
|
||||
expect(value).toBeTruthy()
|
||||
expect(e.isDefaultPrevented()).toBeFalsy()
|
||||
expect(calc.showHint).not.toHaveBeenCalled;
|
||||
expect(value).toBeTruthy();
|
||||
return expect(e.isDefaultPrevented()).toBeFalsy();
|
||||
};
|
||||
|
||||
it 'hint popup becomes hidden on press ENTER', ->
|
||||
assertHintIsHidden(@calculator, KEY.ENTER)
|
||||
it('hint popup becomes hidden on press ENTER', function() {
|
||||
return assertHintIsHidden(this.calculator, KEY.ENTER);
|
||||
});
|
||||
|
||||
it 'hint popup becomes visible on press ENTER', ->
|
||||
assertHintIsVisible(@calculator, KEY.ENTER)
|
||||
it('hint popup becomes visible on press ENTER', function() {
|
||||
return assertHintIsVisible(this.calculator, KEY.ENTER);
|
||||
});
|
||||
|
||||
it 'hint popup becomes hidden on press SPACE', ->
|
||||
assertHintIsHidden(@calculator, KEY.SPACE)
|
||||
it('hint popup becomes hidden on press SPACE', function() {
|
||||
return assertHintIsHidden(this.calculator, KEY.SPACE);
|
||||
});
|
||||
|
||||
it 'hint popup becomes visible on press SPACE', ->
|
||||
assertHintIsVisible(@calculator, KEY.SPACE)
|
||||
it('hint popup becomes visible on press SPACE', function() {
|
||||
return assertHintIsVisible(this.calculator, KEY.SPACE);
|
||||
});
|
||||
|
||||
it 'Nothing happens on press ALT', ->
|
||||
assertNothingHappens(@calculator, KEY.ALT)
|
||||
it('Nothing happens on press ALT', function() {
|
||||
return assertNothingHappens(this.calculator, KEY.ALT);
|
||||
});
|
||||
|
||||
it 'Nothing happens on press any other button', ->
|
||||
assertNothingHappens(@calculator, KEY.DOWN)
|
||||
return it('Nothing happens on press any other button', function() {
|
||||
return assertNothingHappens(this.calculator, KEY.DOWN);
|
||||
});
|
||||
});
|
||||
|
||||
describe 'handleKeyDownOnHint', ->
|
||||
it 'Navigation works in proper way', ->
|
||||
calc = @calculator
|
||||
describe('handleKeyDownOnHint', () =>
|
||||
it('Navigation works in proper way', function() {
|
||||
const calc = this.calculator;
|
||||
|
||||
eventToShowHint = jQuery.Event('keydown', { keyCode: KEY.ENTER } );
|
||||
const eventToShowHint = jQuery.Event('keydown', { keyCode: KEY.ENTER } );
|
||||
$('#calculator_hint').trigger(eventToShowHint);
|
||||
|
||||
spyOn(calc, 'hideHint')
|
||||
spyOn(calc, 'prevHint')
|
||||
spyOn(calc, 'nextHint')
|
||||
spyOn($.fn, 'focus')
|
||||
spyOn(calc, 'hideHint');
|
||||
spyOn(calc, 'prevHint');
|
||||
spyOn(calc, 'nextHint');
|
||||
spyOn($.fn, 'focus');
|
||||
|
||||
cases =
|
||||
left:
|
||||
event:
|
||||
keyCode: KEY.LEFT
|
||||
const cases = {
|
||||
left: {
|
||||
event: {
|
||||
keyCode: KEY.LEFT,
|
||||
shiftKey: false
|
||||
returnedValue: false
|
||||
called:
|
||||
},
|
||||
returnedValue: false,
|
||||
called: {
|
||||
'prevHint': calc
|
||||
},
|
||||
isPropagationStopped: true
|
||||
},
|
||||
|
||||
leftWithShift:
|
||||
returnedValue: true
|
||||
event:
|
||||
keyCode: KEY.LEFT
|
||||
leftWithShift: {
|
||||
returnedValue: true,
|
||||
event: {
|
||||
keyCode: KEY.LEFT,
|
||||
shiftKey: true
|
||||
not_called:
|
||||
},
|
||||
not_called: {
|
||||
'prevHint': calc
|
||||
}
|
||||
},
|
||||
|
||||
up:
|
||||
event:
|
||||
keyCode: KEY.UP
|
||||
up: {
|
||||
event: {
|
||||
keyCode: KEY.UP,
|
||||
shiftKey: false
|
||||
returnedValue: false
|
||||
called:
|
||||
},
|
||||
returnedValue: false,
|
||||
called: {
|
||||
'prevHint': calc
|
||||
},
|
||||
isPropagationStopped: true
|
||||
},
|
||||
|
||||
upWithShift:
|
||||
returnedValue: true
|
||||
event:
|
||||
keyCode: KEY.UP
|
||||
upWithShift: {
|
||||
returnedValue: true,
|
||||
event: {
|
||||
keyCode: KEY.UP,
|
||||
shiftKey: true
|
||||
not_called:
|
||||
},
|
||||
not_called: {
|
||||
'prevHint': calc
|
||||
}
|
||||
},
|
||||
|
||||
right:
|
||||
event:
|
||||
keyCode: KEY.RIGHT
|
||||
right: {
|
||||
event: {
|
||||
keyCode: KEY.RIGHT,
|
||||
shiftKey: false
|
||||
returnedValue: false
|
||||
called:
|
||||
},
|
||||
returnedValue: false,
|
||||
called: {
|
||||
'nextHint': calc
|
||||
},
|
||||
isPropagationStopped: true
|
||||
},
|
||||
|
||||
rightWithShift:
|
||||
returnedValue: true
|
||||
event:
|
||||
keyCode: KEY.RIGHT
|
||||
rightWithShift: {
|
||||
returnedValue: true,
|
||||
event: {
|
||||
keyCode: KEY.RIGHT,
|
||||
shiftKey: true
|
||||
not_called:
|
||||
},
|
||||
not_called: {
|
||||
'nextHint': calc
|
||||
}
|
||||
},
|
||||
|
||||
down:
|
||||
event:
|
||||
keyCode: KEY.DOWN
|
||||
down: {
|
||||
event: {
|
||||
keyCode: KEY.DOWN,
|
||||
shiftKey: false
|
||||
returnedValue: false
|
||||
called:
|
||||
},
|
||||
returnedValue: false,
|
||||
called: {
|
||||
'nextHint': calc
|
||||
},
|
||||
isPropagationStopped: true
|
||||
},
|
||||
|
||||
downWithShift:
|
||||
returnedValue: true
|
||||
event:
|
||||
keyCode: KEY.DOWN
|
||||
downWithShift: {
|
||||
returnedValue: true,
|
||||
event: {
|
||||
keyCode: KEY.DOWN,
|
||||
shiftKey: true
|
||||
not_called:
|
||||
},
|
||||
not_called: {
|
||||
'nextHint': calc
|
||||
}
|
||||
},
|
||||
|
||||
esc:
|
||||
returnedValue: false
|
||||
event:
|
||||
keyCode: KEY.ESC
|
||||
esc: {
|
||||
returnedValue: false,
|
||||
event: {
|
||||
keyCode: KEY.ESC,
|
||||
shiftKey: false
|
||||
called:
|
||||
'hideHint': calc
|
||||
},
|
||||
called: {
|
||||
'hideHint': calc,
|
||||
'focus': $.fn
|
||||
},
|
||||
isPropagationStopped: true
|
||||
},
|
||||
|
||||
alt:
|
||||
returnedValue: true
|
||||
event:
|
||||
alt: {
|
||||
returnedValue: true,
|
||||
event: {
|
||||
which: KEY.ALT
|
||||
not_called:
|
||||
'hideHint': calc
|
||||
'nextHint': calc
|
||||
},
|
||||
not_called: {
|
||||
'hideHint': calc,
|
||||
'nextHint': calc,
|
||||
'prevHint': calc
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
$.each(cases, (key, data) ->
|
||||
calc.hideHint.calls.reset()
|
||||
calc.prevHint.calls.reset()
|
||||
calc.nextHint.calls.reset()
|
||||
$.fn.focus.calls.reset()
|
||||
return $.each(cases, function(key, data) {
|
||||
calc.hideHint.calls.reset();
|
||||
calc.prevHint.calls.reset();
|
||||
calc.nextHint.calls.reset();
|
||||
$.fn.focus.calls.reset();
|
||||
|
||||
e = jQuery.Event('keydown', data.event or {});
|
||||
value = calc.handleKeyDownOnHint(e)
|
||||
const e = jQuery.Event('keydown', data.event || {});
|
||||
const value = calc.handleKeyDownOnHint(e);
|
||||
|
||||
if data.called
|
||||
$.each(data.called, (method, obj) ->
|
||||
expect(obj[method]).toHaveBeenCalled()
|
||||
)
|
||||
if (data.called) {
|
||||
$.each(data.called, (method, obj) => expect(obj[method]).toHaveBeenCalled());
|
||||
}
|
||||
|
||||
if data.not_called
|
||||
$.each(data.not_called, (method, obj) ->
|
||||
expect(obj[method]).not.toHaveBeenCalled()
|
||||
)
|
||||
if (data.not_called) {
|
||||
$.each(data.not_called, (method, obj) => expect(obj[method]).not.toHaveBeenCalled());
|
||||
}
|
||||
|
||||
if data.isPropagationStopped
|
||||
expect(e.isPropagationStopped()).toBeTruthy()
|
||||
else
|
||||
expect(e.isPropagationStopped()).toBeFalsy()
|
||||
if (data.isPropagationStopped) {
|
||||
expect(e.isPropagationStopped()).toBeTruthy();
|
||||
} else {
|
||||
expect(e.isPropagationStopped()).toBeFalsy();
|
||||
}
|
||||
|
||||
expect(value).toBe(data.returnedValue)
|
||||
)
|
||||
return expect(value).toBe(data.returnedValue);
|
||||
});
|
||||
})
|
||||
);
|
||||
|
||||
describe 'calculate', ->
|
||||
beforeEach ->
|
||||
$('#calculator_input').val '1+2'
|
||||
spyOn($, 'getWithPrefix').and.callFake (url, data, callback) ->
|
||||
callback({ result: 3 })
|
||||
@calculator.calculate()
|
||||
return describe('calculate', function() {
|
||||
beforeEach(function() {
|
||||
$('#calculator_input').val('1+2');
|
||||
spyOn($, 'getWithPrefix').and.callFake((url, data, callback) => callback({ result: 3 }));
|
||||
return this.calculator.calculate();
|
||||
});
|
||||
|
||||
it 'send data to /calculate', ->
|
||||
expect($.getWithPrefix).toHaveBeenCalledWith '/calculate',
|
||||
equation: '1+2'
|
||||
, jasmine.any(Function)
|
||||
it('send data to /calculate', () =>
|
||||
expect($.getWithPrefix).toHaveBeenCalledWith('/calculate',
|
||||
{equation: '1+2'}
|
||||
, jasmine.any(Function))
|
||||
);
|
||||
|
||||
it 'update the calculator output', ->
|
||||
expect($('#calculator_output').val()).toEqual('3')
|
||||
return it('update the calculator output', () => expect($('#calculator_output').val()).toEqual('3'));
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,31 +1,40 @@
|
||||
describe 'Courseware', ->
|
||||
describe 'start', ->
|
||||
it 'binds the Logger', ->
|
||||
spyOn(Logger, 'bind')
|
||||
Courseware.start()
|
||||
expect(Logger.bind).toHaveBeenCalled()
|
||||
/*
|
||||
* decaffeinate suggestions:
|
||||
* DS102: Remove unnecessary code created because of implicit returns
|
||||
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
|
||||
*/
|
||||
describe('Courseware', function() {
|
||||
describe('start', () =>
|
||||
it('binds the Logger', function() {
|
||||
spyOn(Logger, 'bind');
|
||||
Courseware.start();
|
||||
return expect(Logger.bind).toHaveBeenCalled();
|
||||
})
|
||||
);
|
||||
|
||||
describe 'render', ->
|
||||
beforeEach ->
|
||||
jasmine.stubRequests()
|
||||
@courseware = new Courseware
|
||||
spyOn(window, 'Histogram')
|
||||
spyOn(window, 'Problem')
|
||||
spyOn(window, 'Video')
|
||||
spyOn(XBlock, 'initializeBlocks')
|
||||
setFixtures """
|
||||
<div class="course-content">
|
||||
<div id="video_1" class="video" data-streams="1.0:abc1234"></div>
|
||||
<div id="video_2" class="video" data-streams="1.0:def5678"></div>
|
||||
<div id="problem_3" class="problems-wrapper" data-problem-id="3" data-url="/example/url/">
|
||||
<div id="histogram_3" class="histogram" data-histogram="[[0, 1]]" style="height: 20px; display: block;">
|
||||
</div>
|
||||
</div>
|
||||
"""
|
||||
@courseware.render()
|
||||
return describe('render', function() {
|
||||
beforeEach(function() {
|
||||
jasmine.stubRequests();
|
||||
this.courseware = new Courseware;
|
||||
spyOn(window, 'Histogram');
|
||||
spyOn(window, 'Problem');
|
||||
spyOn(window, 'Video');
|
||||
spyOn(XBlock, 'initializeBlocks');
|
||||
setFixtures(`\
|
||||
<div class="course-content">
|
||||
<div id="video_1" class="video" data-streams="1.0:abc1234"></div>
|
||||
<div id="video_2" class="video" data-streams="1.0:def5678"></div>
|
||||
<div id="problem_3" class="problems-wrapper" data-problem-id="3" data-url="/example/url/">
|
||||
<div id="histogram_3" class="histogram" data-histogram="[[0, 1]]" style="height: 20px; display: block;">
|
||||
</div>
|
||||
</div>\
|
||||
`
|
||||
);
|
||||
return this.courseware.render();
|
||||
});
|
||||
|
||||
it 'ensure that the XModules have been loaded', ->
|
||||
expect(XBlock.initializeBlocks).toHaveBeenCalled()
|
||||
it('ensure that the XModules have been loaded', () => expect(XBlock.initializeBlocks).toHaveBeenCalled());
|
||||
|
||||
it 'detect the histrogram element and convert it', ->
|
||||
expect(window.Histogram).toHaveBeenCalledWith('3', [[0, 1]])
|
||||
return it('detect the histrogram element and convert it', () => expect(window.Histogram).toHaveBeenCalledWith('3', [[0, 1]]));
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,25 +1,33 @@
|
||||
describe 'FeedbackForm', ->
|
||||
beforeEach ->
|
||||
loadFixtures 'coffee/fixtures/feedback_form.html'
|
||||
/*
|
||||
* decaffeinate suggestions:
|
||||
* DS102: Remove unnecessary code created because of implicit returns
|
||||
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
|
||||
*/
|
||||
describe('FeedbackForm', function() {
|
||||
beforeEach(() => loadFixtures('coffee/fixtures/feedback_form.html'));
|
||||
|
||||
describe 'constructor', ->
|
||||
beforeEach ->
|
||||
new FeedbackForm
|
||||
spyOn($, 'postWithPrefix').and.callFake (url, data, callback, format) ->
|
||||
callback()
|
||||
return describe('constructor', function() {
|
||||
beforeEach(function() {
|
||||
new FeedbackForm;
|
||||
return spyOn($, 'postWithPrefix').and.callFake((url, data, callback, format) => callback());
|
||||
});
|
||||
|
||||
it 'post data to /send_feedback on click', ->
|
||||
$('#feedback_subject').val 'Awesome!'
|
||||
$('#feedback_message').val 'This site is really good.'
|
||||
$('#feedback_button').click()
|
||||
it('post data to /send_feedback on click', function() {
|
||||
$('#feedback_subject').val('Awesome!');
|
||||
$('#feedback_message').val('This site is really good.');
|
||||
$('#feedback_button').click();
|
||||
|
||||
expect($.postWithPrefix).toHaveBeenCalledWith '/send_feedback', {
|
||||
subject: 'Awesome!'
|
||||
message: 'This site is really good.'
|
||||
return expect($.postWithPrefix).toHaveBeenCalledWith('/send_feedback', {
|
||||
subject: 'Awesome!',
|
||||
message: 'This site is really good.',
|
||||
url: window.location.href
|
||||
}, jasmine.any(Function), 'json'
|
||||
}, jasmine.any(Function), 'json');
|
||||
});
|
||||
|
||||
it 'replace the form with a thank you message', ->
|
||||
$('#feedback_button').click()
|
||||
return it('replace the form with a thank you message', function() {
|
||||
$('#feedback_button').click();
|
||||
|
||||
expect($('#feedback_div').html()).toEqual 'Feedback submitted. Thank you'
|
||||
return expect($('#feedback_div').html()).toEqual('Feedback submitted. Thank you');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,72 +1,96 @@
|
||||
jasmine.stubbedMetadata =
|
||||
slowerSpeedYoutubeId:
|
||||
id: 'slowerSpeedYoutubeId'
|
||||
/*
|
||||
* decaffeinate suggestions:
|
||||
* DS102: Remove unnecessary code created because of implicit returns
|
||||
* DS207: Consider shorter variations of null checks
|
||||
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
|
||||
*/
|
||||
jasmine.stubbedMetadata = {
|
||||
slowerSpeedYoutubeId: {
|
||||
id: 'slowerSpeedYoutubeId',
|
||||
duration: 300
|
||||
normalSpeedYoutubeId:
|
||||
id: 'normalSpeedYoutubeId'
|
||||
},
|
||||
normalSpeedYoutubeId: {
|
||||
id: 'normalSpeedYoutubeId',
|
||||
duration: 200
|
||||
bogus:
|
||||
},
|
||||
bogus: {
|
||||
duration: 100
|
||||
}
|
||||
};
|
||||
|
||||
jasmine.stubbedCaption =
|
||||
start: [0, 10000, 20000, 30000]
|
||||
jasmine.stubbedCaption = {
|
||||
start: [0, 10000, 20000, 30000],
|
||||
text: ['Caption at 0', 'Caption at 10000', 'Caption at 20000', 'Caption at 30000']
|
||||
};
|
||||
|
||||
jasmine.stubRequests = ->
|
||||
spyOn($, 'ajax').and.callFake (settings) ->
|
||||
if match = settings.url.match /youtube\.com\/.+\/videos\/(.+)\?v=2&alt=jsonc/
|
||||
settings.success data: jasmine.stubbedMetadata[match[1]]
|
||||
else if match = settings.url.match /static\/subs\/(.+)\.srt\.sjson/
|
||||
settings.success jasmine.stubbedCaption
|
||||
else if settings.url.match /modx\/.+\/problem_get$/
|
||||
settings.success html: readFixtures('problem_content.html')
|
||||
else if settings.url == '/calculate' ||
|
||||
jasmine.stubRequests = () =>
|
||||
spyOn($, 'ajax').and.callFake(function(settings) {
|
||||
let match;
|
||||
if (match = settings.url.match(/youtube\.com\/.+\/videos\/(.+)\?v=2&alt=jsonc/)) {
|
||||
return settings.success({data: jasmine.stubbedMetadata[match[1]]});
|
||||
} else if (match = settings.url.match(/static\/subs\/(.+)\.srt\.sjson/)) {
|
||||
return settings.success(jasmine.stubbedCaption);
|
||||
} else if (settings.url.match(/modx\/.+\/problem_get$/)) {
|
||||
return settings.success({html: readFixtures('problem_content.html')});
|
||||
} else if ((settings.url === '/calculate') ||
|
||||
settings.url.match(/modx\/.+\/goto_position$/) ||
|
||||
settings.url.match(/event$/) ||
|
||||
settings.url.match(/modx\/.+\/problem_(check|reset|show|save)$/)
|
||||
# do nothing
|
||||
else
|
||||
throw "External request attempted for #{settings.url}, which is not defined."
|
||||
settings.url.match(/modx\/.+\/problem_(check|reset|show|save)$/)) {
|
||||
// do nothing
|
||||
} else {
|
||||
throw `External request attempted for ${settings.url}, which is not defined.`;
|
||||
}
|
||||
})
|
||||
;
|
||||
|
||||
jasmine.stubYoutubePlayer = ->
|
||||
YT.Player = -> jasmine.createSpyObj 'YT.Player', ['cueVideoById', 'getVideoEmbedCode',
|
||||
jasmine.stubYoutubePlayer = () =>
|
||||
YT.Player = () => jasmine.createSpyObj('YT.Player', ['cueVideoById', 'getVideoEmbedCode',
|
||||
'getCurrentTime', 'getPlayerState', 'getVolume', 'setVolume', 'loadVideoById',
|
||||
'playVideo', 'pauseVideo', 'seekTo']
|
||||
'playVideo', 'pauseVideo', 'seekTo'])
|
||||
;
|
||||
|
||||
jasmine.stubVideoPlayer = (context, enableParts, createPlayer=true) ->
|
||||
enableParts = [enableParts] unless $.isArray(enableParts)
|
||||
jasmine.stubVideoPlayer = function(context, enableParts, createPlayer) {
|
||||
let currentPartName;
|
||||
if (createPlayer == null) { createPlayer = true; }
|
||||
if (!$.isArray(enableParts)) { enableParts = [enableParts]; }
|
||||
|
||||
suite = context.suite
|
||||
currentPartName = suite.description while suite = suite.parentSuite
|
||||
enableParts.push currentPartName
|
||||
let { suite } = context;
|
||||
while ((suite = suite.parentSuite)) { currentPartName = suite.description; }
|
||||
enableParts.push(currentPartName);
|
||||
|
||||
for part in ['VideoCaption', 'VideoSpeedControl', 'VideoVolumeControl', 'VideoProgressSlider']
|
||||
unless $.inArray(part, enableParts) >= 0
|
||||
spyOn window, part
|
||||
for (let part of ['VideoCaption', 'VideoSpeedControl', 'VideoVolumeControl', 'VideoProgressSlider']) {
|
||||
if (!($.inArray(part, enableParts) >= 0)) {
|
||||
spyOn(window, part);
|
||||
}
|
||||
}
|
||||
|
||||
loadFixtures 'video.html'
|
||||
jasmine.stubRequests()
|
||||
YT.Player = undefined
|
||||
context.video = new Video 'example', '.75:slowerSpeedYoutubeId,1.0:normalSpeedYoutubeId'
|
||||
jasmine.stubYoutubePlayer()
|
||||
if createPlayer
|
||||
return new VideoPlayer(video: context.video)
|
||||
loadFixtures('video.html');
|
||||
jasmine.stubRequests();
|
||||
YT.Player = undefined;
|
||||
context.video = new Video('example', '.75:slowerSpeedYoutubeId,1.0:normalSpeedYoutubeId');
|
||||
jasmine.stubYoutubePlayer();
|
||||
if (createPlayer) {
|
||||
return new VideoPlayer({video: context.video});
|
||||
}
|
||||
};
|
||||
|
||||
# Stub Youtube API
|
||||
window.YT =
|
||||
PlayerState:
|
||||
UNSTARTED: -1
|
||||
ENDED: 0
|
||||
PLAYING: 1
|
||||
PAUSED: 2
|
||||
BUFFERING: 3
|
||||
// Stub Youtube API
|
||||
window.YT = {
|
||||
PlayerState: {
|
||||
UNSTARTED: -1,
|
||||
ENDED: 0,
|
||||
PLAYING: 1,
|
||||
PAUSED: 2,
|
||||
BUFFERING: 3,
|
||||
CUED: 5
|
||||
}
|
||||
};
|
||||
|
||||
# Stub jQuery.cookie
|
||||
$.cookie = jasmine.createSpy('jQuery.cookie').and.returnValue '1.0'
|
||||
// Stub jQuery.cookie
|
||||
$.cookie = jasmine.createSpy('jQuery.cookie').and.returnValue('1.0');
|
||||
|
||||
# Stub jQuery.qtip
|
||||
$.fn.qtip = jasmine.createSpy 'jQuery.qtip'
|
||||
// Stub jQuery.qtip
|
||||
$.fn.qtip = jasmine.createSpy('jQuery.qtip');
|
||||
|
||||
# Stub jQuery.scrollTo
|
||||
$.fn.scrollTo = jasmine.createSpy 'jQuery.scrollTo'
|
||||
// Stub jQuery.scrollTo
|
||||
$.fn.scrollTo = jasmine.createSpy('jQuery.scrollTo');
|
||||
|
||||
@@ -1,54 +1,72 @@
|
||||
describe 'Histogram', ->
|
||||
beforeEach ->
|
||||
spyOn $, 'plot'
|
||||
/*
|
||||
* decaffeinate suggestions:
|
||||
* DS102: Remove unnecessary code created because of implicit returns
|
||||
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
|
||||
*/
|
||||
describe('Histogram', function() {
|
||||
beforeEach(() => spyOn($, 'plot'));
|
||||
|
||||
describe 'constructor', ->
|
||||
it 'instantiate the data arrays', ->
|
||||
histogram = new Histogram 1, []
|
||||
expect(histogram.xTicks).toEqual []
|
||||
expect(histogram.yTicks).toEqual []
|
||||
expect(histogram.data).toEqual []
|
||||
describe('constructor', () =>
|
||||
it('instantiate the data arrays', function() {
|
||||
const histogram = new Histogram(1, []);
|
||||
expect(histogram.xTicks).toEqual([]);
|
||||
expect(histogram.yTicks).toEqual([]);
|
||||
return expect(histogram.data).toEqual([]);
|
||||
})
|
||||
);
|
||||
|
||||
describe 'calculate', ->
|
||||
beforeEach ->
|
||||
@histogram = new Histogram(1, [[null, 1], [1, 1], [2, 2], [3, 3]])
|
||||
describe('calculate', function() {
|
||||
beforeEach(function() {
|
||||
return this.histogram = new Histogram(1, [[null, 1], [1, 1], [2, 2], [3, 3]]);
|
||||
});
|
||||
|
||||
it 'store the correct value for data', ->
|
||||
expect(@histogram.data).toEqual [[1, Math.log(2)], [2, Math.log(3)], [3, Math.log(4)]]
|
||||
it('store the correct value for data', function() {
|
||||
return expect(this.histogram.data).toEqual([[1, Math.log(2)], [2, Math.log(3)], [3, Math.log(4)]]);
|
||||
});
|
||||
|
||||
it 'store the correct value for x ticks', ->
|
||||
expect(@histogram.xTicks).toEqual [[1, '1'], [2, '2'], [3, '3']]
|
||||
it('store the correct value for x ticks', function() {
|
||||
return expect(this.histogram.xTicks).toEqual([[1, '1'], [2, '2'], [3, '3']]);
|
||||
});
|
||||
|
||||
it 'store the correct value for y ticks', ->
|
||||
expect(@histogram.yTicks).toEqual
|
||||
return it('store the correct value for y ticks', function() {
|
||||
return expect(this.histogram.yTicks).toEqual;
|
||||
});
|
||||
});
|
||||
|
||||
describe 'render', ->
|
||||
it 'call flot with correct option', ->
|
||||
new Histogram(1, [[1, 1], [2, 2], [3, 3]])
|
||||
return describe('render', () =>
|
||||
it('call flot with correct option', function() {
|
||||
new Histogram(1, [[1, 1], [2, 2], [3, 3]]);
|
||||
|
||||
firstArg = $.plot.calls.mostRecent().args[0]
|
||||
secondArg = $.plot.calls.mostRecent().args[1]
|
||||
thirdArg = $.plot.calls.mostRecent().args[2]
|
||||
const firstArg = $.plot.calls.mostRecent().args[0];
|
||||
const secondArg = $.plot.calls.mostRecent().args[1];
|
||||
const thirdArg = $.plot.calls.mostRecent().args[2];
|
||||
|
||||
expect(firstArg.selector).toEqual($("#histogram_1").selector)
|
||||
expect(secondArg).toEqual([
|
||||
data: [[1, Math.log(2)], [2, Math.log(3)], [3, Math.log(4)]]
|
||||
bars:
|
||||
show: true
|
||||
align: 'center'
|
||||
lineWidth: 0
|
||||
expect(firstArg.selector).toEqual($("#histogram_1").selector);
|
||||
expect(secondArg).toEqual([{
|
||||
data: [[1, Math.log(2)], [2, Math.log(3)], [3, Math.log(4)]],
|
||||
bars: {
|
||||
show: true,
|
||||
align: 'center',
|
||||
lineWidth: 0,
|
||||
fill: 1.0
|
||||
},
|
||||
color: "#b72121"
|
||||
])
|
||||
expect(thirdArg).toEqual(
|
||||
xaxis:
|
||||
min: -1
|
||||
max: 4
|
||||
ticks: [[1, '1'], [2, '2'], [3, '3']]
|
||||
}
|
||||
]);
|
||||
return expect(thirdArg).toEqual({
|
||||
xaxis: {
|
||||
min: -1,
|
||||
max: 4,
|
||||
ticks: [[1, '1'], [2, '2'], [3, '3']],
|
||||
tickLength: 0
|
||||
yaxis:
|
||||
min: 0.0
|
||||
max: Math.log(4) * 1.1
|
||||
ticks: [[Math.log(2), '1'], [Math.log(3), '2'], [Math.log(4), '3']]
|
||||
},
|
||||
yaxis: {
|
||||
min: 0.0,
|
||||
max: Math.log(4) * 1.1,
|
||||
ticks: [[Math.log(2), '1'], [Math.log(3), '2'], [Math.log(4), '3']],
|
||||
labelWidth: 50
|
||||
)
|
||||
}
|
||||
});
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
@@ -1,44 +1,61 @@
|
||||
describe 'Tab', ->
|
||||
beforeEach ->
|
||||
loadFixtures 'coffee/fixtures/tab.html'
|
||||
@items = $.parseJSON readFixtures('coffee/fixtures/items.json')
|
||||
/*
|
||||
* decaffeinate suggestions:
|
||||
* DS102: Remove unnecessary code created because of implicit returns
|
||||
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
|
||||
*/
|
||||
describe('Tab', function() {
|
||||
beforeEach(function() {
|
||||
loadFixtures('coffee/fixtures/tab.html');
|
||||
return this.items = $.parseJSON(readFixtures('coffee/fixtures/items.json'));
|
||||
});
|
||||
|
||||
describe 'constructor', ->
|
||||
beforeEach ->
|
||||
spyOn($.fn, 'tabs')
|
||||
@tab = new Tab 1, @items
|
||||
describe('constructor', function() {
|
||||
beforeEach(function() {
|
||||
spyOn($.fn, 'tabs');
|
||||
return this.tab = new Tab(1, this.items);
|
||||
});
|
||||
|
||||
it 'set the element', ->
|
||||
expect(@tab.el).toEqual $('#tab_1')
|
||||
it('set the element', function() {
|
||||
return expect(this.tab.el).toEqual($('#tab_1'));
|
||||
});
|
||||
|
||||
it 'build the tabs', ->
|
||||
links = $('.navigation li>a').map(-> $(this).attr('href')).get()
|
||||
expect(links).toEqual ['#tab-1-0', '#tab-1-1', '#tab-1-2']
|
||||
it('build the tabs', function() {
|
||||
const links = $('.navigation li>a').map(function() { return $(this).attr('href'); }).get();
|
||||
return expect(links).toEqual(['#tab-1-0', '#tab-1-1', '#tab-1-2']);
|
||||
});
|
||||
|
||||
it 'build the container', ->
|
||||
containers = $('section').map(-> $(this).attr('id')).get()
|
||||
expect(containers).toEqual ['tab-1-0', 'tab-1-1', 'tab-1-2']
|
||||
it('build the container', function() {
|
||||
const containers = $('section').map(function() { return $(this).attr('id'); }).get();
|
||||
return expect(containers).toEqual(['tab-1-0', 'tab-1-1', 'tab-1-2']);
|
||||
});
|
||||
|
||||
it 'bind the tabs', ->
|
||||
expect($.fn.tabs).toHaveBeenCalledWith show: @tab.onShow
|
||||
return it('bind the tabs', function() {
|
||||
return expect($.fn.tabs).toHaveBeenCalledWith({show: this.tab.onShow});
|
||||
});
|
||||
});
|
||||
|
||||
# As of jQuery 1.9, the onShow callback is deprecated
|
||||
# http://jqueryui.com/upgrade-guide/1.9/#deprecated-show-event-renamed-to-activate
|
||||
# The code below tests that onShow does what is expected,
|
||||
# but note that onShow will NOT be called when the user
|
||||
# clicks on the tab if we're using jQuery version >= 1.9
|
||||
describe 'onShow', ->
|
||||
beforeEach ->
|
||||
@tab = new Tab 1, @items
|
||||
@tab.onShow($('#tab-1-0'), {'index': 1})
|
||||
// As of jQuery 1.9, the onShow callback is deprecated
|
||||
// http://jqueryui.com/upgrade-guide/1.9/#deprecated-show-event-renamed-to-activate
|
||||
// The code below tests that onShow does what is expected,
|
||||
// but note that onShow will NOT be called when the user
|
||||
// clicks on the tab if we're using jQuery version >= 1.9
|
||||
return describe('onShow', function() {
|
||||
beforeEach(function() {
|
||||
this.tab = new Tab(1, this.items);
|
||||
return this.tab.onShow($('#tab-1-0'), {'index': 1});
|
||||
});
|
||||
|
||||
it 'replace content in the container', ->
|
||||
@tab.onShow($('#tab-1-1'), {'index': 1})
|
||||
expect($('#tab-1-0').html()).toEqual ''
|
||||
expect($('#tab-1-1').html()).toEqual 'Video 2'
|
||||
expect($('#tab-1-2').html()).toEqual ''
|
||||
it('replace content in the container', function() {
|
||||
this.tab.onShow($('#tab-1-1'), {'index': 1});
|
||||
expect($('#tab-1-0').html()).toEqual('');
|
||||
expect($('#tab-1-1').html()).toEqual('Video 2');
|
||||
return expect($('#tab-1-2').html()).toEqual('');
|
||||
});
|
||||
|
||||
it 'trigger contentChanged event on the element', ->
|
||||
spyOnEvent @tab.el, 'contentChanged'
|
||||
@tab.onShow($('#tab-1-1'), {'index': 1})
|
||||
expect('contentChanged').toHaveBeenTriggeredOn @tab.el
|
||||
return it('trigger contentChanged event on the element', function() {
|
||||
spyOnEvent(this.tab.el, 'contentChanged');
|
||||
this.tab.onShow($('#tab-1-1'), {'index': 1});
|
||||
return expect('contentChanged').toHaveBeenTriggeredOn(this.tab.el);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,95 +1,114 @@
|
||||
describe "RequireJS namespacing", ->
|
||||
beforeEach ->
|
||||
/*
|
||||
* decaffeinate suggestions:
|
||||
* DS102: Remove unnecessary code created because of implicit returns
|
||||
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
|
||||
*/
|
||||
describe("RequireJS namespacing", function() {
|
||||
beforeEach(() =>
|
||||
|
||||
# Jasmine does not provide a way to use the typeof operator. We need
|
||||
# to create our own custom matchers so that a TypeError is not thrown.
|
||||
jasmine.addMatchers
|
||||
requirejsTobeUndefined: ->
|
||||
{
|
||||
compare: ->
|
||||
{
|
||||
pass: typeof requirejs is "undefined"
|
||||
}
|
||||
// Jasmine does not provide a way to use the typeof operator. We need
|
||||
// to create our own custom matchers so that a TypeError is not thrown.
|
||||
jasmine.addMatchers({
|
||||
requirejsTobeUndefined() {
|
||||
return {
|
||||
compare() {
|
||||
return {
|
||||
pass: typeof requirejs === "undefined"
|
||||
};
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
requireTobeUndefined: ->
|
||||
{
|
||||
compare: ->
|
||||
{
|
||||
pass: typeof require is "undefined"
|
||||
}
|
||||
requireTobeUndefined() {
|
||||
return {
|
||||
compare() {
|
||||
return {
|
||||
pass: typeof require === "undefined"
|
||||
};
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
defineTobeUndefined: ->
|
||||
{
|
||||
compare: ->
|
||||
{
|
||||
pass: typeof define is "undefined"
|
||||
}
|
||||
defineTobeUndefined() {
|
||||
return {
|
||||
compare() {
|
||||
return {
|
||||
pass: typeof define === "undefined"
|
||||
};
|
||||
}
|
||||
};
|
||||
}}));
|
||||
|
||||
|
||||
it "check that the RequireJS object is present in the global namespace", ->
|
||||
expect(RequireJS).toEqual jasmine.any(Object)
|
||||
expect(window.RequireJS).toEqual jasmine.any(Object)
|
||||
it("check that the RequireJS object is present in the global namespace", function() {
|
||||
expect(RequireJS).toEqual(jasmine.any(Object));
|
||||
return expect(window.RequireJS).toEqual(jasmine.any(Object));
|
||||
});
|
||||
|
||||
it "check that requirejs(), require(), and define() are not in the global namespace", ->
|
||||
return it("check that requirejs(), require(), and define() are not in the global namespace", function() {
|
||||
|
||||
# The custom matchers that we defined in the beforeEach() function do
|
||||
# not operate on an object. We pass a dummy empty object {} not to
|
||||
# confuse Jasmine.
|
||||
expect({}).requirejsTobeUndefined()
|
||||
expect({}).requireTobeUndefined()
|
||||
expect({}).defineTobeUndefined()
|
||||
expect(window.requirejs).not.toBeDefined()
|
||||
expect(window.require).not.toBeDefined()
|
||||
expect(window.define).not.toBeDefined()
|
||||
// The custom matchers that we defined in the beforeEach() function do
|
||||
// not operate on an object. We pass a dummy empty object {} not to
|
||||
// confuse Jasmine.
|
||||
expect({}).requirejsTobeUndefined();
|
||||
expect({}).requireTobeUndefined();
|
||||
expect({}).defineTobeUndefined();
|
||||
expect(window.requirejs).not.toBeDefined();
|
||||
expect(window.require).not.toBeDefined();
|
||||
return expect(window.define).not.toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
describe "RequireJS module creation", ->
|
||||
inDefineCallback = undefined
|
||||
inRequireCallback = undefined
|
||||
it "check that we can use RequireJS to define() and require() a module", (done) ->
|
||||
d1 = $.Deferred()
|
||||
d2 = $.Deferred()
|
||||
# Because Require JS works asynchronously when defining and requiring
|
||||
# modules, we need to use the special Jasmine functions runs(), and
|
||||
# waitsFor() to set up this test.
|
||||
func = () ->
|
||||
describe("RequireJS module creation", function() {
|
||||
let inDefineCallback = undefined;
|
||||
let inRequireCallback = undefined;
|
||||
return it("check that we can use RequireJS to define() and require() a module", function(done) {
|
||||
const d1 = $.Deferred();
|
||||
const d2 = $.Deferred();
|
||||
// Because Require JS works asynchronously when defining and requiring
|
||||
// modules, we need to use the special Jasmine functions runs(), and
|
||||
// waitsFor() to set up this test.
|
||||
const func = function() {
|
||||
|
||||
# Initialize the variable that we will test for. They will be set
|
||||
# to true in the appropriate callback functions called by Require
|
||||
# JS. If their values do not change, this will mean that something
|
||||
# is not working as is intended.
|
||||
inDefineCallback = false
|
||||
inRequireCallback = false
|
||||
// Initialize the variable that we will test for. They will be set
|
||||
// to true in the appropriate callback functions called by Require
|
||||
// JS. If their values do not change, this will mean that something
|
||||
// is not working as is intended.
|
||||
inDefineCallback = false;
|
||||
inRequireCallback = false;
|
||||
|
||||
# Define our test module.
|
||||
RequireJS.define "test_module", [], ->
|
||||
inDefineCallback = true
|
||||
// Define our test module.
|
||||
RequireJS.define("test_module", [], function() {
|
||||
inDefineCallback = true;
|
||||
|
||||
d1.resolve()
|
||||
d1.resolve();
|
||||
|
||||
# This module returns an object. It can be accessed via the
|
||||
# Require JS require() function.
|
||||
module_status: "OK"
|
||||
// This module returns an object. It can be accessed via the
|
||||
// Require JS require() function.
|
||||
return {module_status: "OK"};
|
||||
});
|
||||
|
||||
|
||||
# Require our defined test module.
|
||||
RequireJS.require ["test_module"], (test_module) ->
|
||||
inRequireCallback = true
|
||||
// Require our defined test module.
|
||||
return RequireJS.require(["test_module"], function(test_module) {
|
||||
inRequireCallback = true;
|
||||
|
||||
# If our test module was defined properly, then we should
|
||||
# be able to get the object it returned, and query some
|
||||
# property.
|
||||
expect(test_module.module_status).toBe "OK"
|
||||
// If our test module was defined properly, then we should
|
||||
// be able to get the object it returned, and query some
|
||||
// property.
|
||||
expect(test_module.module_status).toBe("OK");
|
||||
|
||||
d2.resolve()
|
||||
return d2.resolve();
|
||||
});
|
||||
};
|
||||
|
||||
func()
|
||||
# We will wait before checking if our module was defined and that we were able to require() the module.
|
||||
$.when(d1, d2).done(->
|
||||
# The final test behavior
|
||||
expect(inDefineCallback).toBeTruthy()
|
||||
expect(inRequireCallback).toBeTruthy()
|
||||
).always(done)
|
||||
func();
|
||||
// We will wait before checking if our module was defined and that we were able to require() the module.
|
||||
return $.when(d1, d2).done(function() {
|
||||
// The final test behavior
|
||||
expect(inDefineCallback).toBeTruthy();
|
||||
return expect(inRequireCallback).toBeTruthy();
|
||||
}).always(done);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user