Unverified Commit 64dd633f by Calen Pennington Committed by GitHub

Merge pull request #15846 from edx/ormsbee/decaffeinate_tests_2

WIP: Decaffeinate Test Files (Part 2)
parents 6635deec 94614db4
define ["js/models/course"], (Course) ->
describe "Course", ->
describe "basic", ->
beforeEach ->
@model = new Course({
name: "Greek Hero"
})
it "should take a name argument", ->
expect(@model.get("name")).toEqual("Greek Hero")
define(["js/models/course"], Course =>
describe("Course", () =>
describe("basic", function() {
beforeEach(function() {
this.model = new Course({
name: "Greek Hero"
});
});
it("should take a name argument", function() {
expect(this.model.get("name")).toEqual("Greek Hero");
});
})
)
);
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()
model = new Metadata(
{'value': 'original', 'explicitly_set': true})
model.setValue('original')
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()
model = new Metadata(
{'value': 'original', 'explicitly_set': true})
model.setValue('modified')
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 "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 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 getter for field name", ->
model = new Metadata({'field_name': 'foo'})
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 type", ->
model = new Metadata({'type': 'Integer'})
expect(model.getType()).toBe(Metadata.INTEGER_TYPE)
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();
});
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();
});
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');
expect(model.isExplicitlySet()).toBeTruthy();
});
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');
expect(model.getDisplayValue()).toBe('modified');
});
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');
expect(model.isExplicitlySet()).toBeFalsy();
});
it("has a getter for field name", function() {
const model = new Metadata({'field_name': 'foo'});
expect(model.getFieldName()).toBe('foo');
});
it("has a getter for options", function() {
const model = new Metadata({'options': ['foo', 'bar']});
expect(model.getOptions()).toEqual(['foo', 'bar']);
});
it("has a getter for type", function() {
const model = new Metadata({'type': 'Integer'});
expect(model.getType()).toBe(Metadata.INTEGER_TYPE);
});
})
);
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
name: "Life, the Universe, and Everything"
})
it "should take an id argument", ->
expect(@model.get("id")).toEqual(42)
it "should take a name argument", ->
expect(@model.get("name")).toEqual("Life, the Universe, and Everything")
it "should have a URL set", ->
expect(@model.url()).toEqual(ModuleUtils.getUpdateUrl(42))
it "should serialize to JSON correctly", ->
expect(@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
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"}, "{}"])
@model.save()
expect(Section.prototype.showNotification).toHaveBeenCalled()
server.respond()
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"}, "{}"])
@model.save()
server.respond()
expect(Section.prototype.hideNotification).not.toHaveBeenCalled()
/*
* 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() {
this.model = new Section({
id: 42,
name: "Life, the Universe, and Everything"
});
});
it("should take an id argument", function() {
expect(this.model.get("id")).toEqual(42);
});
it("should take a name argument", function() {
expect(this.model.get("name")).toEqual("Life, the Universe, and Everything");
});
it("should have a URL set", function() {
expect(this.model.url()).toEqual(ModuleUtils.getUpdateUrl(42));
});
it("should serialize to JSON correctly", function() {
expect(this.model.toJSON()).toEqual({
metadata:
{
display_name: "Life, the Universe, and Everything"
}
});
});
});
describe("XHR", function() {
beforeEach(function() {
spyOn(Section.prototype, 'showNotification');
spyOn(Section.prototype, 'hideNotification');
this.model = new Section({
id: 42,
name: "Life, the Universe, and Everything"
});
});
it("show/hide a notification when it saves to the server", function() {
const server = AjaxHelpers.server([200, {"Content-Type": "application/json"}, "{}"]);
this.model.save();
expect(Section.prototype.showNotification).toHaveBeenCalled();
server.respond();
expect(Section.prototype.hideNotification).toHaveBeenCalled();
});
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"}, "{}"]);
this.model.save();
server.respond();
expect(Section.prototype.hideNotification).not.toHaveBeenCalled();
});
});
})
);
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)
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 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 "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 "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.')
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);
expect(model.get('drop_count')).toBe(2);
});
it("converts float value of weight to an integer with rounding", function() {
const model = new CourseGrader({weight: 28.999999999999996}, {parse:true});
expect(model.get('weight')).toBe(29);
});
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);
expect(model.get('drop_count')).toBe(2);
});
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);
expect(model.get('drop_count')).toBe(1);
});
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.');
expect(errors.drop_count).toBe('Please enter non-negative integer.');
});
})
)
);
define ["underscore", "js/models/settings/course_grading_policy"], (_, CourseGradingPolicy) ->
describe "CourseGradingPolicy", ->
beforeEach ->
@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(
hours: 0,
minutes: 0
)
describe "parseGracePeriod", ->
it "parses a time in HH:MM format", ->
time = @model.parseGracePeriod("07:19")
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)
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')
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)
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", function() {
const attrs = this.model.parse({grace_period: null});
expect(attrs.grace_period).toEqual({
hours: 0,
minutes: 0
});
})
);
describe("parseGracePeriod", function() {
it("parses a time in HH:MM format", function() {
const time = this.model.parseGracePeriod("07:19");
expect(time).toEqual({
hours: 7,
minutes: 19
});
});
it("returns null on an incorrectly formatted string", function() {
expect(this.model.parseGracePeriod("asdf")).toBe(null);
expect(this.model.parseGracePeriod("7:19")).toBe(null);
expect(this.model.parseGracePeriod("1000:00")).toBe(null);
});
});
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});
expect(_.keys(this.model.validationError)).toContain('minimum_grade_credit');
});
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});
expect(this.model.validationError).toBe(null);
});
});
})
);
define ["backbone", "js/models/textbook", "js/collections/textbook", "js/models/chapter", "js/collections/chapter", "cms/js/main"],
(Backbone, Textbook, TextbookSet, Chapter, ChapterSet, main) ->
describe "Textbook model", ->
beforeEach ->
main()
@model = new Textbook()
CMS.URL.TEXTBOOKS = "/textbooks"
afterEach ->
delete CMS.URL.TEXTBOOKS
describe "Basic", ->
it "should have an empty name by default", ->
expect(@model.get("name")).toEqual("")
it "should not show chapters by default", ->
expect(@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 be empty by default", ->
expect(@model.isEmpty()).toBeTruthy()
it "should have a URL root", ->
urlRoot = _.result(@model, 'urlRoot')
expect(urlRoot).toBeTruthy()
it "should be able to reset itself", ->
@model.set("name", "foobar")
@model.reset()
expect(@model.get("name")).toEqual("")
it "should not be dirty by default", ->
expect(@model.isDirty()).toBeFalsy()
it "should be dirty after it's been changed", ->
@model.set("name", "foobar")
expect(@model.isDirty()).toBeTruthy()
it "should not be dirty after calling setOriginalAttributes", ->
@model.set("name", "foobar")
@model.setOriginalAttributes()
expect(@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
it "should match server model to client model", ->
serverModelSpec = {
"tab_title": "My Textbook",
"chapters": [
{"title": "Chapter 1", "url": "/ch1.pdf"},
{"title": "Chapter 2", "url": "/ch2.pdf"},
]
}
clientModelSpec = {
"name": "My Textbook",
"showChapters": false,
"editing": false,
"chapters": [{
"name": "Chapter 1",
"asset_path": "/ch1.pdf",
"order": 1
}, {
"name": "Chapter 2",
"asset_path": "/ch2.pdf",
"order": 2
}
]
}
model = new Textbook(serverModelSpec, {parse: true})
expect(deepAttributes(model)).toEqual(clientModelSpec)
expect(model.toJSON()).toEqual(serverModelSpec)
describe "Validation", ->
it "requires a name", ->
model = new Textbook({name: ""})
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 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 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 "can pass validation", ->
chapter = new Chapter()
chapter.isValid = -> true
model = new Textbook({name: "foo"})
model.get("chapters").reset([chapter])
expect(model.isValid()).toBeTruthy()
describe "Textbook collection", ->
beforeEach ->
CMS.URL.TEXTBOOKS = "/textbooks"
@collection = new TextbookSet()
afterEach ->
delete CMS.URL.TEXTBOOKS
it "should have a url set", ->
url = _.result(@collection, 'url')
expect(url).toEqual("/textbooks")
describe "Chapter model", ->
beforeEach ->
@model = new Chapter()
describe "Basic", ->
it "should have a name by default", ->
expect(@model.get("name")).toEqual("")
it "should have an asset_path by default", ->
expect(@model.get("asset_path")).toEqual("")
it "should have an order by default", ->
expect(@model.get("order")).toEqual(1)
it "should be empty by default", ->
expect(@model.isEmpty()).toBeTruthy()
describe "Validation", ->
it "requires a name", ->
model = new Chapter({name: "", asset_path: "a.pdf"})
expect(model.isValid()).toBeFalsy()
it "requires an asset_path", ->
model = new Chapter({name: "a", asset_path: ""})
expect(model.isValid()).toBeFalsy()
it "can pass validation", ->
model = new Chapter({name: "a", asset_path: "a.pdf"})
expect(model.isValid()).toBeTruthy()
describe "Chapter collection", ->
beforeEach ->
@collection = new ChapterSet()
it "is empty by default", ->
expect(@collection.isEmpty()).toBeTruthy()
it "is empty if all chapters are empty", ->
@collection.add([{}, {}, {}])
expect(@collection.isEmpty()).toBeTruthy()
it "is not empty if a chapter is not empty", ->
@collection.add([{}, {name: "full"}, {}])
expect(@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)
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", function() {
beforeEach(function() {
main();
this.model = new Textbook();
CMS.URL.TEXTBOOKS = "/textbooks";
});
afterEach(() => delete CMS.URL.TEXTBOOKS);
describe("Basic", function() {
it("should have an empty name by default", function() {
expect(this.model.get("name")).toEqual("");
});
it("should not show chapters by default", function() {
expect(this.model.get("showChapters")).toBeFalsy();
});
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);
expect(chapters.at(0).isEmpty()).toBeTruthy();
});
it("should be empty by default", function() {
expect(this.model.isEmpty()).toBeTruthy();
});
it("should have a URL root", function() {
const urlRoot = _.result(this.model, 'urlRoot');
expect(urlRoot).toBeTruthy();
});
it("should be able to reset itself", function() {
this.model.set("name", "foobar");
this.model.reset();
expect(this.model.get("name")).toEqual("");
});
it("should not be dirty by default", function() {
expect(this.model.isDirty()).toBeFalsy();
});
it("should be dirty after it's been changed", function() {
this.model.set("name", "foobar");
expect(this.model.isDirty()).toBeTruthy();
});
it("should not be dirty after calling setOriginalAttributes", function() {
this.model.set("name", "foobar");
this.model.setOriginalAttributes();
expect(this.model.isDirty()).toBeFalsy();
});
});
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", function() {
const serverModelSpec = {
"tab_title": "My Textbook",
"chapters": [
{"title": "Chapter 1", "url": "/ch1.pdf"},
{"title": "Chapter 2", "url": "/ch2.pdf"},
]
};
const clientModelSpec = {
"name": "My Textbook",
"showChapters": false,
"editing": false,
"chapters": [{
"name": "Chapter 1",
"asset_path": "/ch1.pdf",
"order": 1
}, {
"name": "Chapter 2",
"asset_path": "/ch2.pdf",
"order": 2
}
]
};
const model = new Textbook(serverModelSpec, {parse: true});
expect(deepAttributes(model)).toEqual(clientModelSpec);
expect(model.toJSON()).toEqual(serverModelSpec);
});
});
describe("Validation", function() {
it("requires a name", function() {
const model = new Textbook({name: ""});
expect(model.isValid()).toBeFalsy();
});
it("requires at least one chapter", function() {
const model = new Textbook({name: "foo"});
model.get("chapters").reset();
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]);
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]);
expect(model.isValid()).toBeFalsy();
});
it("can pass validation", function() {
const chapter = new Chapter();
chapter.isValid = () => true;
const model = new Textbook({name: "foo"});
model.get("chapters").reset([chapter]);
expect(model.isValid()).toBeTruthy();
});
});
});
describe("Textbook collection", function() {
beforeEach(function() {
CMS.URL.TEXTBOOKS = "/textbooks";
this.collection = new TextbookSet();
});
afterEach(() => delete CMS.URL.TEXTBOOKS);
it("should have a url set", function() {
const url = _.result(this.collection, 'url');
expect(url).toEqual("/textbooks");
});
});
describe("Chapter model", function() {
beforeEach(function() {
this.model = new Chapter();
});
describe("Basic", function() {
it("should have a name by default", function() {
expect(this.model.get("name")).toEqual("");
});
it("should have an asset_path by default", function() {
expect(this.model.get("asset_path")).toEqual("");
});
it("should have an order by default", function() {
expect(this.model.get("order")).toEqual(1);
});
it("should be empty by default", function() {
expect(this.model.isEmpty()).toBeTruthy();
});
});
describe("Validation", function() {
it("requires a name", function() {
const model = new Chapter({name: "", asset_path: "a.pdf"});
expect(model.isValid()).toBeFalsy();
});
it("requires an asset_path", function() {
const model = new Chapter({name: "a", asset_path: ""});
expect(model.isValid()).toBeFalsy();
});
it("can pass validation", function() {
const model = new Chapter({name: "a", asset_path: "a.pdf"});
expect(model.isValid()).toBeTruthy();
});
});
});
describe("Chapter collection", function() {
beforeEach(function() {
this.collection = new ChapterSet();
});
it("is empty by default", function() {
expect(this.collection.isEmpty()).toBeTruthy();
});
it("is empty if all chapters are empty", function() {
this.collection.add([{}, {}, {}]);
expect(this.collection.isEmpty()).toBeTruthy();
});
it("is not empty if a chapter is not empty", function() {
this.collection.add([{}, {name: "full"}, {}]);
expect(this.collection.isEmpty()).toBeFalsy();
});
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());
expect(this.collection.nextOrder()).toEqual(2);
});
});
});
define ["js/models/uploads"], (FileUpload) ->
describe "FileUpload", ->
beforeEach ->
@model = new FileUpload()
it "is unfinished by default", ->
expect(@model.get("finished")).toBeFalsy()
it "is not uploading by default", ->
expect(@model.get("uploading")).toBeFalsy()
it "is valid by default", ->
expect(@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 PNG files by default", ->
file = {"type": "image/png", "name": "filename.png"}
@model.set("selectedFile", file);
expect(@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 format when explicitly set", ->
file = {"type": "", "name": "filename.png"}
@model.set("fileFormats": ["png"])
@model.set("selectedFile", file)
expect(@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 formats", ->
file = {"type": "image/gif", "name": "filename.gif"}
@model.set("fileFormats": ["png", "jpeg", "gif"])
@model.set("selectedFile", file)
expect(@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 "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(
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(
fileTypes: 'PDF',
fileExtensions: '.pdf'
)
define(["js/models/uploads"], FileUpload =>
describe("FileUpload", function() {
beforeEach(function() {
this.model = new FileUpload();
});
it("is unfinished by default", function() {
expect(this.model.get("finished")).toBeFalsy();
});
it("is not uploading by default", function() {
expect(this.model.get("uploading")).toBeFalsy();
});
it("is valid by default", function() {
expect(this.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);
expect(this.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);
expect(this.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);
expect(this.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);
expect(this.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);
expect(this.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);
expect(this.model.isValid()).toBeTruthy();
});
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']);
expect(this.model.fileTypes()).toEqual(['PNG', 'JSON', 'GIF', 'SRT']);
})
);
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();
expect(formatted).toEqual({
fileTypes: 'PNG, JPEG or JSON',
fileExtensions: '.png, .jpeg or .json'
});
});
it("does not format with only one mime type", function() {
this.model.set('mimeTypes', ['application/pdf']);
const formatted = this.model.formatValidTypes();
expect(formatted).toEqual({
fileTypes: 'PDF',
fileExtensions: '.pdf'
});
});
});
})
);
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) ->
describe "UploadDialog", ->
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(
mimeTypes: ['application/pdf']
)
@dialogResponse = dialogResponse = []
@mockFiles = []
afterEach ->
delete CMS.URL.UPLOAD_ASSET
modal_helpers.cancelModalIfShowing()
createTestView = (test) ->
view = new UploadDialog(
model: test.model,
url: CMS.URL.UPLOAD_ASSET,
onSuccess: (response) =>
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
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")
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 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 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()
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)
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")
describe "Uploads", ->
beforeEach ->
@clock = sinon.useFakeTimers()
afterEach ->
modal_helpers.cancelModalIfShowing()
@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 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 "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"})
expect(modal_helpers.isShowingModal(view)).toBeTruthy();
@clock.tick(2001)
expect(modal_helpers.isShowingModal(view)).toBeFalsy();
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) =>
describe("UploadDialog", function() {
const tpl = readFixtures("upload-dialog.underscore");
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']
});
this.dialogResponse = (dialogResponse = []);
this.mockFiles = [];});
afterEach(function() {
delete CMS.URL.UPLOAD_ASSET;
modal_helpers.cancelModalIfShowing();
});
const createTestView = function(test) {
const view = new UploadDialog({
model: test.model,
url: CMS.URL.UPLOAD_ASSET,
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
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);
}
});
this.lastView = view;
return view;
};
describe("Basic", function() {
it("should render without a file selected", function() {
const view = createTestView(this);
view.render();
expect(view.$el).toContainElement("input[type=file]");
expect(view.$(".action-upload")).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");
expect(view.$(".action-upload")).not.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");
expect(view.$(".action-upload")).toHaveClass("disabled");
});
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");
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");
});
});
describe("Uploads", function() {
beforeEach(function() {
this.clock = sinon.useFakeTimers();
});
afterEach(function() {
modal_helpers.cancelModalIfShowing();
this.clock.restore();
});
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();
expect(this.dialogResponse.pop()).toEqual("dummy_response");
});
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/);
expect(view.remove).not.toHaveBeenCalled();
});
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();
this.clock.tick(2001);
expect(modal_helpers.isShowingModal(view)).toBeFalsy();
});
});
})
);
test_problem_display.js
test_problem_generator.js
test_problem_grader.js
xproblem.js
\ No newline at end of file
class MinimaxProblemDisplay extends XProblemDisplay
constructor: (@state, @submission, @evaluation, @container, @submissionField, @parameters={}) ->
super(@state, @submission, @evaluation, @container, @submissionField, @parameters)
render: () ->
createSubmission: () ->
@newSubmission = {}
if @submission?
for id, value of @submission
@newSubmission[id] = value
getCurrentSubmission: () ->
return @newSubmission
root = exports ? this
root.TestProblemDisplay = TestProblemDisplay
/*
* decaffeinate suggestions:
* DS001: Remove Babel/TypeScript constructor workaround
* DS102: Remove unnecessary code created because of implicit returns
* DS205: Consider reworking code to avoid use of IIFEs
* DS207: Consider shorter variations of null checks
* DS208: Avoid top-level this
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
*/
class MinimaxProblemDisplay extends XProblemDisplay {
constructor(state, submission, evaluation, container, submissionField, parameters) {
{
// Hack: trick Babel/TypeScript into allowing this before super.
if (false) { super(); }
let thisFn = (() => { this; }).toString();
let thisName = thisFn.slice(thisFn.indexOf('{') + 1, thisFn.indexOf(';')).trim();
eval(`${thisName} = this;`);
}
this.state = state;
this.submission = submission;
this.evaluation = evaluation;
this.container = container;
this.submissionField = submissionField;
if (parameters == null) { parameters = {}; }
this.parameters = parameters;
super(this.state, this.submission, this.evaluation, this.container, this.submissionField, this.parameters);
}
render() {}
createSubmission() {
this.newSubmission = {};
if (this.submission != null) {
return (() => {
const result = [];
for (let id in this.submission) {
const value = this.submission[id];
result.push(this.newSubmission[id] = value);
}
return result;
})();
}
}
getCurrentSubmission() {
return this.newSubmission;
}
}
const root = typeof exports !== 'undefined' && exports !== null ? exports : this;
root.TestProblemDisplay = TestProblemDisplay;
class TestProblemGenerator extends XProblemGenerator
constructor: (seed, @parameters = {}) ->
super(seed, @parameters)
generate: () ->
@problemState.value = @parameters.value
return @problemState
root = exports ? this
root.generatorClass = TestProblemGenerator
/*
* decaffeinate suggestions:
* DS001: Remove Babel/TypeScript constructor workaround
* DS207: Consider shorter variations of null checks
* DS208: Avoid top-level this
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
*/
class TestProblemGenerator extends XProblemGenerator {
constructor(seed, parameters) {
{
// Hack: trick Babel/TypeScript into allowing this before super.
if (false) { super(); }
let thisFn = (() => { this; }).toString();
let thisName = thisFn.slice(thisFn.indexOf('{') + 1, thisFn.indexOf(';')).trim();
eval(`${thisName} = this;`);
}
if (parameters == null) { parameters = {}; }
this.parameters = parameters;
super(seed, this.parameters);
}
generate() {
this.problemState.value = this.parameters.value;
return this.problemState;
}
}
const root = typeof exports !== 'undefined' && exports !== null ? exports : this;
root.generatorClass = TestProblemGenerator;
class TestProblemGrader extends XProblemGrader
constructor: (@submission, @problemState, @parameters={}) ->
super(@submission, @problemState, @parameters)
solve: () ->
@solution = {0: @problemState.value}
grade: () ->
if not @solution?
@solve()
allCorrect = true
for id, value of @solution
valueCorrect = if @submission? then (value == @submission[id]) else false
@evaluation[id] = valueCorrect
if not valueCorrect
allCorrect = false
return allCorrect
root = exports ? this
root.graderClass = TestProblemGrader
/*
* decaffeinate suggestions:
* DS001: Remove Babel/TypeScript constructor workaround
* DS102: Remove unnecessary code created because of implicit returns
* DS207: Consider shorter variations of null checks
* DS208: Avoid top-level this
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
*/
class TestProblemGrader extends XProblemGrader {
constructor(submission, problemState, parameters) {
{
// Hack: trick Babel/TypeScript into allowing this before super.
if (false) { super(); }
let thisFn = (() => { this; }).toString();
let thisName = thisFn.slice(thisFn.indexOf('{') + 1, thisFn.indexOf(';')).trim();
eval(`${thisName} = this;`);
}
this.submission = submission;
this.problemState = problemState;
if (parameters == null) { parameters = {}; }
this.parameters = parameters;
super(this.submission, this.problemState, this.parameters);
}
solve() {
return this.solution = {0: this.problemState.value};
}
grade() {
if ((this.solution == null)) {
this.solve();
}
let allCorrect = true;
for (let id in this.solution) {
const value = this.solution[id];
const valueCorrect = (this.submission != null) ? (value === this.submission[id]) : false;
this.evaluation[id] = valueCorrect;
if (!valueCorrect) {
allCorrect = false;
}
}
return allCorrect;
}
}
const root = typeof exports !== 'undefined' && exports !== null ? exports : this;
root.graderClass = TestProblemGrader;
class XProblemGenerator
constructor: (seed, @parameters={}) ->
@random = new MersenneTwister(seed)
@problemState = {}
generate: () ->
console.error("Abstract method called: XProblemGenerator.generate")
class XProblemDisplay
constructor: (@state, @submission, @evaluation, @container, @submissionField, @parameters={}) ->
render: () ->
console.error("Abstract method called: XProblemDisplay.render")
updateSubmission: () ->
@submissionField.val(JSON.stringify(@getCurrentSubmission()))
getCurrentSubmission: () ->
console.error("Abstract method called: XProblemDisplay.getCurrentSubmission")
class XProblemGrader
constructor: (@submission, @problemState, @parameters={}) ->
@solution = null
@evaluation = {}
solve: () ->
console.error("Abstract method called: XProblemGrader.solve")
grade: () ->
console.error("Abstract method called: XProblemGrader.grade")
root = exports ? this
root.XProblemGenerator = XProblemGenerator
root.XProblemDisplay = XProblemDisplay
root.XProblemGrader = XProblemGrader
/*
* decaffeinate suggestions:
* DS207: Consider shorter variations of null checks
* DS208: Avoid top-level this
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
*/
class XProblemGenerator {
constructor(seed, parameters) {
if (parameters == null) { parameters = {}; }
this.parameters = parameters;
this.random = new MersenneTwister(seed);
this.problemState = {};
}
generate() {
console.error("Abstract method called: XProblemGenerator.generate");
}
}
class XProblemDisplay {
constructor(state, submission, evaluation, container, submissionField, parameters) {
this.state = state;
this.submission = submission;
this.evaluation = evaluation;
this.container = container;
this.submissionField = submissionField;
if (parameters == null) { parameters = {}; }
this.parameters = parameters;
}
render() {
console.error("Abstract method called: XProblemDisplay.render");
}
updateSubmission() {
this.submissionField.val(JSON.stringify(this.getCurrentSubmission()));
}
getCurrentSubmission() {
console.error("Abstract method called: XProblemDisplay.getCurrentSubmission");
}
}
class XProblemGrader {
constructor(submission, problemState, parameters) {
this.submission = submission;
this.problemState = problemState;
if (parameters == null) { parameters = {}; }
this.parameters = parameters;
this.solution = null;
this.evaluation = {};
}
solve() {
console.error("Abstract method called: XProblemGrader.solve");
}
grade() {
console.error("Abstract method called: XProblemGrader.grade");
}
}
const root = typeof exports !== 'undefined' && exports !== null ? exports : this;
root.XProblemGenerator = XProblemGenerator;
root.XProblemDisplay = XProblemDisplay;
root.XProblemGrader = XProblemGrader;
*.js
# Tests for video are written in pure JavaScript.
!video/*.js
# Tests for Time are written in pure JavaScript.
!time_spec.js
!collapsible_spec.js
!xmodule_spec.js
!sequence/display_spec.js
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)
describe('Annotatable', function() {
beforeEach(() => loadFixtures('annotatable.html'));
describe('constructor', function() {
const el = $('.xblock-student_view.xmodule_AnnotatableModule');
beforeEach(function() {
this.annotatable = new Annotatable(el);
});
it('works', () => expect(1).toBe(1));
});
});
describe 'HTMLEditingDescriptor', ->
beforeEach ->
window.baseUrl = "/static/deadbeef"
afterEach ->
delete window.baseUrl
describe 'Visual HTML Editor', ->
beforeEach ->
loadFixtures 'html-edit-visual.html'
@descriptor = new HTMLEditingDescriptor($('.test-component'))
it 'Returns data from Visual Editor if text has changed', ->
visualEditorStub =
getContent: () -> 'from visual editor'
spyOn(@descriptor, 'getVisualEditor').and.callFake () ->
visualEditorStub
data = @descriptor.save().data
expect(data).toEqual('from visual editor')
it 'Returns data from Raw Editor if text has not changed', ->
visualEditorStub =
getContent: () -> '<p>original visual text</p>'
spyOn(@descriptor, 'getVisualEditor').and.callFake () ->
visualEditorStub
data = @descriptor.save().data
expect(data).toEqual('raw text')
it 'Performs link rewriting for static assets when saving', ->
visualEditorStub =
getContent: () -> 'from visual editor with /c4x/foo/bar/asset/image.jpg'
spyOn(@descriptor, 'getVisualEditor').and.callFake () ->
visualEditorStub
data = @descriptor.save().data
expect(data).toEqual('from visual editor with /static/image.jpg')
it 'When showing visual editor links are rewritten to c4x format', ->
visualEditorStub =
content: 'text /static/image.jpg'
startContent: 'text /static/image.jpg'
focus: ->
setContent: (x) -> @content = x
getContent: -> @content
@descriptor.initInstanceCallback(visualEditorStub)
expect(visualEditorStub.getContent()).toEqual('text /c4x/foo/bar/asset/image.jpg')
it 'Enables spellcheck', ->
expect($('.html-editor iframe')[0].contentDocument.body.spellcheck).toBe(true)
describe 'Raw HTML Editor', ->
beforeEach ->
loadFixtures 'html-editor-raw.html'
@descriptor = new HTMLEditingDescriptor($('.test-component'))
it 'Returns data from raw editor', ->
data = @descriptor.save().data
expect(data).toEqual('raw text')
describe('HTMLEditingDescriptor', function() {
beforeEach(() => window.baseUrl = "/static/deadbeef");
afterEach(() => delete window.baseUrl);
describe('Visual HTML Editor', function() {
beforeEach(function() {
loadFixtures('html-edit-visual.html');
this.descriptor = new HTMLEditingDescriptor($('.test-component'));
});
it('Returns data from Visual Editor if text has changed', function() {
const visualEditorStub =
{getContent() { return 'from visual editor'; }};
spyOn(this.descriptor, 'getVisualEditor').and.callFake(() => visualEditorStub);
const { data } = this.descriptor.save();
expect(data).toEqual('from visual editor');
});
it('Returns data from Raw Editor if text has not changed', function() {
const visualEditorStub =
{getContent() { return '<p>original visual text</p>'; }};
spyOn(this.descriptor, 'getVisualEditor').and.callFake(() => visualEditorStub);
const { data } = this.descriptor.save();
expect(data).toEqual('raw text');
});
it('Performs link rewriting for static assets when saving', function() {
const visualEditorStub =
{getContent() { return 'from visual editor with /c4x/foo/bar/asset/image.jpg'; }};
spyOn(this.descriptor, 'getVisualEditor').and.callFake(() => visualEditorStub);
const { data } = this.descriptor.save();
expect(data).toEqual('from visual editor with /static/image.jpg');
});
it('When showing visual editor links are rewritten to c4x format', function() {
const visualEditorStub = {
content: 'text /static/image.jpg',
startContent: 'text /static/image.jpg',
focus() {},
setContent(x) { this.content = x; },
getContent() { return this.content; }
};
this.descriptor.initInstanceCallback(visualEditorStub);
expect(visualEditorStub.getContent()).toEqual('text /c4x/foo/bar/asset/image.jpg');
});
it('Enables spellcheck', () => expect($('.html-editor iframe')[0].contentDocument.body.spellcheck).toBe(true));
});
describe('Raw HTML Editor', function() {
beforeEach(function() {
loadFixtures('html-editor-raw.html');
this.descriptor = new HTMLEditingDescriptor($('.test-component'));
});
it('Returns data from raw editor', function() {
const { data } = this.descriptor.save();
expect(data).toEqual('raw text');
});
});
});
describe "TabsEditingDescriptor", ->
beforeEach ->
@isInactiveClass = "is-inactive"
@isCurrent = "current"
loadFixtures 'tabs-edit.html'
@descriptor = new TabsEditingDescriptor($('.xblock'))
@html_id = 'test_id'
@tab_0_switch = jasmine.createSpy('tab_0_switch');
@tab_0_modelUpdate = jasmine.createSpy('tab_0_modelUpdate');
@tab_1_switch = jasmine.createSpy('tab_1_switch');
@tab_1_modelUpdate = jasmine.createSpy('tab_1_modelUpdate');
TabsEditingDescriptor.Model.addModelUpdate(@html_id, 'Tab 0 Editor', @tab_0_modelUpdate)
TabsEditingDescriptor.Model.addOnSwitch(@html_id, 'Tab 0 Editor', @tab_0_switch)
TabsEditingDescriptor.Model.addModelUpdate(@html_id, 'Tab 1 Transcripts', @tab_1_modelUpdate)
TabsEditingDescriptor.Model.addOnSwitch(@html_id, 'Tab 1 Transcripts', @tab_1_switch)
spyOn($.fn, 'hide').and.callThrough()
spyOn($.fn, 'show').and.callThrough()
spyOn(TabsEditingDescriptor.Model, 'initialize')
spyOn(TabsEditingDescriptor.Model, 'updateValue')
afterEach ->
TabsEditingDescriptor.Model.modules= {}
describe "constructor", ->
it "first tab should be visible", ->
expect(@descriptor.$tabs.first()).toHaveClass(@isCurrent)
expect(@descriptor.$content.first()).not.toHaveClass(@isInactiveClass)
describe "onSwitchEditor", ->
it "switching tabs changes styles", ->
@descriptor.$tabs.eq(1).trigger("click")
expect(@descriptor.$tabs.eq(0)).not.toHaveClass(@isCurrent)
expect(@descriptor.$content.eq(0)).toHaveClass(@isInactiveClass)
expect(@descriptor.$tabs.eq(1)).toHaveClass(@isCurrent)
expect(@descriptor.$content.eq(1)).not.toHaveClass(@isInactiveClass)
expect(@tab_1_switch).toHaveBeenCalled()
it "if click on current tab, nothing should happen", ->
spyOn($.fn, 'trigger').and.callThrough()
currentTab = @descriptor.$tabs.filter('.' + @isCurrent)
@descriptor.$tabs.eq(0).trigger("click")
expect(@descriptor.$tabs.filter('.' + @isCurrent)).toEqual(currentTab)
expect($.fn.trigger.calls.count()).toEqual(1)
it "onSwitch function call", ->
@descriptor.$tabs.eq(1).trigger("click")
expect(TabsEditingDescriptor.Model.updateValue).toHaveBeenCalled()
expect(@tab_1_switch).toHaveBeenCalled()
describe "save", ->
it "function for current tab should be called", ->
@descriptor.$tabs.eq(1).trigger("click")
data = @descriptor.save().data
expect(@tab_1_modelUpdate).toHaveBeenCalled()
it "detach click event", ->
spyOn($.fn, "off")
@descriptor.save()
expect($.fn.off).toHaveBeenCalledWith(
'click',
'.editor-tabs .tab',
@descriptor.onSwitchEditor
)
describe "TabsEditingDescriptor special save cases", ->
beforeEach ->
@isInactiveClass = "is-inactive"
@isCurrent = "current"
loadFixtures 'tabs-edit.html'
@descriptor = new window.TabsEditingDescriptor($('.xblock'))
@html_id = 'test_id'
describe "save", ->
it "case: no init", ->
data = @descriptor.save().data
expect(data).toEqual(null)
it "case: no function in model update", ->
TabsEditingDescriptor.Model.initialize(@html_id)
data = @descriptor.save().data
expect(data).toEqual(null)
it "case: no function in model update, but value presented", ->
@tab_0_modelUpdate = jasmine.createSpy('tab_0_modelUpdate').and.returnValue(1)
TabsEditingDescriptor.Model.addModelUpdate(@html_id, 'Tab 0 Editor', @tab_0_modelUpdate)
@descriptor.$tabs.eq(1).trigger("click")
expect(@tab_0_modelUpdate).toHaveBeenCalled()
data = @descriptor.save().data
expect(data).toEqual(1)
describe("TabsEditingDescriptor", function() {
beforeEach(function() {
this.isInactiveClass = "is-inactive";
this.isCurrent = "current";
loadFixtures('tabs-edit.html');
this.descriptor = new TabsEditingDescriptor($('.xblock'));
this.html_id = 'test_id';
this.tab_0_switch = jasmine.createSpy('tab_0_switch');
this.tab_0_modelUpdate = jasmine.createSpy('tab_0_modelUpdate');
this.tab_1_switch = jasmine.createSpy('tab_1_switch');
this.tab_1_modelUpdate = jasmine.createSpy('tab_1_modelUpdate');
TabsEditingDescriptor.Model.addModelUpdate(this.html_id, 'Tab 0 Editor', this.tab_0_modelUpdate);
TabsEditingDescriptor.Model.addOnSwitch(this.html_id, 'Tab 0 Editor', this.tab_0_switch);
TabsEditingDescriptor.Model.addModelUpdate(this.html_id, 'Tab 1 Transcripts', this.tab_1_modelUpdate);
TabsEditingDescriptor.Model.addOnSwitch(this.html_id, 'Tab 1 Transcripts', this.tab_1_switch);
spyOn($.fn, 'hide').and.callThrough();
spyOn($.fn, 'show').and.callThrough();
spyOn(TabsEditingDescriptor.Model, 'initialize');
spyOn(TabsEditingDescriptor.Model, 'updateValue');
});
afterEach(() => TabsEditingDescriptor.Model.modules= {});
describe("constructor", () =>
it("first tab should be visible", function() {
expect(this.descriptor.$tabs.first()).toHaveClass(this.isCurrent);
expect(this.descriptor.$content.first()).not.toHaveClass(this.isInactiveClass);
})
);
describe("onSwitchEditor", function() {
it("switching tabs changes styles", function() {
this.descriptor.$tabs.eq(1).trigger("click");
expect(this.descriptor.$tabs.eq(0)).not.toHaveClass(this.isCurrent);
expect(this.descriptor.$content.eq(0)).toHaveClass(this.isInactiveClass);
expect(this.descriptor.$tabs.eq(1)).toHaveClass(this.isCurrent);
expect(this.descriptor.$content.eq(1)).not.toHaveClass(this.isInactiveClass);
expect(this.tab_1_switch).toHaveBeenCalled();
});
it("if click on current tab, nothing should happen", function() {
spyOn($.fn, 'trigger').and.callThrough();
const currentTab = this.descriptor.$tabs.filter(`.${this.isCurrent}`);
this.descriptor.$tabs.eq(0).trigger("click");
expect(this.descriptor.$tabs.filter(`.${this.isCurrent}`)).toEqual(currentTab);
expect($.fn.trigger.calls.count()).toEqual(1);
});
it("onSwitch function call", function() {
this.descriptor.$tabs.eq(1).trigger("click");
expect(TabsEditingDescriptor.Model.updateValue).toHaveBeenCalled();
expect(this.tab_1_switch).toHaveBeenCalled();
});
});
describe("save", function() {
it("function for current tab should be called", function() {
this.descriptor.$tabs.eq(1).trigger("click");
const { data } = this.descriptor.save();
expect(this.tab_1_modelUpdate).toHaveBeenCalled();
});
it("detach click event", function() {
spyOn($.fn, "off");
this.descriptor.save();
expect($.fn.off).toHaveBeenCalledWith(
'click',
'.editor-tabs .tab',
this.descriptor.onSwitchEditor
);
});
});
});
describe("TabsEditingDescriptor special save cases", function() {
beforeEach(function() {
this.isInactiveClass = "is-inactive";
this.isCurrent = "current";
loadFixtures('tabs-edit.html');
this.descriptor = new window.TabsEditingDescriptor($('.xblock'));
this.html_id = 'test_id';
});
describe("save", function() {
it("case: no init", function() {
const { data } = this.descriptor.save();
expect(data).toEqual(null);
});
it("case: no function in model update", function() {
TabsEditingDescriptor.Model.initialize(this.html_id);
const { data } = this.descriptor.save();
expect(data).toEqual(null);
});
it("case: no function in model update, but value presented", function() {
this.tab_0_modelUpdate = jasmine.createSpy('tab_0_modelUpdate').and.returnValue(1);
TabsEditingDescriptor.Model.addModelUpdate(this.html_id, 'Tab 0 Editor', this.tab_0_modelUpdate);
this.descriptor.$tabs.eq(1).trigger("click");
expect(this.tab_0_modelUpdate).toHaveBeenCalled();
const { data } = this.descriptor.save();
expect(data).toEqual(1);
});
});
});
describe "$.immediateDescendents", ->
beforeEach ->
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()
it "finds non-immediate children", ->
expect(@descendents).toContain($('#grandchild').get(0))
it "finds immediate children", ->
expect(@descendents).toContain($('#child').get(0))
it "skips nested descendents", ->
expect(@descendents).not.toContain($('#nested').get(0))
it "finds 2 children", ->
expect(@descendents.length).toBe(2)
\ No newline at end of file
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>\
`
);
this.descendents = $('#jasmine-fixtures').immediateDescendents(".xblock").get();
});
it("finds non-immediate children", function() {
expect(this.descendents).toContain($('#grandchild').get(0));
});
it("finds immediate children", function() {
expect(this.descendents).toContain($('#child').get(0));
});
it("skips nested descendents", function() {
expect(this.descendents).not.toContain($('#nested').get(0));
});
it("finds 2 children", function() {
expect(this.descendents.length).toBe(2);
});
});
\ No newline at end of file
describe 'Courseware', ->
describe 'start', ->
it 'binds the Logger', ->
spyOn(Logger, 'bind')
Courseware.start()
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()
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]])
describe('Courseware', function() {
describe('start', () =>
it('binds the Logger', function() {
spyOn(Logger, 'bind');
Courseware.start();
expect(Logger.bind).toHaveBeenCalled();
})
);
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>\
`
);
this.courseware.render();
});
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]]));
});
});
describe 'FeedbackForm', ->
beforeEach ->
loadFixtures 'coffee/fixtures/feedback_form.html'
describe 'constructor', ->
beforeEach ->
new FeedbackForm
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()
expect($.postWithPrefix).toHaveBeenCalledWith '/send_feedback', {
subject: 'Awesome!'
message: 'This site is really good.'
url: window.location.href
}, jasmine.any(Function), 'json'
it 'replace the form with a thank you message', ->
$('#feedback_button').click()
expect($('#feedback_div').html()).toEqual 'Feedback submitted. Thank you'
describe('FeedbackForm', function() {
beforeEach(() => loadFixtures('coffee/fixtures/feedback_form.html'));
describe('constructor', function() {
beforeEach(function() {
new FeedbackForm;
spyOn($, 'postWithPrefix').and.callFake((url, data, callback, format) => callback());
});
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.',
url: window.location.href
}, jasmine.any(Function), 'json');
});
it('replace the form with a thank you message', function() {
$('#feedback_button').click();
expect($('#feedback_div').html()).toEqual('Feedback submitted. Thank you');
});
});
});
jasmine.stubbedMetadata =
slowerSpeedYoutubeId:
id: 'slowerSpeedYoutubeId'
duration: 300
normalSpeedYoutubeId:
id: 'normalSpeedYoutubeId'
duration: 200
bogus:
duration: 100
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' ||
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."
jasmine.stubYoutubePlayer = ->
YT.Player = -> jasmine.createSpyObj 'YT.Player', ['cueVideoById', 'getVideoEmbedCode',
'getCurrentTime', 'getPlayerState', 'getVolume', 'setVolume', 'loadVideoById',
'playVideo', 'pauseVideo', 'seekTo']
jasmine.stubVideoPlayer = (context, enableParts, createPlayer=true) ->
enableParts = [enableParts] unless $.isArray(enableParts)
suite = context.suite
currentPartName = suite.description while suite = suite.parentSuite
enableParts.push currentPartName
for part in ['VideoCaption', 'VideoSpeedControl', 'VideoVolumeControl', 'VideoProgressSlider']
unless $.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)
# 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.qtip
$.fn.qtip = jasmine.createSpy 'jQuery.qtip'
# Stub jQuery.scrollTo
$.fn.scrollTo = jasmine.createSpy 'jQuery.scrollTo'
/*
* decaffeinate suggestions:
* 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',
duration: 200
},
bogus: {
duration: 100
}
};
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(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.`;
}
})
;
jasmine.stubYoutubePlayer = () =>
YT.Player = () => jasmine.createSpyObj('YT.Player', ['cueVideoById', 'getVideoEmbedCode',
'getCurrentTime', 'getPlayerState', 'getVolume', 'setVolume', 'loadVideoById',
'playVideo', 'pauseVideo', 'seekTo'])
;
jasmine.stubVideoPlayer = function(context, enableParts, createPlayer) {
let currentPartName;
if (createPlayer == null) { createPlayer = true; }
if (!$.isArray(enableParts)) { enableParts = [enableParts]; }
let { suite } = context;
while ((suite = suite.parentSuite)) { currentPartName = suite.description; }
enableParts.push(currentPartName);
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});
}
};
// 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.qtip
$.fn.qtip = jasmine.createSpy('jQuery.qtip');
// Stub jQuery.scrollTo
$.fn.scrollTo = jasmine.createSpy('jQuery.scrollTo');
describe 'Histogram', ->
beforeEach ->
spyOn $, 'plot'
describe 'constructor', ->
it 'instantiate the data arrays', ->
histogram = new Histogram 1, []
expect(histogram.xTicks).toEqual []
expect(histogram.yTicks).toEqual []
expect(histogram.data).toEqual []
describe 'calculate', ->
beforeEach ->
@histogram = new Histogram(1, [[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 x ticks', ->
expect(@histogram.xTicks).toEqual [[1, '1'], [2, '2'], [3, '3']]
it 'store the correct value for y ticks', ->
expect(@histogram.yTicks).toEqual
describe 'render', ->
it 'call flot with correct option', ->
new Histogram(1, [[1, 1], [2, 2], [3, 3]])
firstArg = $.plot.calls.mostRecent().args[0]
secondArg = $.plot.calls.mostRecent().args[1]
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
fill: 1.0
color: "#b72121"
])
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']]
labelWidth: 50
)
describe('Histogram', function() {
beforeEach(() => spyOn($, 'plot'));
describe('constructor', () =>
it('instantiate the data arrays', function() {
const histogram = new Histogram(1, []);
expect(histogram.xTicks).toEqual([]);
expect(histogram.yTicks).toEqual([]);
expect(histogram.data).toEqual([]);
})
);
describe('calculate', function() {
beforeEach(function() {
this.histogram = new Histogram(1, [[null, 1], [1, 1], [2, 2], [3, 3]]);
});
it('store the correct value for data', function() {
expect(this.histogram.data).toEqual([[1, Math.log(2)], [2, Math.log(3)], [3, Math.log(4)]]);
});
it('store the correct value for x ticks', function() {
expect(this.histogram.xTicks).toEqual([[1, '1'], [2, '2'], [3, '3']]);
});
it('store the correct value for y ticks', function() {
expect(this.histogram.yTicks).toEqual;
});
});
describe('render', () =>
it('call flot with correct option', function() {
new Histogram(1, [[1, 1], [2, 2], [3, 3]]);
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,
fill: 1.0
},
color: "#b72121"
}
]);
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']],
labelWidth: 50
}
});
})
);
});
describe 'Tab', ->
beforeEach ->
loadFixtures 'coffee/fixtures/tab.html'
@items = $.parseJSON readFixtures('coffee/fixtures/items.json')
describe 'constructor', ->
beforeEach ->
spyOn($.fn, 'tabs')
@tab = new Tab 1, @items
it 'set the element', ->
expect(@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 container', ->
containers = $('section').map(-> $(this).attr('id')).get()
expect(containers).toEqual ['tab-1-0', 'tab-1-1', 'tab-1-2']
it 'bind the tabs', ->
expect($.fn.tabs).toHaveBeenCalledWith show: @tab.onShow
# 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})
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 'trigger contentChanged event on the element', ->
spyOnEvent @tab.el, 'contentChanged'
@tab.onShow($('#tab-1-1'), {'index': 1})
expect('contentChanged').toHaveBeenTriggeredOn @tab.el
describe('Tab', function() {
beforeEach(function() {
loadFixtures('coffee/fixtures/tab.html');
this.items = $.parseJSON(readFixtures('coffee/fixtures/items.json'));
});
describe('constructor', function() {
beforeEach(function() {
spyOn($.fn, 'tabs');
this.tab = new Tab(1, this.items);
});
it('set the element', function() {
expect(this.tab.el).toEqual($('#tab_1'));
});
it('build the tabs', function() {
const links = $('.navigation li>a').map(function() { return $(this).attr('href'); }).get();
expect(links).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();
expect(containers).toEqual(['tab-1-0', 'tab-1-1', 'tab-1-2']);
});
it('bind the tabs', function() {
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', function() {
beforeEach(function() {
this.tab = new Tab(1, this.items);
this.tab.onShow($('#tab-1-0'), {'index': 1});
});
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');
expect($('#tab-1-2').html()).toEqual('');
});
it('trigger contentChanged event on the element', function() {
spyOnEvent(this.tab.el, 'contentChanged');
this.tab.onShow($('#tab-1-1'), {'index': 1});
expect('contentChanged').toHaveBeenTriggeredOn(this.tab.el);
});
});
});
describe "RequireJS namespacing", ->
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"
}
}
requireTobeUndefined: ->
{
compare: ->
{
pass: typeof require is "undefined"
}
}
defineTobeUndefined: ->
{
compare: ->
{
pass: typeof define is "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 requirejs(), require(), and define() are not in the global namespace", ->
# 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()
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 = () ->
# 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
d1.resolve()
# This module returns an object. It can be accessed via the
# Require JS require() function.
module_status: "OK"
# Require our defined test module.
RequireJS.require ["test_module"], (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"
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)
/*
* 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() {
return {
compare() {
return {
pass: typeof requirejs === "undefined"
};
}
};
},
requireTobeUndefined() {
return {
compare() {
return {
pass: typeof require === "undefined"
};
}
};
},
defineTobeUndefined() {
return {
compare() {
return {
pass: typeof define === "undefined"
};
}
};
}}));
it("check that the RequireJS object is present in the global namespace", function() {
expect(RequireJS).toEqual(jasmine.any(Object));
expect(window.RequireJS).toEqual(jasmine.any(Object));
});
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();
});
});
describe("RequireJS module creation", function() {
let inDefineCallback = undefined;
let inRequireCallback = undefined;
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;
// Define our test module.
RequireJS.define("test_module", [], function() {
inDefineCallback = true;
d1.resolve();
// This module returns an object. It can be accessed via the
// Require JS require() function.
return {module_status: "OK"};
});
// 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");
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(function() {
// The final test behavior
expect(inDefineCallback).toBeTruthy();
expect(inRequireCallback).toBeTruthy();
}).always(done);
});
});
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment