Commit 5703adb7 by Ben Patterson

Merge branch 'release'

Includes rc/2014-10-14
parents 208c6f1d bb6cd7dc
......@@ -15,10 +15,6 @@ def run():
"""
Executed during django startup
"""
# Patch the xml libs.
from safe_lxml import defuse_xml_libs
defuse_xml_libs()
django_utils_translation.patch()
autostartup()
......
# Patch the xml libs before anything else.
from safe_lxml import defuse_xml_libs
defuse_xml_libs()
# Disable PyContract contract checking when running as a webserver
import contracts
contracts.disable_all()
......
......@@ -19,6 +19,23 @@ class @DiscussionSpecHelper
{always: ->}
)
@makeEventSpy = () ->
jasmine.createSpyObj('event', ['preventDefault', 'target'])
@makeCourseSettings = (is_cohorted=true) ->
new DiscussionCourseSettings(
category_map:
children: ['Test Topic', 'Other Topic']
entries:
'Test Topic':
is_cohorted: is_cohorted
id: 'test_topic'
'Other Topic':
is_cohorted: is_cohorted
id: 'other_topic'
is_cohorted: is_cohorted
)
@setUnderscoreFixtures = ->
for templateName in ['thread-show']
templateFixture = readFixtures('templates/discussion/' + templateName + '.underscore')
......
(function() {
'use strict';
describe('DiscussionThreadEditView', function() {
var testUpdate, testCancel;
beforeEach(function() {
DiscussionSpecHelper.setUpGlobals();
DiscussionSpecHelper.setUnderscoreFixtures();
spyOn(DiscussionUtil, 'makeWmdEditor');
this.threadData = DiscussionViewSpecHelper.makeThreadWithProps();
this.thread = new Thread(this.threadData);
this.course_settings = new DiscussionCourseSettings({
'category_map': {
'children': ['Topic'],
'entries': {
'Topic': {
'is_cohorted': true,
'id': 'topic'
}
}
},
'is_cohorted': true
this.threadData = DiscussionViewSpecHelper.makeThreadWithProps({
'commentable_id': 'test_topic',
'title': 'test thread title'
});
this.thread = new Thread(this.threadData);
this.course_settings = DiscussionSpecHelper.makeCourseSettings();
this.createEditView = function (options) {
options = _.extend({
container: $('#fixture-element'),
model: this.thread,
mode: 'tab',
topicId: 'dummy_id',
threadType: 'question',
course_settings: this.course_settings
}, options);
this.view = new DiscussionThreadEditView(options);
......@@ -34,36 +26,52 @@
};
});
it('can save new data correctly', function() {
var view;
testUpdate = function(view, thread) {
spyOn($, 'ajax').andCallFake(function(params) {
expect(params.url.path()).toEqual(DiscussionUtil.urlFor('update_thread', 'dummy_id'));
expect(params.data.thread_type).toBe('discussion');
expect(params.data.commentable_id).toBe('topic');
expect(params.data.title).toBe('new_title');
expect(params.data.commentable_id).toBe('other_topic');
expect(params.data.title).toBe('changed thread title');
params.success();
return {always: function() {}};
});
this.createEditView();
this.view.$el.find('a.topic-title').first().click(); // set new topic
this.view.$('.edit-post-title').val('new_title'); // set new title
this.view.$("label[for$='post-type-discussion']").click(); // set new thread type
this.view.$('.post-update').click();
view.$el.find('a.topic-title')[1].click(); // set new topic
view.$('.edit-post-title').val('changed thread title'); // set new title
view.$("label[for$='post-type-discussion']").click(); // set new thread type
view.$('.post-update').click();
expect($.ajax).toHaveBeenCalled();
expect(this.thread.get('title')).toBe('new_title');
expect(this.thread.get('commentable_id')).toBe('topic');
expect(this.thread.get('thread_type')).toBe('discussion');
expect(this.thread.get('courseware_title')).toBe('Topic');
expect(thread.get('title')).toBe('changed thread title');
expect(thread.get('thread_type')).toBe('discussion');
expect(thread.get('commentable_id')).toBe('other_topic');
expect(thread.get('courseware_title')).toBe('Other Topic');
expect(view.$('.edit-post-title')).toHaveValue('');
expect(view.$('.wmd-preview p')).toHaveText('');
};
it('can save new data correctly in tab mode', function() {
this.createEditView();
testUpdate(this.view, this.thread);
});
expect(this.view.$('.edit-post-title')).toHaveValue('');
expect(this.view.$('.wmd-preview p')).toHaveText('');
it('can save new data correctly in inline mode', function() {
this.createEditView({"mode": "inline"});
testUpdate(this.view, this.thread);
});
it('can close the view', function() {
this.createEditView();
this.view.$('.post-cancel').click();
testCancel = function(view) {
view.$('.post-cancel').click();
expect($('.edit-post-form')).not.toExist();
}
it('can close the view in tab mode', function() {
this.createEditView();
testCancel(this.view);
});
it('can close the view in inline mode', function() {
this.createEditView({"mode": "inline"});
testCancel(this.view);
});
});
}).call(this);
......@@ -11,6 +11,7 @@ describe "DiscussionThreadView", ->
# Avoid unnecessary boilerplate
spyOn(DiscussionThreadShowView.prototype, "convertMath")
spyOn(DiscussionContentView.prototype, "makeWmdEditor")
spyOn(DiscussionUtil, "makeWmdEditor")
spyOn(ThreadResponseView.prototype, "renderShowView")
renderWithContent = (view, content) ->
......@@ -46,7 +47,12 @@ describe "DiscussionThreadView", ->
threadData = DiscussionViewSpecHelper.makeThreadWithProps({closed: originallyClosed})
thread = new Thread(threadData)
discussion = new Discussion(thread)
view = new DiscussionThreadView({ model: thread, el: $("#fixture-element"), mode: mode})
view = new DiscussionThreadView(
model: thread
el: $("#fixture-element")
mode: mode
course_settings: DiscussionSpecHelper.makeCourseSettings()
)
renderWithContent(view, {resp_total: 1, children: [{}]})
if mode == "inline"
view.expand()
......@@ -70,7 +76,12 @@ describe "DiscussionThreadView", ->
describe "tab mode", ->
beforeEach ->
@view = new DiscussionThreadView({ model: @thread, el: $("#fixture-element"), mode: "tab"})
@view = new DiscussionThreadView(
model: @thread
el: $("#fixture-element")
mode: "tab"
course_settings: DiscussionSpecHelper.makeCourseSettings()
)
describe "response count and pagination", ->
it "correctly render for a thread with no responses", ->
......@@ -111,7 +122,12 @@ describe "DiscussionThreadView", ->
describe "inline mode", ->
beforeEach ->
@view = new DiscussionThreadView({ model: @thread, el: $("#fixture-element"), mode: "inline"})
@view = new DiscussionThreadView(
model: @thread
el: $("#fixture-element")
mode: "inline"
course_settings: DiscussionSpecHelper.makeCourseSettings()
)
describe "render", ->
it "shows content that should be visible when collapsed", ->
......@@ -178,11 +194,26 @@ describe "DiscussionThreadView", ->
expect($(".post-body").text()).toEqual(maliciousAbbreviation)
expect($(".post-body").html()).not.toContain("<script")
it "re-renders the show view correctly when leaving the edit view", ->
DiscussionViewSpecHelper.setNextResponseContent({resp_total: 0, children: []})
@view.render()
@view.expand()
assertExpandedContentVisible(@view, true)
@view.edit()
assertContentVisible(@view, ".edit-post-body", true)
expect(@view.$el.find(".post-actions-list").length).toBe(0)
@view.closeEditView(DiscussionSpecHelper.makeEventSpy())
expect(@view.$el.find(".edit-post-body").length).toBe(0)
assertContentVisible(@view, ".post-actions-list", true)
describe "for question threads", ->
beforeEach ->
@thread.set("thread_type", "question")
@view = new DiscussionThreadView(
{model: @thread, el: $("#fixture-element"), mode: "tab"}
model: @thread
el: $("#fixture-element")
mode: "tab"
course_settings: DiscussionSpecHelper.makeCourseSettings()
)
renderTestCase = (view, numEndorsed, numNonEndorsed) ->
......
......@@ -127,7 +127,6 @@ describe "NewPostView", ->
view.$(".cancel").click()
expect(eventSpy).toHaveBeenCalled()
expect(view.$(".post-errors").html()).toEqual("");
if mode == "tab"
expect($("input[id$='post-type-question']")).toBeChecked()
expect($("input[id$='post-type-discussion']")).not.toBeChecked()
expect(view.$(".js-post-title").val()).toEqual("");
......
......@@ -17,12 +17,10 @@ describe 'ResponseCommentView', ->
spyOn(DiscussionUtil, "makeWmdEditor")
@view.render()
makeEventSpy = () -> jasmine.createSpyObj('event', ['preventDefault', 'target'])
describe '_delete', ->
beforeEach ->
@comment.updateInfo {ability: {can_delete: true}}
@event = makeEventSpy()
@event = DiscussionSpecHelper.makeEventSpy()
spyOn(@comment, "remove")
spyOn(@view.$el, "remove")
......@@ -81,9 +79,9 @@ describe 'ResponseCommentView', ->
# Without calling renderEditView first, renderShowView is a no-op
@view.renderEditView()
@view.renderShowView()
@view.showView.trigger "comment:_delete", makeEventSpy()
@view.showView.trigger "comment:_delete", DiscussionSpecHelper.makeEventSpy()
expect(@view._delete).toHaveBeenCalled()
@view.showView.trigger "comment:edit", makeEventSpy()
@view.showView.trigger "comment:edit", DiscussionSpecHelper.makeEventSpy()
expect(@view.edit).toHaveBeenCalled()
expect(@view.$(".edit-post-form#comment_#{@comment.id}")).not.toHaveClass("edit-post-form")
......@@ -92,9 +90,9 @@ describe 'ResponseCommentView', ->
spyOn(@view, "update")
spyOn(@view, "cancelEdit")
@view.renderEditView()
@view.editView.trigger "comment:update", makeEventSpy()
@view.editView.trigger "comment:update", DiscussionSpecHelper.makeEventSpy()
expect(@view.update).toHaveBeenCalled()
@view.editView.trigger "comment:cancel_edit", makeEventSpy()
@view.editView.trigger "comment:cancel_edit", DiscussionSpecHelper.makeEventSpy()
expect(@view.cancelEdit).toHaveBeenCalled()
expect(@view.$(".edit-post-form#comment_#{@comment.id}")).toHaveClass("edit-post-form")
......@@ -138,7 +136,7 @@ describe 'ResponseCommentView', ->
it 'calls the update endpoint correctly and displays the show view on success', ->
@ajaxSucceed = true
@view.update(makeEventSpy())
@view.update(DiscussionSpecHelper.makeEventSpy())
expect($.ajax).toHaveBeenCalled()
expect($.ajax.mostRecentCall.args[0].url._parts.path).toEqual('/courses/edX/999/test/discussion/comments/01234567/update')
expect($.ajax.mostRecentCall.args[0].data.body).toEqual(@updatedBody)
......@@ -148,7 +146,7 @@ describe 'ResponseCommentView', ->
it 'handles AJAX errors', ->
originalBody = @comment.get("body")
@ajaxSucceed = false
@view.update(makeEventSpy())
@view.update(DiscussionSpecHelper.makeEventSpy())
expect($.ajax).toHaveBeenCalled()
expect($.ajax.mostRecentCall.args[0].url._parts.path).toEqual('/courses/edX/999/test/discussion/comments/01234567/update')
expect($.ajax.mostRecentCall.args[0].data.body).toEqual(@updatedBody)
......
......@@ -98,14 +98,18 @@ if Backbone?
@$el.append($discussion)
@newPostForm = $('.new-post-article')
@threadviews = @discussion.map (thread) ->
new DiscussionThreadView(
@threadviews = @discussion.map (thread) =>
view = new DiscussionThreadView(
el: @$("article#thread_#{thread.id}"),
model: thread,
mode: "inline",
course_settings: @course_settings,
topicId: discussionId
)
thread.on "thread:thread_type_updated", ->
view.rerender()
view.expand()
return view
_.each @threadviews, (dtv) -> dtv.render()
DiscussionUtil.bulkUpdateContentInfo(window.$$annotated_content_info)
@newPostView = new NewPostView(
......
......@@ -17,7 +17,7 @@
this.mode = options.mode || 'inline';
this.course_settings = options.course_settings;
this.threadType = this.model.get('thread_type');
this.topicId = options.topicId;
this.topicId = this.model.get('commentable_id');
_.bindAll(this);
return this;
},
......@@ -28,7 +28,6 @@
this.template = _.template($('#thread-edit-template').html());
this.$el.html(this.template(this.model.toJSON())).appendTo(this.container);
this.submitBtn = this.$('.post-update');
if (this.isTabMode()) {
threadTypeTemplate = _.template($("#thread-type-template").html());
this.addField(threadTypeTemplate({form_id: formId}));
this.$("#" + formId + "-post-type-" + this.threadType).attr('checked', true);
......@@ -37,7 +36,6 @@
course_settings: this.course_settings
});
this.addField(this.topicView.render());
}
DiscussionUtil.makeWmdEditor(this.$el, $.proxy(this.$, this), 'edit-post-body');
return this;
},
......@@ -55,7 +53,13 @@
var title = this.$('.edit-post-title').val(),
threadType = this.$(".post-type-input:checked").val(),
body = this.$('.edit-post-body textarea').val(),
commentableId = this.isTabMode() ? this.topicView.getCurrentTopicId() : null;
commentableId = this.topicView.getCurrentTopicId(),
postData = {
title: title,
thread_type: threadType,
body: body,
commentable_id: commentableId
};
return DiscussionUtil.safeAjax({
$elem: this.submitBtn,
......@@ -64,32 +68,18 @@
type: 'POST',
dataType: 'json',
async: false, // @TODO when the rest of the stuff below is made to work properly..
data: {
title: title,
thread_type: threadType,
body: body,
commentable_id: commentableId
},
data: postData,
error: DiscussionUtil.formErrorHandler(this.$('.post-errors')),
success: function() {
var newAttrs = {
title: title,
body: body
};
// @TODO: Move this out of the callback, this makes it feel sluggish
this.$('.edit-post-title').val('').attr('prev-text', '');
this.$('.edit-post-body textarea').val('').attr('prev-text', '');
this.$('.wmd-preview p').html('');
if (this.isTabMode()) {
_.extend(newAttrs, {
thread_type: threadType,
commentable_id: commentableId,
courseware_title: this.topicView.getFullTopicName()
});
}
this.model.set(newAttrs).unset('abbreviatedBody');
postData.courseware_title = this.topicView.getFullTopicName();
this.model.set(postData).unset('abbreviatedBody');
this.trigger('thread:updated');
if (this.threadType !== threadType) {
this.model.set("thread_type", threadType)
this.model.trigger('thread:thread_type_updated');
this.trigger('comment:endorse');
}
......
......@@ -34,6 +34,20 @@ if Backbone?
if @isQuestion()
@markedAnswers = new Comments()
rerender: () ->
if @showView?
@showView.undelegateEvents()
@undelegateEvents()
@$el.empty()
@initialize(
mode: @mode
model: @model
el: @el
course_settings: @course_settings
topicId: @topicId
)
@render()
renderTemplate: ->
@template = _.template($("#thread-template").html())
@template(@model.toJSON())
......@@ -272,7 +286,6 @@ if Backbone?
model: @model
mode: @mode
course_settings: @options.course_settings
topicId: @model.get('commentable_id')
)
@editView.bind "thread:updated thread:cancel_edit", @closeEditView
@editView.bind "comment:endorse", @endorseThread
......@@ -296,6 +309,9 @@ if Backbone?
closeEditView: (event) =>
@createShowView()
@renderShowView()
# next call is necessary to re-render the post action controls after
# submitting or cancelling a thread edit in inline mode.
@$el.find(".post-extended-content").show()
# If you use "delete" here, it will compile down into JS that includes the
# use of DiscussionThreadView.prototype.delete, and that will break IE8
......
......@@ -16,9 +16,9 @@ if Backbone?
form_id: @mode + (if @topicId then "-" + @topicId else "")
})
@$el.html(_.template($("#new-post-template").html(), context))
if @isTabMode()
threadTypeTemplate = _.template($("#thread-type-template").html());
@addField(threadTypeTemplate({form_id: _.uniqueId("form-")}));
if @isTabMode()
@topicView = new DiscussionTopicMenuView {
topicId: @topicId
course_settings: @course_settings
......
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
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