Commit 2b8c02a7 by E. Kolpakov Committed by Andy Armstrong

Discussion coffee files to JS

parent b1af59ad
!view/discussion_thread_edit_view_spec.js
!view/discussion_topic_menu_view_spec.js
!views/discussion_thread_edit_view.js
!views/discussion_topic_menu_view.js
if Backbone? /* globals DiscussionThreadListView, DiscussionThreadView, DiscussionUtil, NewPostView */
class @DiscussionRouter extends Backbone.Router (function() {
routes: 'use strict';
"": "allThreads" var __hasProp = {}.hasOwnProperty,
":forum_name/threads/:thread_id" : "showThread" __extends = function(child, parent) {
for (var key in parent) {
initialize: (options) -> if (__hasProp.call(parent, key)) {
@discussion = options['discussion'] child[key] = parent[key];
@course_settings = options['course_settings'] }
}
@nav = new DiscussionThreadListView( function ctor() {
collection: @discussion, this.constructor = child;
el: $(".forum-nav"), }
courseSettings: @course_settings
) ctor.prototype = parent.prototype;
@nav.on "thread:selected", @navigateToThread child.prototype = new ctor();
@nav.on "thread:removed", @navigateToAllThreads child.__super__ = parent.prototype;
@nav.on "threads:rendered", @setActiveThread return child;
@nav.on "thread:created", @navigateToThread };
@nav.render()
if (typeof Backbone !== "undefined" && Backbone !== null) {
@newPost = $('.new-post-article') this.DiscussionRouter = (function(_super) {
@newPostView = new NewPostView(
el: @newPost, __extends(DiscussionRouter, _super);
collection: @discussion,
course_settings: @course_settings, function DiscussionRouter() {
mode: "tab" var self = this;
) this.hideNewPost = function() {
@newPostView.render() return DiscussionRouter.prototype.hideNewPost.apply(self, arguments);
@listenTo( @newPostView, 'newPost:cancel', @hideNewPost ) };
$('.new-post-btn').bind "click", @showNewPost this.showNewPost = function() {
$('.new-post-btn').bind "keydown", (event) => DiscussionUtil.activateOnSpace(event, @showNewPost) return DiscussionRouter.prototype.showNewPost.apply(self, arguments);
};
allThreads: -> this.navigateToAllThreads = function() {
@nav.updateSidebar() return DiscussionRouter.prototype.navigateToAllThreads.apply(self, arguments);
@nav.goHome() };
this.navigateToThread = function() {
setActiveThread: => return DiscussionRouter.prototype.navigateToThread.apply(self, arguments);
if @thread };
@nav.setActiveThread(@thread.get("id")) this.showMain = function() {
else return DiscussionRouter.prototype.showMain.apply(self, arguments);
@nav.goHome };
this.setActiveThread = function() {
showThread: (forum_name, thread_id) -> return DiscussionRouter.prototype.setActiveThread.apply(self, arguments);
@thread = @discussion.get(thread_id) };
@thread.set("unread_comments_count", 0) return DiscussionRouter.__super__.constructor.apply(this, arguments);
@thread.set("read", true) }
@setActiveThread()
@showMain() DiscussionRouter.prototype.routes = {
"": "allThreads",
showMain: => ":forum_name/threads/:thread_id": "showThread"
if(@main) };
@main.cleanup()
@main.undelegateEvents() DiscussionRouter.prototype.initialize = function(options) {
unless($(".forum-content").is(":visible")) var self = this;
$(".forum-content").fadeIn() this.discussion = options.discussion;
if(@newPost.is(":visible")) this.course_settings = options.course_settings;
@newPost.fadeOut() this.nav = new DiscussionThreadListView({
collection: this.discussion,
@main = new DiscussionThreadView( el: $(".forum-nav"),
el: $(".forum-content"), courseSettings: this.course_settings
model: @thread, });
mode: "tab", this.nav.on("thread:selected", this.navigateToThread);
course_settings: @course_settings, this.nav.on("thread:removed", this.navigateToAllThreads);
) this.nav.on("threads:rendered", this.setActiveThread);
@main.render() this.nav.on("thread:created", this.navigateToThread);
@main.on "thread:responses:rendered", => this.nav.render();
@nav.updateSidebar() this.newPost = $('.new-post-article');
@thread.on "thread:thread_type_updated", @showMain this.newPostView = new NewPostView({
el: this.newPost,
navigateToThread: (thread_id) => collection: this.discussion,
thread = @discussion.get(thread_id) course_settings: this.course_settings,
@navigate("#{thread.get("commentable_id")}/threads/#{thread_id}", trigger: true) mode: "tab"
});
navigateToAllThreads: => this.newPostView.render();
@navigate("", trigger: true) this.listenTo(this.newPostView, 'newPost:cancel', this.hideNewPost);
$('.new-post-btn').bind("click", this.showNewPost);
showNewPost: (event) => return $('.new-post-btn').bind("keydown", function(event) {
$('.forum-content').fadeOut( return DiscussionUtil.activateOnSpace(event, self.showNewPost);
duration: 200 });
complete: => };
@newPost.fadeIn(200).focus()
) DiscussionRouter.prototype.allThreads = function() {
this.nav.updateSidebar();
hideNewPost: => return this.nav.goHome();
@newPost.fadeOut( };
duration: 200
complete: => DiscussionRouter.prototype.setActiveThread = function() {
$('.forum-content').fadeIn(200).find('.thread-wrapper').focus() if (this.thread) {
) return this.nav.setActiveThread(this.thread.get("id"));
} else {
return this.nav.goHome;
}
};
DiscussionRouter.prototype.showThread = function(forum_name, thread_id) {
this.thread = this.discussion.get(thread_id);
this.thread.set("unread_comments_count", 0);
this.thread.set("read", true);
this.setActiveThread();
return this.showMain();
};
DiscussionRouter.prototype.showMain = function() {
var self = this;
if (this.main) {
this.main.cleanup();
this.main.undelegateEvents();
}
if (!($(".forum-content").is(":visible"))) {
$(".forum-content").fadeIn();
}
if (this.newPost.is(":visible")) {
this.newPost.fadeOut();
}
this.main = new DiscussionThreadView({
el: $(".forum-content"),
model: this.thread,
mode: "tab",
course_settings: this.course_settings
});
this.main.render();
this.main.on("thread:responses:rendered", function() {
return self.nav.updateSidebar();
});
return this.thread.on("thread:thread_type_updated", this.showMain);
};
DiscussionRouter.prototype.navigateToThread = function(thread_id) {
var thread;
thread = this.discussion.get(thread_id);
return this.navigate("" + (thread.get("commentable_id")) + "/threads/" + thread_id, {
trigger: true
});
};
DiscussionRouter.prototype.navigateToAllThreads = function() {
return this.navigate("", {
trigger: true
});
};
DiscussionRouter.prototype.showNewPost = function() {
var self = this;
return $('.forum-content').fadeOut({
duration: 200,
complete: function() {
return self.newPost.fadeIn(200).focus();
}
});
};
DiscussionRouter.prototype.hideNewPost = function() {
return this.newPost.fadeOut({
duration: 200,
complete: function() {
return $('.forum-content').fadeIn(200).find('.thread-wrapper').focus();
}
});
};
return DiscussionRouter;
})(Backbone.Router);
}
}).call(window);
if Backbone? /* global $$course_id, Content, Discussion, DiscussionRouter, DiscussionCourseSettings,
DiscussionApp = DiscussionUser, DiscussionUserProfileView, DiscussionUtil */
start: (elem)-> (function() {
# TODO: Perhaps eliminate usage of global variables when possible 'use strict';
DiscussionUtil.loadRolesFromContainer() var DiscussionApp, DiscussionProfileApp;
element = $(elem)
window.$$course_id = element.data("course-id") if (typeof Backbone !== "undefined" && Backbone !== null) {
window.courseName = element.data("course-name") DiscussionApp = {
user_info = element.data("user-info") start: function(elem) {
sort_preference = element.data("sort-preference") var content_info, course_settings, discussion, element, sort_preference, thread_pages, threads,
threads = element.data("threads") user, user_info;
thread_pages = element.data("thread-pages") DiscussionUtil.loadRolesFromContainer();
content_info = element.data("content-info") element = $(elem);
user = new DiscussionUser(user_info) window.$$course_id = element.data("course-id");
DiscussionUtil.setUser(user) window.courseName = element.data("course-name");
window.user = user user_info = element.data("user-info");
Content.loadContentInfos(content_info) sort_preference = element.data("sort-preference");
discussion = new Discussion(threads, {pages: thread_pages, sort: sort_preference}) threads = element.data("threads");
course_settings = new DiscussionCourseSettings(element.data("course-settings")) thread_pages = element.data("thread-pages");
new DiscussionRouter({discussion: discussion, course_settings: course_settings}) content_info = element.data("content-info");
Backbone.history.start({pushState: true, root: "/courses/#{$$course_id}/discussion/forum/"}) user = new DiscussionUser(user_info);
DiscussionProfileApp = DiscussionUtil.setUser(user);
start: (elem) -> window.user = user;
# Roles are not included in user profile page, but they are not used for anything Content.loadContentInfos(content_info);
DiscussionUtil.loadRoles({"Moderator": [], "Administrator": [], "Community TA": []}) discussion = new Discussion(threads, {
element = $(elem) pages: thread_pages,
window.$$course_id = element.data("course-id") sort: sort_preference
threads = element.data("threads") });
user_info = element.data("user-info") course_settings = new DiscussionCourseSettings(element.data("course-settings"));
window.user = new DiscussionUser(user_info) // suppressing Do not use 'new' for side effects.
page = element.data("page") /* jshint -W031*/
numPages = element.data("num-pages") new DiscussionRouter({
new DiscussionUserProfileView(el: element, collection: threads, page: page, numPages: numPages) discussion: discussion,
$ -> course_settings: course_settings
$("section.discussion").each (index, elem) -> });
DiscussionApp.start(elem) /* jshint +W031*/
$("section.discussion-user-threads").each (index, elem) -> return Backbone.history.start({
DiscussionProfileApp.start(elem) pushState: true,
root: "/courses/" + $$course_id + "/discussion/forum/"
});
}
};
DiscussionProfileApp = {
start: function(elem) {
var element, numPages, page, threads, user_info;
DiscussionUtil.loadRoles({
"Moderator": [],
"Administrator": [],
"Community TA": []
});
element = $(elem);
window.$$course_id = element.data("course-id");
threads = element.data("threads");
user_info = element.data("user-info");
window.user = new DiscussionUser(user_info);
page = element.data("page");
numPages = element.data("num-pages");
return new DiscussionUserProfileView({
el: element,
collection: threads,
page: page,
numPages: numPages
});
}
};
$(function() {
$("section.discussion").each(function(index, elem) {
return DiscussionApp.start(elem);
});
return $("section.discussion-user-threads").each(function(index, elem) {
return DiscussionProfileApp.start(elem);
});
});
}
}).call(window);
if Backbone? (function() {
class @DiscussionCourseSettings extends Backbone.Model 'use strict';
var __hasProp = {}.hasOwnProperty,
__extends = function(child, parent) {
for (var key in parent) {
if (__hasProp.call(parent, key)) {
child[key] = parent[key];
}
}
function ctor() {
this.constructor = child;
}
ctor.prototype = parent.prototype;
child.prototype = new ctor();
child.__super__ = parent.prototype;
return child;
};
if (typeof Backbone !== "undefined" && Backbone !== null) {
this.DiscussionCourseSettings = (function(_super) {
__extends(DiscussionCourseSettings, _super);
function DiscussionCourseSettings() {
return DiscussionCourseSettings.__super__.constructor.apply(this, arguments);
}
return DiscussionCourseSettings;
})(Backbone.Model);
}
}).call(this);
if Backbone? (function() {
class @DiscussionUser extends Backbone.Model 'use strict';
following: (thread) -> var __hasProp = {}.hasOwnProperty,
_.include(@get('subscribed_thread_ids'), thread.id) __extends = function(child, parent) {
for (var key in parent) {
if (__hasProp.call(parent, key)) {
child[key] = parent[key];
}
}
function ctor() {
this.constructor = child;
}
voted: (thread) -> ctor.prototype = parent.prototype;
_.include(@get('upvoted_ids'), thread.id) child.prototype = new ctor();
child.__super__ = parent.prototype;
return child;
};
vote: (thread) -> if (typeof Backbone !== "undefined" && Backbone !== null) {
@get('upvoted_ids').push(thread.id) this.DiscussionUser = (function(_super) {
thread.vote()
unvote: (thread) -> __extends(DiscussionUser, _super);
@set('upvoted_ids', _.without(@get('upvoted_ids'), thread.id))
thread.unvote() function DiscussionUser() {
return DiscussionUser.__super__.constructor.apply(this, arguments);
}
DiscussionUser.prototype.following = function(thread) {
return _.include(this.get('subscribed_thread_ids'), thread.id);
};
DiscussionUser.prototype.voted = function(thread) {
return _.include(this.get('upvoted_ids'), thread.id);
};
DiscussionUser.prototype.vote = function(thread) {
this.get('upvoted_ids').push(thread.id);
return thread.vote();
};
DiscussionUser.prototype.unvote = function(thread) {
this.set('upvoted_ids', _.without(this.get('upvoted_ids'), thread.id));
return thread.unvote();
};
return DiscussionUser;
})(Backbone.Model);
}
}).call(this);
(function(Backbone) { /* globals DiscussionTopicMenuView, DiscussionUtil */
(function() {
'use strict'; 'use strict';
if (Backbone) { if (Backbone) {
this.DiscussionThreadEditView = Backbone.View.extend({ this.DiscussionThreadEditView = Backbone.View.extend({
...@@ -50,7 +51,7 @@ ...@@ -50,7 +51,7 @@
return this; return this;
}, },
isTabMode: function () { isTabMode: function() {
return this.mode === 'tab'; return this.mode === 'tab';
}, },
...@@ -85,7 +86,7 @@ ...@@ -85,7 +86,7 @@
this.model.set(postData).unset('abbreviatedBody'); this.model.set(postData).unset('abbreviatedBody');
this.trigger('thread:updated'); this.trigger('thread:updated');
if (this.threadType !== threadType) { if (this.threadType !== threadType) {
this.model.set("thread_type", threadType) this.model.set("thread_type", threadType);
this.model.trigger('thread:thread_type_updated'); this.model.trigger('thread:thread_type_updated');
this.trigger('comment:endorse'); this.trigger('comment:endorse');
} }
...@@ -109,4 +110,4 @@ ...@@ -109,4 +110,4 @@
} }
}); });
} }
}).call(this, Backbone); }).call(window); // jshint ignore:line
if Backbone? /* globals DiscussionUtil, MathJax */
class @DiscussionThreadProfileView extends Backbone.View (function() {
render: -> 'use strict';
@convertMath() var __hasProp = {}.hasOwnProperty,
@abbreviateBody() __extends = function(child, parent) {
params = $.extend(@model.toJSON(),{permalink: @model.urlFor('retrieve')}) for (var key in parent) {
if not @model.get('anonymous') if (__hasProp.call(parent, key)) {
params = $.extend(params, user:{username: @model.username, user_url: @model.user_url}) child[key] = parent[key];
@$el.html(_.template($("#profile-thread-template").html())(params)) }
@$("span.timeago").timeago() }
element = @$(".post-body") function ctor() {
if MathJax? this.constructor = child;
MathJax.Hub.Queue ["Typeset", MathJax.Hub, element[0]] }
@
ctor.prototype = parent.prototype;
convertMath: -> child.prototype = new ctor();
@model.set('markdownBody', DiscussionUtil.postMathJaxProcessor DiscussionUtil.markdownWithHighlight @model.get('body')) child.__super__ = parent.prototype;
return child;
abbreviateBody: -> };
abbreviated = DiscussionUtil.abbreviateHTML @model.get('markdownBody'), 140
@model.set('abbreviatedBody', abbreviated) if (typeof Backbone !== "undefined" && Backbone !== null) {
this.DiscussionThreadProfileView = (function(_super) {
__extends(DiscussionThreadProfileView, _super);
function DiscussionThreadProfileView() {
return DiscussionThreadProfileView.__super__.constructor.apply(this, arguments);
}
DiscussionThreadProfileView.prototype.render = function() {
var element, params;
this.convertMath();
this.abbreviateBody();
params = $.extend(this.model.toJSON(), {
permalink: this.model.urlFor('retrieve')
});
if (!this.model.get('anonymous')) {
params = $.extend(params, {
user: {
username: this.model.username,
user_url: this.model.user_url
}
});
}
this.$el.html(_.template($("#profile-thread-template").html())(params));
this.$("span.timeago").timeago();
element = this.$(".post-body");
if (typeof MathJax !== "undefined" && MathJax !== null) {
MathJax.Hub.Queue(["Typeset", MathJax.Hub, element[0]]);
}
return this;
};
DiscussionThreadProfileView.prototype.convertMath = function() {
return this.model.set(
'markdownBody',
DiscussionUtil.postMathJaxProcessor(DiscussionUtil.markdownWithHighlight(this.model.get('body')))
);
};
DiscussionThreadProfileView.prototype.abbreviateBody = function() {
var abbreviated;
abbreviated = DiscussionUtil.abbreviateHTML(this.model.get('markdownBody'), 140);
return this.model.set('abbreviatedBody', abbreviated);
};
return DiscussionThreadProfileView;
})(Backbone.View);
}
}).call(window);
if Backbone? /* globals DiscussionUtil, DiscussionContentShowView, MathJax */
class @DiscussionThreadShowView extends DiscussionContentShowView (function() {
initialize: (options) -> 'use strict';
super() var __hasProp = {}.hasOwnProperty,
@mode = options.mode or "inline" # allowed values are "tab" or "inline" __extends = function(child, parent) {
if @mode not in ["tab", "inline"] for (var key in parent) {
throw new Error("invalid mode: " + @mode) if (__hasProp.call(parent, key)) {
child[key] = parent[key];
renderTemplate: -> }
@template = _.template($("#thread-show-template").html()) }
context = $.extend( function ctor() {
{ this.constructor = child;
mode: @mode, }
flagged: @model.isFlagged(),
author_display: @getAuthorDisplay(), ctor.prototype = parent.prototype;
cid: @model.cid, child.prototype = new ctor();
readOnly: $('.discussion-module').data('read-only') child.__super__ = parent.prototype;
}, return child;
@model.attributes, };
)
@template(context) if (typeof Backbone !== "undefined" && Backbone !== null) {
this.DiscussionThreadShowView = (function(_super) {
render: ->
@$el.html(@renderTemplate()) __extends(DiscussionThreadShowView, _super);
@delegateEvents()
@renderAttrs() function DiscussionThreadShowView() {
@$("span.timeago").timeago() return DiscussionThreadShowView.__super__.constructor.apply(this, arguments);
@convertMath() }
@highlight @$(".post-body")
@highlight @$("h1,h3") DiscussionThreadShowView.prototype.initialize = function(options) {
@ var _ref;
DiscussionThreadShowView.__super__.initialize.call(this);
convertMath: -> this.mode = options.mode || "inline";
element = @$(".post-body") if ((_ref = this.mode) !== "tab" && _ref !== "inline") {
element.html DiscussionUtil.postMathJaxProcessor DiscussionUtil.markdownWithHighlight element.text() throw new Error("invalid mode: " + this.mode);
if MathJax? }
MathJax.Hub.Queue ["Typeset", MathJax.Hub, element[0]] };
edit: (event) -> DiscussionThreadShowView.prototype.renderTemplate = function() {
@trigger "thread:edit", event var context;
this.template = _.template($("#thread-show-template").html());
_delete: (event) -> context = $.extend({
@trigger "thread:_delete", event mode: this.mode,
flagged: this.model.isFlagged(),
highlight: (el) -> author_display: this.getAuthorDisplay(),
if el.html() cid: this.model.cid,
el.html(el.html().replace(/&lt;mark&gt;/g, "<mark>").replace(/&lt;\/mark&gt;/g, "</mark>")) readOnly: $('.discussion-module').data('read-only')
}, this.model.attributes);
return this.template(context);
};
DiscussionThreadShowView.prototype.render = function() {
this.$el.html(this.renderTemplate());
this.delegateEvents();
this.renderAttrs();
this.$("span.timeago").timeago();
this.convertMath();
this.highlight(this.$(".post-body"));
this.highlight(this.$("h1,h3"));
return this;
};
DiscussionThreadShowView.prototype.convertMath = function() {
var element;
element = this.$(".post-body");
element.html(DiscussionUtil.postMathJaxProcessor(DiscussionUtil.markdownWithHighlight(element.text())));
if (typeof MathJax !== "undefined" && MathJax !== null) {
return MathJax.Hub.Queue(["Typeset", MathJax.Hub, element[0]]);
}
};
DiscussionThreadShowView.prototype.edit = function(event) {
return this.trigger("thread:edit", event);
};
DiscussionThreadShowView.prototype._delete = function(event) {
return this.trigger("thread:_delete", event);
};
DiscussionThreadShowView.prototype.highlight = function(el) {
if (el.html()) {
return el.html(el.html().replace(/&lt;mark&gt;/g, "<mark>").replace(/&lt;\/mark&gt;/g, "</mark>"));
}
};
return DiscussionThreadShowView;
})(DiscussionContentShowView);
}
}).call(window);
(function(Backbone) { (function() {
'use strict'; 'use strict';
if (Backbone) { if (Backbone) {
this.DiscussionTopicMenuView = Backbone.View.extend({ this.DiscussionTopicMenuView = Backbone.View.extend({
...@@ -42,7 +42,9 @@ ...@@ -42,7 +42,9 @@
this.selectedTopic = this.$('.js-selected-topic'); this.selectedTopic = this.$('.js-selected-topic');
this.hideTopicDropdown(); this.hideTopicDropdown();
if (this.getCurrentTopicId()) { if (this.getCurrentTopicId()) {
this.setTopic(this.$('a.topic-title').filter('[data-discussion-id="' + this.getCurrentTopicId() + '"]')); this.setTopic(this.$('a.topic-title').filter(
'[data-discussion-id="' + this.getCurrentTopicId() + '"]')
);
} else { } else {
this.setTopic(this.$('a.topic-title').first()); this.setTopic(this.$('a.topic-title').first());
} }
...@@ -174,7 +176,7 @@ ...@@ -174,7 +176,7 @@
// TODO: this helper class duplicates functionality in DiscussionThreadListView.filterTopics // TODO: this helper class duplicates functionality in DiscussionThreadListView.filterTopics
// for use with a very similar category dropdown in the New Post form. The two menus' implementations // for use with a very similar category dropdown in the New Post form. The two menus' implementations
// should be merged into a single reusable view. // should be merged into a single reusable view.
filterDrop: function (e) { filterDrop: function(e) {
var $drop, $items, query; var $drop, $items, query;
$drop = $(e.target).parents('.topic-menu-wrapper'); $drop = $(e.target).parents('.topic-menu-wrapper');
query = $(e.target).val(); query = $(e.target).val();
...@@ -186,14 +188,14 @@ ...@@ -186,14 +188,14 @@
} }
$items.addClass('hidden'); $items.addClass('hidden');
$items.each(function (_index, item) { $items.each(function(_index, item) {
var path, pathText, pathTitles; var path, pathText, pathTitles;
path = $(item).parents(".topic-menu-item").andSelf(); path = $(item).parents(".topic-menu-item").andSelf();
pathTitles = path.children(".topic-title").map(function (_, elem) { pathTitles = path.children(".topic-title").map(function(_, elem) {
return $(elem).text(); return $(elem).text();
}).get(); }).get();
pathText = pathTitles.join(" / ").toLowerCase(); pathText = pathTitles.join(" / ").toLowerCase();
if (query.split(" ").every(function (term) { if (query.split(" ").every(function(term) {
return pathText.search(term.toLowerCase()) !== -1; return pathText.search(term.toLowerCase()) !== -1;
})) { })) {
$(item).removeClass('hidden'); $(item).removeClass('hidden');
...@@ -204,4 +206,4 @@ ...@@ -204,4 +206,4 @@
} }
}); });
} }
}).call(this, Backbone); }).call(this);
if Backbone? /* globals Discussion, DiscussionThreadProfileView, DiscussionUtil, URI */
class @DiscussionUserProfileView extends Backbone.View (function() {
events: 'use strict';
"click .discussion-paginator a": "changePage" var __hasProp = {}.hasOwnProperty,
__extends = function(child, parent) {
initialize: (options) -> for (var key in parent) {
super() if (__hasProp.call(parent, key)) {
@page = options.page child[key] = parent[key];
@numPages = options.numPages }
@discussion = new Discussion() }
@discussion.on("reset", @render) function ctor() {
@discussion.reset(@collection, {silent: false}) this.constructor = child;
}
render: () =>
@$el.html(_.template($("#user-profile-template").html())({threads: @discussion.models})) ctor.prototype = parent.prototype;
@discussion.map (thread) -> child.prototype = new ctor();
new DiscussionThreadProfileView(el: @$("article#thread_#{thread.id}"), model: thread).render() child.__super__ = parent.prototype;
baseUri = URI(window.location).removeSearch("page") return child;
pageUrlFunc = (page) -> baseUri.clone().addSearch("page", page) };
paginationParams = DiscussionUtil.getPaginationParams(@page, @numPages, pageUrlFunc)
@$el.find(".discussion-pagination").html(_.template($("#pagination-template").html())(paginationParams)) if (typeof Backbone !== "undefined" && Backbone !== null) {
this.DiscussionUserProfileView = (function(_super) {
changePage: (event) ->
event.preventDefault() __extends(DiscussionUserProfileView, _super);
url = $(event.target).attr("href")
DiscussionUtil.safeAjax function DiscussionUserProfileView() {
$elem: @$el var self = this;
$loading: $(event.target) this.render = function() {
takeFocus: true return DiscussionUserProfileView.prototype.render.apply(self, arguments);
url: url };
type: "GET" return DiscussionUserProfileView.__super__.constructor.apply(this, arguments);
dataType: "json" }
success: (response, textStatus, xhr) =>
@page = response.page DiscussionUserProfileView.prototype.events = {
@numPages = response.num_pages "click .discussion-paginator a": "changePage"
@discussion.reset(response.discussion_data, {silent: false}) };
history.pushState({}, "", url)
$("html, body").animate({ scrollTop: 0 }); DiscussionUserProfileView.prototype.initialize = function(options) {
error: => DiscussionUserProfileView.__super__.initialize.call(this);
DiscussionUtil.discussionAlert( this.page = options.page;
gettext("Sorry"), this.numPages = options.numPages;
gettext("We had some trouble loading the page you requested. Please try again.") this.discussion = new Discussion();
) this.discussion.on("reset", this.render);
return this.discussion.reset(this.collection, {
silent: false
});
};
DiscussionUserProfileView.prototype.render = function() {
var baseUri, pageUrlFunc, paginationParams,
self = this;
this.$el.html(_.template($("#user-profile-template").html())({
threads: this.discussion.models
}));
this.discussion.map(function(thread) {
return new DiscussionThreadProfileView({
el: self.$("article#thread_" + thread.id),
model: thread
}).render();
});
baseUri = URI(window.location).removeSearch("page");
pageUrlFunc = function(page) {
return baseUri.clone().addSearch("page", page);
};
paginationParams = DiscussionUtil.getPaginationParams(this.page, this.numPages, pageUrlFunc);
this.$el.find(".discussion-pagination")
.html(_.template($("#pagination-template").html())(paginationParams));
};
DiscussionUserProfileView.prototype.changePage = function(event) {
var url,
self = this;
event.preventDefault();
url = $(event.target).attr("href");
return DiscussionUtil.safeAjax({
$elem: this.$el,
$loading: $(event.target),
takeFocus: true,
url: url,
type: "GET",
dataType: "json",
success: function(response) {
self.page = response.page;
self.numPages = response.num_pages;
self.discussion.reset(response.discussion_data, {
silent: false
});
history.pushState({}, "", url);
return $("html, body").animate({
scrollTop: 0
});
},
error: function() {
return DiscussionUtil.discussionAlert(
gettext("Sorry"),
gettext("We had some trouble loading the page you requested. Please try again.")
);
}
});
};
return DiscussionUserProfileView;
})(Backbone.View);
}
}).call(window);
if Backbone? /* globals DiscussionUtil */
class @ResponseCommentEditView extends Backbone.View (function() {
'use strict';
var __hasProp = {}.hasOwnProperty,
__extends = function(child, parent) {
for (var key in parent) {
if (__hasProp.call(parent, key)) {
child[key] = parent[key];
}
}
function ctor() {
this.constructor = child;
}
events: ctor.prototype = parent.prototype;
"click .post-update": "update" child.prototype = new ctor();
"click .post-cancel": "cancel_edit" child.__super__ = parent.prototype;
return child;
};
$: (selector) -> if (typeof Backbone !== "undefined" && Backbone !== null) {
@$el.find(selector) this.ResponseCommentEditView = (function(_super) {
initialize: -> __extends(ResponseCommentEditView, _super);
super()
render: -> function ResponseCommentEditView() {
@template = _.template($("#response-comment-edit-template").html()) return ResponseCommentEditView.__super__.constructor.apply(this, arguments);
@$el.html(@template(@model.toJSON())) }
@delegateEvents()
DiscussionUtil.makeWmdEditor @$el, $.proxy(@$, @), "edit-comment-body"
@
update: (event) -> ResponseCommentEditView.prototype.events = {
@trigger "comment:update", event "click .post-update": "update",
"click .post-cancel": "cancel_edit"
};
cancel_edit: (event) -> ResponseCommentEditView.prototype.$ = function(selector) {
@trigger "comment:cancel_edit", event return this.$el.find(selector);
};
ResponseCommentEditView.prototype.initialize = function() {
return ResponseCommentEditView.__super__.initialize.call(this);
};
ResponseCommentEditView.prototype.render = function() {
this.template = _.template($("#response-comment-edit-template").html());
this.$el.html(this.template(this.model.toJSON()));
this.delegateEvents();
DiscussionUtil.makeWmdEditor(this.$el, $.proxy(this.$, this), "edit-comment-body");
return this;
};
ResponseCommentEditView.prototype.update = function(event) {
return this.trigger("comment:update", event);
};
ResponseCommentEditView.prototype.cancel_edit = function(event) {
return this.trigger("comment:cancel_edit", event);
};
return ResponseCommentEditView;
})(Backbone.View);
}
}).call(window);
if Backbone? /* globals DiscussionContentShowView, DiscussionUtil, MathJax */
class @ResponseCommentShowView extends DiscussionContentShowView (function() {
tagName: "li" 'use strict';
var __hasProp = {}.hasOwnProperty,
render: -> __extends = function(child, parent) {
@template = _.template($("#response-comment-show-template").html()) for (var key in parent) {
@$el.html( if (__hasProp.call(parent, key)) {
@template( child[key] = parent[key];
_.extend( }
{ }
cid: @model.cid, function ctor() {
author_display: @getAuthorDisplay(), this.constructor = child;
readOnly: $('.discussion-module').data('read-only') }
},
@model.attributes ctor.prototype = parent.prototype;
) child.prototype = new ctor();
) child.__super__ = parent.prototype;
) return child;
};
@delegateEvents()
@renderAttrs() if (typeof Backbone !== "undefined" && Backbone !== null) {
@$el.find(".timeago").timeago() this.ResponseCommentShowView = (function(_super) {
@convertMath()
@addReplyLink() __extends(ResponseCommentShowView, _super);
@
function ResponseCommentShowView() {
addReplyLink: () -> var self = this;
if @model.hasOwnProperty('parent') this.edit = function() {
name = @model.parent.get('username') ? gettext("anonymous") return ResponseCommentShowView.prototype.edit.apply(self, arguments);
html = "<a href='#comment_#{@model.parent.id}'>@#{name}</a>: " };
p = @$('.response-body p:first') this._delete = function() {
p.prepend(html) return ResponseCommentShowView.prototype._delete.apply(self, arguments);
};
convertMath: -> return ResponseCommentShowView.__super__.constructor.apply(this, arguments);
body = @$el.find(".response-body") }
body.html DiscussionUtil.postMathJaxProcessor DiscussionUtil.markdownWithHighlight body.text()
if MathJax? ResponseCommentShowView.prototype.tagName = "li";
MathJax.Hub.Queue ["Typeset", MathJax.Hub, body[0]]
ResponseCommentShowView.prototype.render = function() {
_delete: (event) => this.template = _.template($("#response-comment-show-template").html());
@trigger "comment:_delete", event this.$el.html(this.template(_.extend({
cid: this.model.cid,
edit: (event) => author_display: this.getAuthorDisplay(),
@trigger "comment:edit", event readOnly: $('.discussion-module').data('read-only')
}, this.model.attributes)));
this.delegateEvents();
this.renderAttrs();
this.$el.find(".timeago").timeago();
this.convertMath();
this.addReplyLink();
return this;
};
ResponseCommentShowView.prototype.addReplyLink = function() {
var html, name, p, _ref;
if (this.model.hasOwnProperty('parent')) {
name = (_ref = this.model.parent.get('username')) !== null ? _ref : gettext("anonymous");
html = "<a href='#comment_" + this.model.parent.id + "'>@" + name + "</a>: ";
p = this.$('.response-body p:first');
return p.prepend(html);
}
};
ResponseCommentShowView.prototype.convertMath = function() {
var body;
body = this.$el.find(".response-body");
body.html(DiscussionUtil.postMathJaxProcessor(DiscussionUtil.markdownWithHighlight(body.text())));
if (typeof MathJax !== "undefined" && MathJax !== null) {
return MathJax.Hub.Queue(["Typeset", MathJax.Hub, body[0]]);
}
};
ResponseCommentShowView.prototype._delete = function(event) {
return this.trigger("comment:_delete", event);
};
ResponseCommentShowView.prototype.edit = function(event) {
return this.trigger("comment:edit", event);
};
return ResponseCommentShowView;
})(DiscussionContentShowView);
}
}).call(window);
if Backbone? /* globals DiscussionContentView, DiscussionUtil, ResponseCommentEditView, ResponseCommentShowView */
class @ResponseCommentView extends DiscussionContentView (function() {
tagName: "li" 'use strict';
var __hasProp = {}.hasOwnProperty,
$: (selector) -> __extends = function(child, parent) {
@$el.find(selector) for (var key in parent) {
if (__hasProp.call(parent, key)) {
initialize: -> child[key] = parent[key];
super() }
}
render: -> function ctor() {
@renderShowView() this.constructor = child;
@ }
renderSubView: (view) -> ctor.prototype = parent.prototype;
view.setElement(@$el) child.prototype = new ctor();
view.render() child.__super__ = parent.prototype;
view.delegateEvents() return child;
};
renderShowView: () ->
if not @showView? if (typeof Backbone !== "undefined" && Backbone !== null) {
if @editView? this.ResponseCommentView = (function(_super) {
@editView.undelegateEvents()
@editView.$el.empty() __extends(ResponseCommentView, _super);
@editView = null
@showView = new ResponseCommentShowView(model: @model) function ResponseCommentView() {
@showView.bind "comment:_delete", @_delete var self = this;
@showView.bind "comment:edit", @edit this.update = function() {
@renderSubView(@showView) return ResponseCommentView.prototype.update.apply(self, arguments);
};
renderEditView: () -> this.edit = function() {
if not @editView? return ResponseCommentView.prototype.edit.apply(self, arguments);
if @showView? };
@showView.undelegateEvents() this.cancelEdit = function() {
@showView.$el.empty() return ResponseCommentView.prototype.cancelEdit.apply(self, arguments);
@showView = null };
@editView = new ResponseCommentEditView(model: @model) this._delete = function() {
@editView.bind "comment:update", @update return ResponseCommentView.prototype._delete.apply(self, arguments);
@editView.bind "comment:cancel_edit", @cancelEdit };
@renderSubView(@editView) return ResponseCommentView.__super__.constructor.apply(this, arguments);
}
_delete: (event) =>
event.preventDefault() ResponseCommentView.prototype.tagName = "li";
if not @model.can('can_delete')
return ResponseCommentView.prototype.$ = function(selector) {
if not confirm gettext("Are you sure you want to delete this comment?") return this.$el.find(selector);
return };
url = @model.urlFor('_delete')
$elem = $(event.target) ResponseCommentView.prototype.initialize = function() {
DiscussionUtil.safeAjax return ResponseCommentView.__super__.initialize.call(this);
$elem: $elem };
url: url
type: "POST" ResponseCommentView.prototype.render = function() {
success: (response, textStatus) => this.renderShowView();
@model.remove() return this;
@$el.remove() };
error: =>
DiscussionUtil.discussionAlert( ResponseCommentView.prototype.renderSubView = function(view) {
gettext("Sorry"), view.setElement(this.$el);
gettext("We had some trouble deleting this comment. Please try again.") view.render();
) return view.delegateEvents();
};
cancelEdit: (event) =>
@trigger "comment:cancel_edit", event ResponseCommentView.prototype.renderShowView = function() {
@renderShowView() if (!this.showView) {
if (this.editView) {
edit: (event) => this.editView.undelegateEvents();
@trigger "comment:edit", event this.editView.$el.empty();
@renderEditView() this.editView = null;
}
update: (event) => this.showView = new ResponseCommentShowView({
newBody = @editView.$(".edit-comment-body textarea").val() model: this.model
url = DiscussionUtil.urlFor("update_comment", @model.id) });
DiscussionUtil.safeAjax this.showView.bind("comment:_delete", this._delete);
$elem: $(event.target) this.showView.bind("comment:edit", this.edit);
$loading: $(event.target) return this.renderSubView(this.showView);
url: url }
type: "POST" };
dataType: "json"
data: ResponseCommentView.prototype.renderEditView = function() {
body: newBody if (!this.editView) {
error: DiscussionUtil.formErrorHandler(@$(".edit-comment-form-errors")) if (this.showView) {
success: (response, textStatus) => this.showView.undelegateEvents();
@model.set("body", newBody) this.showView.$el.empty();
@cancelEdit() this.showView = null;
}
this.editView = new ResponseCommentEditView({
model: this.model
});
this.editView.bind("comment:update", this.update);
this.editView.bind("comment:cancel_edit", this.cancelEdit);
return this.renderSubView(this.editView);
}
};
ResponseCommentView.prototype._delete = function(event) {
var $elem, url,
self = this;
event.preventDefault();
if (!this.model.can('can_delete')) {
return;
}
if (!confirm(gettext("Are you sure you want to delete this comment?"))) {
return;
}
url = this.model.urlFor('_delete');
$elem = $(event.target);
return DiscussionUtil.safeAjax({
$elem: $elem,
url: url,
type: "POST",
success: function() {
self.model.remove();
return self.$el.remove();
},
error: function() {
return DiscussionUtil.discussionAlert(
gettext("Sorry"),
gettext("We had some trouble deleting this comment. Please try again.")
);
}
});
};
ResponseCommentView.prototype.cancelEdit = function(event) {
this.trigger("comment:cancel_edit", event);
return this.renderShowView();
};
ResponseCommentView.prototype.edit = function(event) {
this.trigger("comment:edit", event);
return this.renderEditView();
};
ResponseCommentView.prototype.update = function(event) {
var newBody, url,
self = this;
newBody = this.editView.$(".edit-comment-body textarea").val();
url = DiscussionUtil.urlFor("update_comment", this.model.id);
return DiscussionUtil.safeAjax({
$elem: $(event.target),
$loading: $(event.target),
url: url,
type: "POST",
dataType: "json",
data: {
body: newBody
},
error: DiscussionUtil.formErrorHandler(this.$(".edit-comment-form-errors")),
success: function() {
self.model.set("body", newBody);
return self.cancelEdit();
}
});
};
return ResponseCommentView;
})(DiscussionContentView);
}
}).call(window);
if Backbone? /* globals DiscussionUtil */
class @ThreadResponseEditView extends Backbone.View (function() {
'use strict';
var __hasProp = {}.hasOwnProperty,
__extends = function(child, parent) {
for (var key in parent) {
if (__hasProp.call(parent, key)) {
child[key] = parent[key];
}
}
function ctor() {
this.constructor = child;
}
events: ctor.prototype = parent.prototype;
"click .post-update": "update" child.prototype = new ctor();
"click .post-cancel": "cancel_edit" child.__super__ = parent.prototype;
return child;
};
$: (selector) -> if (typeof Backbone !== "undefined" && Backbone !== null) {
@$el.find(selector) this.ThreadResponseEditView = (function(_super) {
initialize: -> __extends(ThreadResponseEditView, _super);
super()
render: -> function ThreadResponseEditView() {
@template = _.template($("#thread-response-edit-template").html()) return ThreadResponseEditView.__super__.constructor.apply(this, arguments);
@$el.html(@template(@model.toJSON())) }
@delegateEvents()
DiscussionUtil.makeWmdEditor @$el, $.proxy(@$, @), "edit-post-body"
@
update: (event) -> ThreadResponseEditView.prototype.events = {
@trigger "response:update", event "click .post-update": "update",
"click .post-cancel": "cancel_edit"
};
cancel_edit: (event) -> ThreadResponseEditView.prototype.$ = function(selector) {
@trigger "response:cancel_edit", event return this.$el.find(selector);
};
ThreadResponseEditView.prototype.initialize = function() {
return ThreadResponseEditView.__super__.initialize.call(this);
};
ThreadResponseEditView.prototype.render = function() {
this.template = _.template($("#thread-response-edit-template").html());
this.$el.html(this.template(this.model.toJSON()));
this.delegateEvents();
DiscussionUtil.makeWmdEditor(this.$el, $.proxy(this.$, this), "edit-post-body");
return this;
};
ThreadResponseEditView.prototype.update = function(event) {
return this.trigger("response:update", event);
};
ThreadResponseEditView.prototype.cancel_edit = function(event) {
return this.trigger("response:cancel_edit", event);
};
return ThreadResponseEditView;
})(Backbone.View);
}
}).call(window);
if Backbone? /* globals DiscussionContentShowView, DiscussionUtil, MathJax */
class @ThreadResponseShowView extends DiscussionContentShowView (function() {
initialize: -> 'use strict';
super() var __hasProp = {}.hasOwnProperty,
@listenTo(@model, "change", @render) __extends = function(child, parent) {
for (var key in parent) {
renderTemplate: -> if (__hasProp.call(parent, key)) {
@template = _.template($("#thread-response-show-template").html()) child[key] = parent[key];
context = _.extend( }
{ }
cid: @model.cid, function ctor() {
author_display: @getAuthorDisplay(), this.constructor = child;
endorser_display: @getEndorserDisplay(), }
readOnly: $('.discussion-module').data('read-only')
}, ctor.prototype = parent.prototype;
@model.attributes child.prototype = new ctor();
) child.__super__ = parent.prototype;
@template(context) return child;
};
render: ->
@$el.html(@renderTemplate()) if (typeof Backbone !== "undefined" && Backbone !== null) {
@delegateEvents() this.ThreadResponseShowView = (function(_super) {
@renderAttrs()
@$el.find(".posted-details .timeago").timeago() __extends(ThreadResponseShowView, _super);
@convertMath()
@ function ThreadResponseShowView() {
return ThreadResponseShowView.__super__.constructor.apply(this, arguments);
convertMath: -> }
element = @$(".response-body")
element.html DiscussionUtil.postMathJaxProcessor DiscussionUtil.markdownWithHighlight element.text() ThreadResponseShowView.prototype.initialize = function() {
if MathJax? ThreadResponseShowView.__super__.initialize.call(this);
MathJax.Hub.Queue ["Typeset", MathJax.Hub, element[0]] return this.listenTo(this.model, "change", this.render);
};
edit: (event) ->
@trigger "response:edit", event ThreadResponseShowView.prototype.renderTemplate = function() {
var context;
_delete: (event) -> this.template = _.template($("#thread-response-show-template").html());
@trigger "response:_delete", event context = _.extend({
cid: this.model.cid,
author_display: this.getAuthorDisplay(),
endorser_display: this.getEndorserDisplay(),
readOnly: $('.discussion-module').data('read-only')
}, this.model.attributes);
return this.template(context);
};
ThreadResponseShowView.prototype.render = function() {
this.$el.html(this.renderTemplate());
this.delegateEvents();
this.renderAttrs();
this.$el.find(".posted-details .timeago").timeago();
this.convertMath();
return this;
};
ThreadResponseShowView.prototype.convertMath = function() {
var element;
element = this.$(".response-body");
element.html(DiscussionUtil.postMathJaxProcessor(DiscussionUtil.markdownWithHighlight(element.text())));
if (typeof MathJax !== "undefined" && MathJax !== null) {
return MathJax.Hub.Queue(["Typeset", MathJax.Hub, element[0]]);
}
};
ThreadResponseShowView.prototype.edit = function(event) {
return this.trigger("response:edit", event);
};
ThreadResponseShowView.prototype._delete = function(event) {
return this.trigger("response:_delete", event);
};
return ThreadResponseShowView;
})(DiscussionContentShowView);
}
}).call(window);
...@@ -49,6 +49,7 @@ var commonFiles = { ...@@ -49,6 +49,7 @@ var commonFiles = {
{pattern: 'edx-pattern-library/js/**/*.js'}, {pattern: 'edx-pattern-library/js/**/*.js'},
{pattern: 'edx-ui-toolkit/js/**/*.js'}, {pattern: 'edx-ui-toolkit/js/**/*.js'},
{pattern: 'xmodule_js/common_static/coffee/src/**/!(*spec).js'}, {pattern: 'xmodule_js/common_static/coffee/src/**/!(*spec).js'},
{pattern: 'xmodule_js/common_static/common/js/**/!(*spec).js'},
{pattern: 'xmodule_js/common_static/js/**/!(*spec).js'}, {pattern: 'xmodule_js/common_static/js/**/!(*spec).js'},
{pattern: 'xmodule_js/src/**/*.js'} {pattern: 'xmodule_js/src/**/*.js'}
], ],
......
describe 'DiscussionUtil', -> /* globals DiscussionSpecHelper, DiscussionUtil */
beforeEach -> (function() {
DiscussionSpecHelper.setUpGlobals() 'use strict';
describe('DiscussionUtil', function() {
beforeEach(function() {
return DiscussionSpecHelper.setUpGlobals();
});
return describe("updateWithUndo", function() {
it("calls through to safeAjax with correct params, and reverts the model in case of failure", function() {
var deferred, model, res, updates;
deferred = $.Deferred();
spyOn($, "ajax").and.returnValue(deferred);
spyOn(DiscussionUtil, "safeAjax").and.callThrough();
model = new Backbone.Model({
hello: false,
number: 42
});
updates = {
hello: "world"
};
res = DiscussionUtil.updateWithUndo(model, updates, {
foo: "bar"
}, "error message");
expect(DiscussionUtil.safeAjax).toHaveBeenCalled();
expect(model.attributes).toEqual({
hello: "world",
number: 42
});
spyOn(DiscussionUtil, "discussionAlert");
DiscussionUtil.safeAjax.calls.mostRecent().args[0].error();
expect(DiscussionUtil.discussionAlert).toHaveBeenCalledWith("Sorry", "error message");
deferred.reject();
return expect(model.attributes).toEqual({
hello: false,
number: 42
});
});
return it("rolls back the changes if the associated element is disabled", function() {
var $elem, failed, model, res, updates;
spyOn(DiscussionUtil, "safeAjax").and.callThrough();
model = new Backbone.Model({
hello: false,
number: 42
});
updates = {
hello: "world"
};
$elem = jasmine.createSpyObj('$elem', ['attr']);
$elem.attr.and.returnValue(true);
res = DiscussionUtil.updateWithUndo(model, updates, {
foo: "bar",
$elem: $elem
}, "error message");
expect($elem.attr).toHaveBeenCalledWith("disabled");
expect(DiscussionUtil.safeAjax).toHaveBeenCalled();
expect(model.attributes).toEqual({
hello: false,
number: 42
});
failed = false;
res.fail(function() {
failed = true;
});
return expect(failed).toBe(true);
});
});
});
describe "updateWithUndo", -> }).call(this);
it "calls through to safeAjax with correct params, and reverts the model in case of failure", ->
deferred = $.Deferred()
spyOn($, "ajax").and.returnValue(deferred)
spyOn(DiscussionUtil, "safeAjax").and.callThrough()
model = new Backbone.Model({hello: false, number: 42})
updates = {hello: "world"}
# the ajax request should fire and the model should be updated
res = DiscussionUtil.updateWithUndo(model, updates, {foo: "bar"}, "error message")
expect(DiscussionUtil.safeAjax).toHaveBeenCalled()
expect(model.attributes).toEqual({hello: "world", number: 42})
# the error message callback should be set up correctly
spyOn(DiscussionUtil, "discussionAlert")
DiscussionUtil.safeAjax.calls.mostRecent().args[0].error()
expect(DiscussionUtil.discussionAlert).toHaveBeenCalledWith("Sorry", "error message")
# if the ajax call ends in failure, the model state should be reverted
deferred.reject()
expect(model.attributes).toEqual({hello: false, number: 42})
it "rolls back the changes if the associated element is disabled", ->
spyOn(DiscussionUtil, "safeAjax").and.callThrough()
model = new Backbone.Model({hello: false, number: 42})
updates = {hello: "world"}
# This is the element that is disabled/enabled while the ajax request is
# in progress
$elem = jasmine.createSpyObj('$elem', ['attr'])
$elem.attr.and.returnValue(true)
res = DiscussionUtil.updateWithUndo(model, updates, {foo: "bar", $elem:$elem}, "error message")
expect($elem.attr).toHaveBeenCalledWith("disabled")
expect(DiscussionUtil.safeAjax).toHaveBeenCalled()
expect(model.attributes).toEqual({hello: false, number: 42})
failed = false
res.fail(() => failed = true)
expect(failed).toBe(true);
describe "DiscussionContentView", -> /* globals DiscussionSpecHelper, DiscussionContentView, Thread */
beforeEach -> (function() {
DiscussionSpecHelper.setUpGlobals() 'use strict';
DiscussionSpecHelper.setUnderscoreFixtures() describe("DiscussionContentView", function() {
beforeEach(function() {
DiscussionSpecHelper.setUpGlobals();
DiscussionSpecHelper.setUnderscoreFixtures();
this.threadData = {
id: '01234567',
user_id: '567',
course_id: 'edX/999/test',
body: 'this is a thread',
created_at: '2013-04-03T20:08:39Z',
abuse_flaggers: ['123'],
votes: {
up_count: '42'
},
type: "thread",
roles: []
};
this.thread = new Thread(this.threadData);
this.view = new DiscussionContentView({
model: this.thread
});
this.view.setElement($('#fixture-element'));
return this.view.render();
});
@threadData = { it('defines the tag', function() {
id: '01234567', expect($('#jasmine-fixtures')).toExist();
user_id: '567', expect(this.view.tagName).toBeDefined();
course_id: 'edX/999/test', return expect(this.view.el.tagName.toLowerCase()).toBe('div');
body: 'this is a thread', });
created_at: '2013-04-03T20:08:39Z',
abuse_flaggers: ['123'],
votes: {up_count: '42'},
type: "thread",
roles: []
}
@thread = new Thread(@threadData)
@view = new DiscussionContentView({ model: @thread })
@view.setElement($('#fixture-element'))
@view.render()
it 'defines the tag', -> it("defines the class", function() {
expect($('#jasmine-fixtures')).toExist return expect(this.view.model).toBeDefined();
expect(@view.tagName).toBeDefined });
expect(@view.el.tagName.toLowerCase()).toBe 'div'
it "defines the class", -> it('is tied to the model', function() {
# spyOn @content, 'initialize' return expect(this.view.model).toBeDefined();
expect(@view.model).toBeDefined(); });
it 'is tied to the model', -> it('can be flagged for abuse', function() {
expect(@view.model).toBeDefined(); this.thread.flagAbuse();
return expect(this.thread.get('abuse_flaggers')).toEqual(['123', '567']);
});
it 'can be flagged for abuse', -> it('can be unflagged for abuse', function() {
@thread.flagAbuse() var temp_array;
expect(@thread.get 'abuse_flaggers').toEqual ['123', '567'] temp_array = [];
temp_array.push(window.user.get('id'));
it 'can be unflagged for abuse', -> this.thread.set("abuse_flaggers", temp_array);
temp_array = [] this.thread.unflagAbuse();
temp_array.push(window.user.get('id')) return expect(this.thread.get('abuse_flaggers')).toEqual([]);
@thread.set("abuse_flaggers",temp_array) });
@thread.unflagAbuse() });
expect(@thread.get 'abuse_flaggers').toEqual [] }).call(this);
/* globals
DiscussionCourseSettings, DiscussionSpecHelper, DiscussionThreadEditView, DiscussionUtil,
DiscussionViewSpecHelper, Thread
*/
(function() { (function() {
'use strict'; 'use strict';
describe('DiscussionThreadEditView', function() { describe('DiscussionThreadEditView', function() {
...@@ -14,7 +18,7 @@ ...@@ -14,7 +18,7 @@
this.thread = new Thread(this.threadData); this.thread = new Thread(this.threadData);
this.course_settings = DiscussionSpecHelper.makeCourseSettings(); this.course_settings = DiscussionSpecHelper.makeCourseSettings();
this.createEditView = function (options) { this.createEditView = function(options) {
options = _.extend({ options = _.extend({
container: $('#fixture-element'), container: $('#fixture-element'),
model: this.thread, model: this.thread,
...@@ -33,10 +37,13 @@ ...@@ -33,10 +37,13 @@
expect(params.data.commentable_id).toBe(newTopicId); expect(params.data.commentable_id).toBe(newTopicId);
expect(params.data.title).toBe('changed thread title'); expect(params.data.title).toBe('changed thread title');
params.success(); params.success();
return {always: function() {}}; return {
always: function() {
}
};
}); });
view.$el.find('a.topic-title').filter(function (idx, el) { view.$el.find('a.topic-title').filter(function(idx, el) {
return $(el).data('discussionId') === newTopicId; return $(el).data('discussionId') === newTopicId;
}).click(); // set new topic }).click(); // set new topic
view.$('.edit-post-title').val('changed thread title'); // set new title view.$('.edit-post-title').val('changed thread title'); // set new title
...@@ -76,6 +83,7 @@ ...@@ -76,6 +83,7 @@
this.createEditView({"mode": "inline"}); this.createEditView({"mode": "inline"});
testCancel(this.view); testCancel(this.view);
}); });
describe('renderComments', function() { describe('renderComments', function() {
beforeEach(function() { beforeEach(function() {
this.course_settings = new DiscussionCourseSettings({ this.course_settings = new DiscussionCourseSettings({
...@@ -100,21 +108,21 @@ ...@@ -100,21 +108,21 @@
'is_cohorted': true 'is_cohorted': true
}); });
}); });
it('can save new data correctly for current discussion id without dots', function () {
it('can save new data correctly for current discussion id without dots', function() {
this.createEditView({topicId: "topic"}); this.createEditView({topicId: "topic"});
testUpdate(this.view, this.thread, "6.00.1x_General", "General"); testUpdate(this.view, this.thread, "6.00.1x_General", "General");
}); });
it('can save new data correctly for current discussion id with dots', function () { it('can save new data correctly for current discussion id with dots', function() {
this.createEditView({topicId: "6.00.1x_General"}); this.createEditView({topicId: "6.00.1x_General"});
testUpdate(this.view, this.thread, "6>00\'1x\"Basic_Question", "Basic Question"); testUpdate(this.view, this.thread, "6>00\'1x\"Basic_Question", "Basic Question");
}); });
it('can save new data correctly for current discussion id with special characters', function () { it('can save new data correctly for current discussion id with special characters', function() {
this.createEditView({topicId: "6>00\'1x\"Basic_Question"}); this.createEditView({topicId: "6>00\'1x\"Basic_Question"});
testUpdate(this.view, this.thread, "6.00.1x_General", "General"); testUpdate(this.view, this.thread, "6.00.1x_General", "General");
}); });
}); });
}); });
}).call(this); }).call(this);
/* globals DiscussionTopicMenuView, DiscussionSpecHelper, DiscussionCourseSettings */
(function() { (function() {
'use strict'; 'use strict';
describe('DiscussionTopicMenuView', function() { describe('DiscussionTopicMenuView', function() {
beforeEach(function() { beforeEach(function() {
this.createTopicView = function (options) { this.createTopicView = function(options) {
options = _.extend({ options = _.extend({
course_settings: this.course_settings, course_settings: this.course_settings,
topicId: void 0 topicId: void 0
...@@ -12,14 +13,14 @@ ...@@ -12,14 +13,14 @@
this.defaultTextWidth = this.completeText.length; this.defaultTextWidth = this.completeText.length;
}; };
this.openMenu = function () { this.openMenu = function() {
var menuWrapper = this.view.$('.topic-menu-wrapper'); var menuWrapper = this.view.$('.topic-menu-wrapper');
expect(menuWrapper).toBeHidden(); expect(menuWrapper).toBeHidden();
this.view.$el.find('.post-topic-button').first().click(); this.view.$el.find('.post-topic-button').first().click();
expect(menuWrapper).toBeVisible(); expect(menuWrapper).toBeVisible();
}; };
this.closeMenu = function () { this.closeMenu = function() {
var menuWrapper = this.view.$('.topic-menu-wrapper'); var menuWrapper = this.view.$('.topic-menu-wrapper');
expect(menuWrapper).toBeVisible(); expect(menuWrapper).toBeVisible();
this.view.$el.find('.post-topic-button').first().click(); this.view.$el.find('.post-topic-button').first().click();
...@@ -76,7 +77,7 @@ ...@@ -76,7 +77,7 @@
expect(dropdownText.indexOf('/ span>')).toEqual(-1); expect(dropdownText.indexOf('/ span>')).toEqual(-1);
}); });
it('appropriate topic is selected if `topicId` is passed', function () { it('appropriate topic is selected if `topicId` is passed', function() {
var completeText = this.parentCategoryText + ' / Numerical Input', var completeText = this.parentCategoryText + ' / Numerical Input',
dropdownText; dropdownText;
this.createTopicView({ this.createTopicView({
...@@ -88,14 +89,14 @@ ...@@ -88,14 +89,14 @@
expect(completeText).toEqual(dropdownText); expect(completeText).toEqual(dropdownText);
}); });
it('click outside of the dropdown close it', function () { it('click outside of the dropdown close it', function() {
this.createTopicView(); this.createTopicView();
this.openMenu(); this.openMenu();
$(document.body).click(); $(document.body).click();
expect(this.view.$('.topic-menu-wrapper')).toBeHidden(); expect(this.view.$('.topic-menu-wrapper')).toBeHidden();
}); });
it('can toggle the menu', function () { it('can toggle the menu', function() {
this.createTopicView(); this.createTopicView();
this.openMenu(); this.openMenu();
this.closeMenu(); this.closeMenu();
......
class @DiscussionViewSpecHelper /* globals DiscussionUtil */
@makeThreadWithProps = (props) -> (function() {
# Minimal set of properties necessary for rendering 'use strict';
thread = { var __indexOf = [].indexOf || function(item) {
id: "dummy_id", for (var i = 0, l = this.length; i < l; i++) {
thread_type: "discussion", if (i in this && this[i] === item) {
pinned: false, return i;
endorsed: false, }
votes: {up_count: '0'},
read: false,
unread_comments_count: 0,
comments_count: 0,
abuse_flaggers: [],
body: "",
title: "dummy title",
created_at: "2014-08-18T01:02:03Z"
ability: {
can_delete: false,
can_reply: true,
can_vote: false,
editable: false,
} }
return -1;
};
this.DiscussionViewSpecHelper = (function() {
var triggerVoteEvent;
function DiscussionViewSpecHelper() {
} }
$.extend(thread, props)
@checkVoteClasses = (view) -> DiscussionViewSpecHelper.makeThreadWithProps = function(props) {
view.render() var thread;
display_button = view.$el.find(".display-vote") thread = {
expect(display_button.hasClass("is-hidden")).toBe(true) id: "dummy_id",
action_button = view.$el.find(".action-vote") thread_type: "discussion",
# Check that inline css is not applied to the ".action-vote" pinned: false,
expect(action_button).not.toHaveAttr('style','display: inline; '); endorsed: false,
votes: {
up_count: '0'
},
read: false,
unread_comments_count: 0,
comments_count: 0,
abuse_flaggers: [],
body: "",
title: "dummy title",
created_at: "2014-08-18T01:02:03Z",
ability: {
can_delete: false,
can_reply: true,
can_vote: false,
editable: false
}
};
return $.extend(thread, props);
};
DiscussionViewSpecHelper.checkVoteClasses = function(view) {
var action_button, display_button;
view.render();
display_button = view.$el.find(".display-vote");
expect(display_button.hasClass("is-hidden")).toBe(true);
action_button = view.$el.find(".action-vote");
return expect(action_button).not.toHaveAttr('style', 'display: inline; ');
};
DiscussionViewSpecHelper.expectVoteRendered = function(view, model, user) {
var button;
button = view.$el.find(".action-vote");
expect(button.hasClass("is-checked")).toBe(user.voted(model));
expect(button.attr("aria-checked")).toEqual(user.voted(model).toString());
expect(button.find(".vote-count").text()).toMatch("^" + (model.get('votes').up_count) + " Votes?$");
return expect(button.find(".sr.js-sr-vote-count").text())
.toMatch("^there are currently " + (model.get('votes').up_count) + " votes?$");
};
DiscussionViewSpecHelper.checkRenderVote = function(view, model) {
view.render();
DiscussionViewSpecHelper.expectVoteRendered(view, model, window.user);
window.user.vote(model);
view.render();
DiscussionViewSpecHelper.expectVoteRendered(view, model, window.user);
window.user.unvote(model);
view.render();
return DiscussionViewSpecHelper.expectVoteRendered(view, model, window.user);
};
@expectVoteRendered = (view, model, user) -> triggerVoteEvent = function(view, event, expectedUrl) {
button = view.$el.find(".action-vote") var deferred;
expect(button.hasClass("is-checked")).toBe(user.voted(model)) deferred = $.Deferred();
expect(button.attr("aria-checked")).toEqual(user.voted(model).toString()) spyOn($, "ajax").and.callFake(function(params) {
expect(button.find(".vote-count").text()).toMatch("^#{model.get('votes').up_count} Votes?$") expect(params.url.toString()).toEqual(expectedUrl);
expect(button.find(".sr.js-sr-vote-count").text()).toMatch("^there are currently #{model.get('votes').up_count} votes?$") return deferred;
});
view.render();
view.$el.find(".action-vote").trigger(event);
expect($.ajax).toHaveBeenCalled();
return deferred.resolve();
};
@checkRenderVote = (view, model) -> DiscussionViewSpecHelper.checkUpvote = function(view, model, user, event) {
view.render() var initialVoteCount, _ref, _ref1;
DiscussionViewSpecHelper.expectVoteRendered(view, model, window.user) expect((_ref = model.id, __indexOf.call(user.get('upvoted_ids'), _ref) >= 0)).toBe(false);
window.user.vote(model) initialVoteCount = model.get('votes').up_count;
view.render() triggerVoteEvent(view, event, DiscussionUtil.urlFor("upvote_" + (model.get('type')), model.id) + "?ajax=1");
DiscussionViewSpecHelper.expectVoteRendered(view, model, window.user) expect((_ref1 = model.id, __indexOf.call(user.get('upvoted_ids'), _ref1) >= 0)).toBe(true);
window.user.unvote(model) return expect(model.get('votes').up_count).toEqual(initialVoteCount + 1);
view.render() };
DiscussionViewSpecHelper.expectVoteRendered(view, model, window.user)
triggerVoteEvent = (view, event, expectedUrl) -> DiscussionViewSpecHelper.checkUnvote = function(view, model, user, event) {
deferred = $.Deferred() var initialVoteCount, _ref;
spyOn($, "ajax").and.callFake((params) => user.vote(model);
expect(params.url.toString()).toEqual(expectedUrl) expect((_ref = model.id, __indexOf.call(user.get('upvoted_ids'), _ref) >= 0)).toBe(true);
return deferred initialVoteCount = model.get('votes').up_count;
) triggerVoteEvent(
view.render() view, event, DiscussionUtil.urlFor("undo_vote_for_" + (model.get('type')), model.id) + "?ajax=1"
view.$el.find(".action-vote").trigger(event) );
expect($.ajax).toHaveBeenCalled() expect(user.get('upvoted_ids')).toEqual([]);
deferred.resolve() return expect(model.get('votes').up_count).toEqual(initialVoteCount - 1);
};
@checkUpvote = (view, model, user, event) -> DiscussionViewSpecHelper.checkButtonEvents = function(view, viewFunc, buttonSelector) {
expect(model.id in user.get('upvoted_ids')).toBe(false) var button, spy;
initialVoteCount = model.get('votes').up_count spy = spyOn(view, viewFunc);
triggerVoteEvent(view, event, DiscussionUtil.urlFor("upvote_#{model.get('type')}", model.id) + "?ajax=1") button = view.$el.find(buttonSelector);
expect(model.id in user.get('upvoted_ids')).toBe(true) button.click();
expect(model.get('votes').up_count).toEqual(initialVoteCount + 1) expect(spy).toHaveBeenCalled();
spy.calls.reset();
button.trigger($.Event("keydown", {
which: 13
}));
expect(spy).not.toHaveBeenCalled();
spy.calls.reset();
button.trigger($.Event("keydown", {
which: 32
}));
return expect(spy).toHaveBeenCalled();
};
@checkUnvote = (view, model, user, event) -> DiscussionViewSpecHelper.checkVoteButtonEvents = function(view) {
user.vote(model) return this.checkButtonEvents(view, "toggleVote", ".action-vote");
expect(model.id in user.get('upvoted_ids')).toBe(true) };
initialVoteCount = model.get('votes').up_count
triggerVoteEvent(view, event, DiscussionUtil.urlFor("undo_vote_for_#{model.get('type')}", model.id) + "?ajax=1")
expect(user.get('upvoted_ids')).toEqual([])
expect(model.get('votes').up_count).toEqual(initialVoteCount - 1)
@checkButtonEvents = (view, viewFunc, buttonSelector) -> DiscussionViewSpecHelper.setNextResponseContent = function(content) {
spy = spyOn(view, viewFunc) return $.ajax.and.callFake(function(params) {
button = view.$el.find(buttonSelector) params.success({
"content": content
});
return {
always: function() {
}
};
});
};
button.click() return DiscussionViewSpecHelper;
expect(spy).toHaveBeenCalled()
spy.calls.reset()
button.trigger($.Event("keydown", {which: 13}))
expect(spy).not.toHaveBeenCalled()
spy.calls.reset()
button.trigger($.Event("keydown", {which: 32}))
expect(spy).toHaveBeenCalled()
@checkVoteButtonEvents = (view) -> })();
@checkButtonEvents(view, "toggleVote", ".action-vote")
@setNextResponseContent = (content) -> }).call(this);
$.ajax.and.callFake(
(params) =>
params.success({"content": content})
{always: ->}
)
describe 'ResponseCommentShowView', -> /* globals DiscussionSpecHelper, DiscussionUtil, DiscussionViewSpecHelper, ResponseCommentShowView */
beforeEach -> (function() {
DiscussionSpecHelper.setUpGlobals() 'use strict';
# set up the container for the response to go in describe('ResponseCommentShowView', function() {
DiscussionSpecHelper.setUnderscoreFixtures() beforeEach(function() {
DiscussionSpecHelper.setUpGlobals();
# set up a model for a new Comment DiscussionSpecHelper.setUnderscoreFixtures();
@comment = new Comment { this.comment = new Comment({
id: '01234567', id: '01234567',
user_id: '567', user_id: '567',
course_id: 'edX/999/test', course_id: 'edX/999/test',
body: 'this is a response', body: 'this is a response',
created_at: '2013-04-03T20:08:39Z', created_at: '2013-04-03T20:08:39Z',
abuse_flaggers: ['123'] abuse_flaggers: ['123'],
roles: [] roles: []
} });
@view = new ResponseCommentShowView({ model: @comment }) this.view = new ResponseCommentShowView({
spyOn(@view, "convertMath") model: this.comment
});
it 'defines the tag', -> return spyOn(this.view, "convertMath");
expect($('#jasmine-fixtures')).toExist });
expect(@view.tagName).toBeDefined it('defines the tag', function() {
expect(@view.el.tagName.toLowerCase()).toBe 'li' expect($('#jasmine-fixtures')).toExist();
expect(this.view.tagName).toBeDefined();
it 'is tied to the model', -> return expect(this.view.el.tagName.toLowerCase()).toBe('li');
expect(@view.model).toBeDefined() });
it('is tied to the model', function() {
describe 'rendering', -> return expect(this.view.model).toBeDefined();
});
beforeEach -> describe('rendering', function() {
spyOn(@view, 'renderAttrs') beforeEach(function() {
return spyOn(this.view, 'renderAttrs');
it 'can be flagged for abuse', -> });
@comment.flagAbuse() it('can be flagged for abuse', function() {
expect(@comment.get 'abuse_flaggers').toEqual ['123', '567'] this.comment.flagAbuse();
return expect(this.comment.get('abuse_flaggers')).toEqual(['123', '567']);
it 'can be unflagged for abuse', -> });
temp_array = [] it('can be unflagged for abuse', function() {
temp_array.push(window.user.get('id')) var temp_array;
@comment.set("abuse_flaggers",temp_array) temp_array = [];
@comment.unflagAbuse() temp_array.push(window.user.get('id'));
expect(@comment.get 'abuse_flaggers').toEqual [] this.comment.set("abuse_flaggers", temp_array);
this.comment.unflagAbuse();
describe '_delete', -> return expect(this.comment.get('abuse_flaggers')).toEqual([]);
});
it 'triggers on the correct events', -> });
DiscussionUtil.loadRoles [] describe('_delete', function() {
@comment.updateInfo {ability: {'can_delete': true}} it('triggers on the correct events', function() {
@view.render() DiscussionUtil.loadRoles([]);
DiscussionViewSpecHelper.checkButtonEvents(@view, "_delete", ".action-delete") this.comment.updateInfo({
ability: {
it 'triggers the delete event', -> 'can_delete': true
triggerTarget = jasmine.createSpy() }
@view.bind "comment:_delete", triggerTarget });
@view._delete() this.view.render();
expect(triggerTarget).toHaveBeenCalled() return DiscussionViewSpecHelper.checkButtonEvents(this.view, "_delete", ".action-delete");
});
describe 'edit', -> it('triggers the delete event', function() {
var triggerTarget;
it 'triggers on the correct events', -> triggerTarget = jasmine.createSpy();
DiscussionUtil.loadRoles [] this.view.bind("comment:_delete", triggerTarget);
@comment.updateInfo {ability: {'can_edit': true}} this.view._delete();
@view.render() return expect(triggerTarget).toHaveBeenCalled();
DiscussionViewSpecHelper.checkButtonEvents(@view, "edit", ".action-edit") });
});
it 'triggers comment:edit when the edit button is clicked', -> describe('edit', function() {
triggerTarget = jasmine.createSpy() it('triggers on the correct events', function() {
@view.bind "comment:edit", triggerTarget DiscussionUtil.loadRoles([]);
@view.edit() this.comment.updateInfo({
expect(triggerTarget).toHaveBeenCalled() ability: {
'can_edit': true
describe "labels", -> }
});
expectOneElement = (view, selector, visible=true) => this.view.render();
view.render() return DiscussionViewSpecHelper.checkButtonEvents(this.view, "edit", ".action-edit");
elements = view.$el.find(selector) });
expect(elements.length).toEqual(1) it('triggers comment:edit when the edit button is clicked', function() {
if visible var triggerTarget;
expect(elements).not.toHaveClass("is-hidden") triggerTarget = jasmine.createSpy();
else this.view.bind("comment:edit", triggerTarget);
expect(elements).toHaveClass("is-hidden") this.view.edit();
return expect(triggerTarget).toHaveBeenCalled();
it 'displays the reported label when appropriate for a non-staff user', -> });
@comment.set('abuse_flaggers', []) });
expectOneElement(@view, '.post-label-reported', false) describe("labels", function() {
# flagged by current user - should be labelled var expectOneElement;
@comment.set('abuse_flaggers', [DiscussionUtil.getUser().id]) expectOneElement = function(view, selector, visible) {
expectOneElement(@view, '.post-label-reported') var elements;
# flagged by some other user but not the current one - should not be labelled if (typeof visible === "undefined" || visible === null) {
@comment.set('abuse_flaggers', [DiscussionUtil.getUser().id + 1]) visible = true;
expectOneElement(@view, '.post-label-reported', false) }
view.render();
it 'displays the reported label when appropriate for a flag moderator', -> elements = view.$el.find(selector);
DiscussionSpecHelper.makeModerator() expect(elements.length).toEqual(1);
@comment.set('abuse_flaggers', []) if (visible) {
expectOneElement(@view, '.post-label-reported', false) return expect(elements).not.toHaveClass("is-hidden");
# flagged by current user - should be labelled } else {
@comment.set('abuse_flaggers', [DiscussionUtil.getUser().id]) return expect(elements).toHaveClass("is-hidden");
expectOneElement(@view, '.post-label-reported') }
# flagged by some other user but not the current one - should still be labelled };
@comment.set('abuse_flaggers', [DiscussionUtil.getUser().id + 1]) it('displays the reported label when appropriate for a non-staff user', function() {
expectOneElement(@view, '.post-label-reported') this.comment.set('abuse_flaggers', []);
expectOneElement(this.view, '.post-label-reported', false);
this.comment.set('abuse_flaggers', [DiscussionUtil.getUser().id]);
expectOneElement(this.view, '.post-label-reported');
this.comment.set('abuse_flaggers', [DiscussionUtil.getUser().id + 1]);
return expectOneElement(this.view, '.post-label-reported', false);
});
it('displays the reported label when appropriate for a flag moderator', function() {
DiscussionSpecHelper.makeModerator();
this.comment.set('abuse_flaggers', []);
expectOneElement(this.view, '.post-label-reported', false);
this.comment.set('abuse_flaggers', [DiscussionUtil.getUser().id]);
expectOneElement(this.view, '.post-label-reported');
this.comment.set('abuse_flaggers', [DiscussionUtil.getUser().id + 1]);
return expectOneElement(this.view, '.post-label-reported');
});
});
});
}).call(this);
describe 'ThreadResponseView', -> /* globals DiscussionSpecHelper, ResponseCommentView, Thread, ThreadResponseView, ThreadResponseShowView */
beforeEach -> (function() {
DiscussionSpecHelper.setUpGlobals() 'use strict';
DiscussionSpecHelper.setUnderscoreFixtures() describe('ThreadResponseView', function() {
beforeEach(function() {
DiscussionSpecHelper.setUpGlobals();
DiscussionSpecHelper.setUnderscoreFixtures();
this.thread = new Thread({
"thread_type": "discussion"
});
this.response = new Comment({
children: [{}, {}],
thread: this.thread
});
this.view = new ThreadResponseView({
model: this.response,
el: $("#fixture-element")
});
spyOn(ThreadResponseShowView.prototype, "render");
return spyOn(ResponseCommentView.prototype, "render");
});
describe('closed and open Threads', function() {
var checkCommentForm;
checkCommentForm = function(closed) {
var comment, commentData, thread, view;
thread = new Thread({
"thread_type": "discussion",
"closed": closed
});
commentData = {
id: "dummy",
user_id: "567",
course_id: "TestOrg/TestCourse/TestRun",
body: "this is a comment",
created_at: "2013-04-03T20:08:39Z",
abuse_flaggers: [],
type: "comment",
children: [],
thread: thread
};
comment = new Comment(commentData);
view = new ThreadResponseView({
model: comment,
el: $("#fixture-element")
});
view.render();
return expect(view.$('.comment-form').closest('li').is(":visible")).toBe(!closed);
};
it('hides comment form when thread is closed', function() {
return checkCommentForm(true);
});
it('show comment form when thread is open', function() {
return checkCommentForm(false);
});
});
describe('renderComments', function() {
it('hides "show comments" link if collapseComments is not set', function() {
this.view.render();
expect(this.view.$(".comments")).toBeVisible();
return expect(this.view.$(".action-show-comments")).not.toBeVisible();
});
it('hides "show comments" link if collapseComments is set but response has no comments', function() {
this.response = new Comment({
children: [],
thread: this.thread
});
this.view = new ThreadResponseView({
model: this.response,
el: $("#fixture-element"),
collapseComments: true
});
this.view.render();
expect(this.view.$(".comments")).toBeVisible();
return expect(this.view.$(".action-show-comments")).not.toBeVisible();
});
it(
'hides comments if collapseComments is set and shows them when "show comments" link is clicked',
function() {
this.view = new ThreadResponseView({
model: this.response,
el: $("#fixture-element"),
collapseComments: true
});
this.view.render();
expect(this.view.$(".comments")).not.toBeVisible();
expect(this.view.$(".action-show-comments")).toBeVisible();
this.view.$(".action-show-comments").click();
expect(this.view.$(".comments")).toBeVisible();
return expect(this.view.$(".action-show-comments")).not.toBeVisible();
}
);
it('populates commentViews and binds events', function() {
this.view.createEditView();
spyOn(this.view, 'cancelEdit');
spyOn(this.view, 'cancelCommentEdits');
spyOn(this.view, 'hideCommentForm');
spyOn(this.view, 'showCommentForm');
this.view.renderComments();
expect(this.view.commentViews.length).toEqual(2);
this.view.commentViews[0].trigger("comment:edit", jasmine.createSpyObj("event", ["preventDefault"]));
expect(this.view.cancelEdit).toHaveBeenCalled();
expect(this.view.cancelCommentEdits).toHaveBeenCalled();
expect(this.view.hideCommentForm).toHaveBeenCalled();
this.view.commentViews[0].trigger("comment:cancel_edit");
return expect(this.view.showCommentForm).toHaveBeenCalled();
});
});
describe('cancelCommentEdits', function() {
it('calls cancelEdit on each comment view', function() {
this.view.renderComments();
expect(this.view.commentViews.length).toEqual(2);
_.each(this.view.commentViews, function(commentView) {
return spyOn(commentView, 'cancelEdit');
});
this.view.cancelCommentEdits();
return _.each(this.view.commentViews, function(commentView) {
return expect(commentView.cancelEdit).toHaveBeenCalled();
});
});
});
});
@thread = new Thread({"thread_type": "discussion"}) }).call(this);
@response = new Comment {
children: [{}, {}],
thread: @thread,
}
@view = new ThreadResponseView({model: @response, el: $("#fixture-element")})
spyOn(ThreadResponseShowView.prototype, "render")
spyOn(ResponseCommentView.prototype, "render")
describe 'closed and open Threads', ->
checkCommentForm = (closed) ->
thread = new Thread({"thread_type": "discussion", "closed": closed})
commentData = {
id: "dummy",
user_id: "567",
course_id: "TestOrg/TestCourse/TestRun",
body: "this is a comment",
created_at: "2013-04-03T20:08:39Z",
abuse_flaggers: [],
type: "comment",
children: [],
thread: thread,
}
comment = new Comment(commentData)
view = new ThreadResponseView({
model: comment, el: $("#fixture-element"),
})
view.render()
expect(view.$('.comment-form').closest('li').is(":visible")).toBe(not closed)
it 'hides comment form when thread is closed', ->
checkCommentForm(true)
it 'show comment form when thread is open', ->
checkCommentForm(false)
describe 'renderComments', ->
it 'hides "show comments" link if collapseComments is not set', ->
@view.render()
expect(@view.$(".comments")).toBeVisible()
expect(@view.$(".action-show-comments")).not.toBeVisible()
it 'hides "show comments" link if collapseComments is set but response has no comments', ->
@response = new Comment { children: [], thread: @thread }
@view = new ThreadResponseView({
model: @response, el: $("#fixture-element"),
collapseComments: true
})
@view.render()
expect(@view.$(".comments")).toBeVisible()
expect(@view.$(".action-show-comments")).not.toBeVisible()
it 'hides comments if collapseComments is set and shows them when "show comments" link is clicked', ->
@view = new ThreadResponseView({
model: @response, el: $("#fixture-element"),
collapseComments: true
})
@view.render()
expect(@view.$(".comments")).not.toBeVisible()
expect(@view.$(".action-show-comments")).toBeVisible()
@view.$(".action-show-comments").click()
expect(@view.$(".comments")).toBeVisible()
expect(@view.$(".action-show-comments")).not.toBeVisible()
it 'populates commentViews and binds events', ->
# Ensure that edit view is set to test invocation of cancelEdit
@view.createEditView()
spyOn(@view, 'cancelEdit')
spyOn(@view, 'cancelCommentEdits')
spyOn(@view, 'hideCommentForm')
spyOn(@view, 'showCommentForm')
@view.renderComments()
expect(@view.commentViews.length).toEqual(2)
@view.commentViews[0].trigger "comment:edit", jasmine.createSpyObj("event", ["preventDefault"])
expect(@view.cancelEdit).toHaveBeenCalled()
expect(@view.cancelCommentEdits).toHaveBeenCalled()
expect(@view.hideCommentForm).toHaveBeenCalled()
@view.commentViews[0].trigger "comment:cancel_edit"
expect(@view.showCommentForm).toHaveBeenCalled()
describe 'cancelCommentEdits', ->
it 'calls cancelEdit on each comment view', ->
@view.renderComments()
expect(@view.commentViews.length).toEqual(2)
_.each(@view.commentViews, (commentView) -> spyOn(commentView, 'cancelEdit'))
@view.cancelCommentEdits()
_.each(@view.commentViews, (commentView) -> expect(commentView.cancelEdit).toHaveBeenCalled())
class @DiscussionSpecHelper /* global DiscussionCourseSettings, DiscussionUtil, DiscussionUser */
# This is sad. We should avoid dependence on global vars. (function () {
@setUpGlobals = -> 'use strict';
DiscussionUtil.loadRoles({"Moderator": [], "Administrator": [], "Community TA": []}) this.DiscussionSpecHelper = (function () {
window.$$course_id = "edX/999/test"
window.user = new DiscussionUser({username: "test_user", id: "567", upvoted_ids: []})
DiscussionUtil.setUser(window.user)
@makeTA = () -> function DiscussionSpecHelper() {
DiscussionUtil.roleIds["Community TA"].push(parseInt(DiscussionUtil.getUser().id)) }
@makeModerator = () -> DiscussionSpecHelper.setUpGlobals = function () {
DiscussionUtil.roleIds["Moderator"].push(parseInt(DiscussionUtil.getUser().id)) DiscussionUtil.loadRoles({
"Moderator": [],
"Administrator": [],
"Community TA": []
});
window.$$course_id = "edX/999/test";
window.user = new DiscussionUser({
username: "test_user",
id: "567",
upvoted_ids: []
});
return DiscussionUtil.setUser(window.user);
};
@makeAjaxSpy = (fakeAjax) -> DiscussionSpecHelper.makeTA = function () {
spyOn($, "ajax").and.callFake( return DiscussionUtil.roleIds["Community TA"].push(parseInt(DiscussionUtil.getUser().id));
(params) -> };
fakeAjax(params)
{always: ->}
)
@makeEventSpy = () -> DiscussionSpecHelper.makeModerator = function () {
jasmine.createSpyObj('event', ['preventDefault', 'target']) return DiscussionUtil.roleIds.Moderator.push(parseInt(DiscussionUtil.getUser().id));
};
@makeCourseSettings = (is_cohorted=true) -> DiscussionSpecHelper.makeAjaxSpy = function (fakeAjax) {
new DiscussionCourseSettings( return spyOn($, "ajax").and.callFake(function (params) {
category_map: fakeAjax(params);
children: ['Test Topic', 'Other Topic'] return {
entries: always: function () {
'Test Topic': }
is_cohorted: is_cohorted };
id: 'test_topic' });
'Other Topic': };
is_cohorted: is_cohorted
id: 'other_topic'
is_cohorted: is_cohorted
)
@setUnderscoreFixtures = -> DiscussionSpecHelper.makeEventSpy = function () {
templateNames = [ return jasmine.createSpyObj('event', ['preventDefault', 'target']);
'thread', 'thread-show', 'thread-edit', };
'thread-response', 'thread-response-show', 'thread-response-edit',
'response-comment-show', 'response-comment-edit',
'thread-list-item', 'discussion-home', 'search-alert',
'new-post', 'thread-type', 'new-post-menu-entry',
'new-post-menu-category', 'topic', 'post-user-display',
'inline-discussion', 'pagination', 'user-profile', 'profile-thread'
]
templateNamesNoTrailingTemplate = [
'forum-action-endorse', 'forum-action-answer', 'forum-action-follow',
'forum-action-vote', 'forum-action-report', 'forum-action-pin',
'forum-action-close', 'forum-action-edit', 'forum-action-delete',
'forum-actions',
]
for templateName in templateNames DiscussionSpecHelper.makeCourseSettings = function (is_cohorted) {
templateFixture = readFixtures('common/templates/discussion/' + templateName + '.underscore') if (typeof is_cohorted === 'undefined' || is_cohorted === null) {
appendSetFixtures($('<script>', { id: templateName + '-template', type: 'text/template' }) is_cohorted = true;
.text(templateFixture)) }
for templateName in templateNamesNoTrailingTemplate return new DiscussionCourseSettings({
templateFixture = readFixtures('common/templates/discussion/' + templateName + '.underscore') category_map: {
appendSetFixtures($('<script>', { id: templateName, type: 'text/template' }) children: ['Test Topic', 'Other Topic'],
.text(templateFixture)) entries: {
appendSetFixtures(""" 'Test Topic': {
<div id="fixture-element"></div> is_cohorted: is_cohorted,
<div id="discussion-container" id: 'test_topic'
data-course-name="Fake Course" },
data-user-create-comment="true" 'Other Topic': {
data-user-create-subcomment="true" is_cohorted: is_cohorted,
data-read-only="false" id: 'other_topic'
></div> }
""") }
},
is_cohorted: is_cohorted
});
};
DiscussionSpecHelper.setUnderscoreFixtures = function () {
var templateFixture, templateName, templateNames, templateNamesNoTrailingTemplate, _i, _j, _len, _len1;
templateNames = [
'thread', 'thread-show', 'thread-edit', 'thread-response', 'thread-response-show',
'thread-response-edit', 'response-comment-show', 'response-comment-edit', 'thread-list-item',
'discussion-home', 'search-alert', 'new-post', 'thread-type', 'new-post-menu-entry',
'new-post-menu-category', 'topic', 'post-user-display', 'inline-discussion', 'pagination',
'user-profile', 'profile-thread'
];
templateNamesNoTrailingTemplate = [
'forum-action-endorse', 'forum-action-answer', 'forum-action-follow', 'forum-action-vote',
'forum-action-report', 'forum-action-pin', 'forum-action-close', 'forum-action-edit',
'forum-action-delete', 'forum-actions'
];
for (_i = 0, _len = templateNames.length; _i < _len; _i++) {
templateName = templateNames[_i];
templateFixture = readFixtures('common/templates/discussion/' + templateName + '.underscore');
appendSetFixtures($('<script>', {
id: templateName + '-template',
type: 'text/template'
}).text(templateFixture));
}
for (_j = 0, _len1 = templateNamesNoTrailingTemplate.length; _j < _len1; _j++) {
templateName = templateNamesNoTrailingTemplate[_j];
templateFixture = readFixtures('common/templates/discussion/' + templateName + '.underscore');
appendSetFixtures($('<script>', {
id: templateName,
type: 'text/template'
}).text(templateFixture));
}
return appendSetFixtures(
"<div id=\"fixture-element\"></div>\n" +
"<div id=\"discussion-container\"" +
" data-course-name=\"Fake Course\"" +
" data-user-create-comment=\"true\"" +
" data-user-create-subcomment=\"true\"" +
" data-read-only=\"false\"" +
"></div>"
);
};
return DiscussionSpecHelper;
})();
}).call(this);
define([ define([
'underscore', 'edx-ui-toolkit/js/utils/spec-helpers/ajax-helpers', 'teams/js/views/team_discussion', 'underscore', 'edx-ui-toolkit/js/utils/spec-helpers/ajax-helpers', 'teams/js/views/team_discussion',
'teams/js/spec_helpers/team_spec_helpers', 'teams/js/spec_helpers/team_spec_helpers',
'xmodule_js/common_static/coffee/spec/discussion/discussion_spec_helper' 'xmodule_js/common_static/common/js/spec_helpers/discussion_spec_helper'
], function (_, AjaxHelpers, TeamDiscussionView, TeamSpecHelpers, DiscussionSpecHelper) { ], function (_, AjaxHelpers, TeamDiscussionView, TeamSpecHelpers, DiscussionSpecHelper) {
'use strict'; 'use strict';
xdescribe('TeamDiscussionView', function() { xdescribe('TeamDiscussionView', function() {
......
define([ define([
'underscore', 'edx-ui-toolkit/js/utils/spec-helpers/ajax-helpers', 'teams/js/models/team', 'underscore', 'edx-ui-toolkit/js/utils/spec-helpers/ajax-helpers', 'teams/js/models/team',
'teams/js/views/team_profile', 'teams/js/spec_helpers/team_spec_helpers', 'teams/js/views/team_profile', 'teams/js/spec_helpers/team_spec_helpers',
'xmodule_js/common_static/coffee/spec/discussion/discussion_spec_helper' 'xmodule_js/common_static/common/js/spec_helpers/discussion_spec_helper'
], function (_, AjaxHelpers, TeamModel, TeamProfileView, TeamSpecHelpers, DiscussionSpecHelper) { ], function (_, AjaxHelpers, TeamModel, TeamProfileView, TeamSpecHelpers, DiscussionSpecHelper) {
'use strict'; 'use strict';
describe('TeamProfileView', function () { describe('TeamProfileView', function () {
......
...@@ -1296,7 +1296,7 @@ discussion_js = ( ...@@ -1296,7 +1296,7 @@ discussion_js = (
rooted_glob(PROJECT_ROOT / 'static', 'coffee/src/customwmd.js') + rooted_glob(PROJECT_ROOT / 'static', 'coffee/src/customwmd.js') +
rooted_glob(PROJECT_ROOT / 'static', 'coffee/src/mathjax_accessible.js') + rooted_glob(PROJECT_ROOT / 'static', 'coffee/src/mathjax_accessible.js') +
rooted_glob(PROJECT_ROOT / 'static', 'coffee/src/mathjax_delay_renderer.js') + rooted_glob(PROJECT_ROOT / 'static', 'coffee/src/mathjax_delay_renderer.js') +
sorted(rooted_glob(COMMON_ROOT / 'static', 'coffee/src/discussion/**/*.js')) sorted(rooted_glob(COMMON_ROOT / 'static', 'common/js/discussion/**/*.js'))
) )
discussion_vendor_js = [ discussion_vendor_js = [
......
...@@ -2,18 +2,18 @@ ...@@ -2,18 +2,18 @@
"rules": { "rules": {
"javascript-concat-html": 205, "javascript-concat-html": 205,
"javascript-escape": 7, "javascript-escape": 7,
"javascript-interpolate": 51, "javascript-interpolate": 49,
"javascript-jquery-append": 110, "javascript-jquery-append": 104,
"javascript-jquery-html": 274, "javascript-jquery-html": 275,
"javascript-jquery-insert-into-target": 26, "javascript-jquery-insert-into-target": 27,
"javascript-jquery-insertion": 28, "javascript-jquery-insertion": 26,
"javascript-jquery-prepend": 12, "javascript-jquery-prepend": 11,
"mako-html-entities": 0, "mako-html-entities": 0,
"mako-invalid-html-filter": 29, "mako-invalid-html-filter": 27,
"mako-invalid-js-filter": 209, "mako-invalid-js-filter": 207,
"mako-js-html-string": 0, "mako-js-html-string": 0,
"mako-js-missing-quotes": 0, "mako-js-missing-quotes": 0,
"mako-missing-default": 215, "mako-missing-default": 213,
"mako-multiple-page-tags": 0, "mako-multiple-page-tags": 0,
"mako-unknown-context": 0, "mako-unknown-context": 0,
"mako-unparseable-expression": 0, "mako-unparseable-expression": 0,
...@@ -21,12 +21,12 @@ ...@@ -21,12 +21,12 @@
"python-close-before-format": 0, "python-close-before-format": 0,
"python-concat-html": 27, "python-concat-html": 27,
"python-custom-escape": 13, "python-custom-escape": 13,
"python-deprecated-display-name": 54, "python-deprecated-display-name": 53,
"python-interpolate-html": 68, "python-interpolate-html": 66,
"python-parse-error": 0, "python-parse-error": 0,
"python-requires-html-or-text": 0, "python-requires-html-or-text": 0,
"python-wrap-html": 266, "python-wrap-html": 264,
"underscore-not-escaped": 659 "underscore-not-escaped": 658
}, },
"total": 2237 "total": 2232
} }
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