This basically commits the transpiled CoffeeScript JS (with minor cleanup) and removes coffee build support. A tiny amount of support for xblocks exists, because external users may have xblocks with coffee. But no coffee in our tree anyway.
376 lines
20 KiB
JavaScript
376 lines
20 KiB
JavaScript
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"],
|
|
function(Textbook, Chapter, ChapterSet, Course, TextbookSet, ShowTextbook, EditTextbook, ListTextbooks, EditChapter,
|
|
Prompt, Notification, ViewUtils, AjaxHelpers, modal_helpers) {
|
|
|
|
describe("ShowTextbook", function() {
|
|
const tpl = readFixtures('show-textbook.underscore');
|
|
|
|
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});
|
|
|
|
this.promptSpies = jasmine.stealth.spyOnConstructor(Prompt, "Warning", ["show", "hide"]);
|
|
this.promptSpies.show.and.returnValue(this.promptSpies);
|
|
window.course = new Course({
|
|
id: "5",
|
|
name: "Course Name",
|
|
url_name: "course_name",
|
|
org: "course_org",
|
|
num: "course_num",
|
|
revision: "course_rev"
|
|
});
|
|
});
|
|
|
|
afterEach(() => delete window.course);
|
|
|
|
describe("Basic", function() {
|
|
it("should render properly", function() {
|
|
this.view.render();
|
|
expect(this.view.$el).toContainText("Life Sciences");
|
|
});
|
|
|
|
it("should set the 'editing' property on the model when the edit button is clicked", function() {
|
|
this.view.render().$(".edit").click();
|
|
expect(this.model.get("editing")).toBeTruthy();
|
|
});
|
|
|
|
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();
|
|
expect(this.collection).toContain(this.model);
|
|
});
|
|
|
|
it("should show chapters appropriately", function() {
|
|
this.model.get("chapters").add([{}, {}, {}]);
|
|
this.model.set('showChapters', false);
|
|
this.view.render().$(".show-chapters").click();
|
|
expect(this.model.get('showChapters')).toBeTruthy();
|
|
});
|
|
|
|
it("should hide chapters appropriately", function() {
|
|
this.model.get("chapters").add([{}, {}, {}]);
|
|
this.model.set('showChapters', true);
|
|
this.view.render().$(".hide-chapters").click();
|
|
expect(this.model.get('showChapters')).toBeFalsy();
|
|
});
|
|
});
|
|
|
|
describe("AJAX", function() {
|
|
beforeEach(function() {
|
|
this.savingSpies = jasmine.stealth.spyOnConstructor(Notification, "Mini",
|
|
["show", "hide"]);
|
|
this.savingSpies.show.and.returnValue(this.savingSpies);
|
|
CMS.URL.TEXTBOOKS = "/textbooks";
|
|
});
|
|
|
|
afterEach(() => delete CMS.URL.TEXTBOOKS);
|
|
|
|
it("should destroy itself on confirmation", function() {
|
|
const requests = AjaxHelpers["requests"](this);
|
|
|
|
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();
|
|
expect(this.collection.contains(this.model)).toBeFalsy();
|
|
});
|
|
});
|
|
});
|
|
|
|
describe("EditTextbook", () =>
|
|
describe("Basic", function() {
|
|
const tpl = readFixtures('edit-textbook.underscore');
|
|
|
|
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});
|
|
spyOn(this.view, 'render').and.callThrough();
|
|
});
|
|
|
|
it("should render properly", function() {
|
|
this.view.render();
|
|
expect(this.view.$("input[name=textbook-name]").val()).toEqual("Life Sciences");
|
|
});
|
|
|
|
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);
|
|
expect(this.model.get("chapters").last().isEmpty()).toBeTruthy();
|
|
});
|
|
|
|
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");
|
|
expect(this.model.save).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();
|
|
expect(this.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");
|
|
expect(this.model.save).not.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();
|
|
expect(this.model.save).toHaveBeenCalled();
|
|
});
|
|
|
|
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);
|
|
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", 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();
|
|
expect(chapters.length).toEqual(1);
|
|
});
|
|
})
|
|
);
|
|
|
|
describe("ListTextbooks", function() {
|
|
const noTextbooksTpl = readFixtures("no-textbooks.underscore");
|
|
const editTextbooktpl = readFixtures('edit-textbook.underscore');
|
|
|
|
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});
|
|
this.view.render();
|
|
});
|
|
|
|
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);
|
|
expect(ViewUtils.setScrollOffset).toHaveBeenCalledWith($sectionEl, 0);
|
|
});
|
|
|
|
it("should focus first input element of newly added textbook", function() {
|
|
spyOn(jQuery.fn, 'focus').and.callThrough();
|
|
jasmine.addMatchers({
|
|
toHaveBeenCalledOnJQueryObject() {
|
|
return {
|
|
compare(actual, expected) {
|
|
return {
|
|
pass: actual.calls && actual.calls.mostRecent() &&
|
|
(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)
|
|
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("EditChapter", function() {
|
|
beforeEach(function() {
|
|
modal_helpers.installModalTemplates();
|
|
this.model = new Chapter({
|
|
name: "Chapter 1",
|
|
asset_path: "/ch1.pdf"
|
|
});
|
|
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";
|
|
window.course = new Course({name: "abcde"});
|
|
});
|
|
|
|
afterEach(function() {
|
|
delete CMS.URL.UPLOAD_ASSET;
|
|
delete window.course;
|
|
});
|
|
|
|
it("can render", function() {
|
|
this.view.render();
|
|
expect(this.view.$("input.chapter-name").val()).toEqual("Chapter 1");
|
|
expect(this.view.$("input.chapter-asset-path").val()).toEqual("/ch1.pdf");
|
|
});
|
|
|
|
it("can delete itself", function() {
|
|
this.view.render().$(".action-close").click();
|
|
expect(this.collection.length).toEqual(0);
|
|
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()
|
|
|
|
// 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", 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");
|
|
expect(this.model.get("asset_path")).toEqual("unicorns");
|
|
});
|
|
});
|
|
});
|