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;
}
ctor.prototype = parent.prototype;
child.prototype = new ctor();
child.__super__ = parent.prototype;
return child;
};
if (typeof Backbone !== "undefined" && Backbone !== null) {
this.DiscussionRouter = (function(_super) {
__extends(DiscussionRouter, _super);
function DiscussionRouter() {
var self = this;
this.hideNewPost = function() {
return DiscussionRouter.prototype.hideNewPost.apply(self, arguments);
};
this.showNewPost = function() {
return DiscussionRouter.prototype.showNewPost.apply(self, arguments);
};
this.navigateToAllThreads = function() {
return DiscussionRouter.prototype.navigateToAllThreads.apply(self, arguments);
};
this.navigateToThread = function() {
return DiscussionRouter.prototype.navigateToThread.apply(self, arguments);
};
this.showMain = function() {
return DiscussionRouter.prototype.showMain.apply(self, arguments);
};
this.setActiveThread = function() {
return DiscussionRouter.prototype.setActiveThread.apply(self, arguments);
};
return DiscussionRouter.__super__.constructor.apply(this, arguments);
}
DiscussionRouter.prototype.routes = {
"": "allThreads",
":forum_name/threads/:thread_id": "showThread"
};
DiscussionRouter.prototype.initialize = function(options) {
var self = this;
this.discussion = options.discussion;
this.course_settings = options.course_settings;
this.nav = new DiscussionThreadListView({
collection: this.discussion,
el: $(".forum-nav"), el: $(".forum-nav"),
courseSettings: @course_settings courseSettings: this.course_settings
) });
@nav.on "thread:selected", @navigateToThread this.nav.on("thread:selected", this.navigateToThread);
@nav.on "thread:removed", @navigateToAllThreads this.nav.on("thread:removed", this.navigateToAllThreads);
@nav.on "threads:rendered", @setActiveThread this.nav.on("threads:rendered", this.setActiveThread);
@nav.on "thread:created", @navigateToThread this.nav.on("thread:created", this.navigateToThread);
@nav.render() this.nav.render();
this.newPost = $('.new-post-article');
@newPost = $('.new-post-article') this.newPostView = new NewPostView({
@newPostView = new NewPostView( el: this.newPost,
el: @newPost, collection: this.discussion,
collection: @discussion, course_settings: this.course_settings,
course_settings: @course_settings,
mode: "tab" mode: "tab"
) });
@newPostView.render() this.newPostView.render();
@listenTo( @newPostView, 'newPost:cancel', @hideNewPost ) this.listenTo(this.newPostView, 'newPost:cancel', this.hideNewPost);
$('.new-post-btn').bind "click", @showNewPost $('.new-post-btn').bind("click", this.showNewPost);
$('.new-post-btn').bind "keydown", (event) => DiscussionUtil.activateOnSpace(event, @showNewPost) return $('.new-post-btn').bind("keydown", function(event) {
return DiscussionUtil.activateOnSpace(event, self.showNewPost);
allThreads: -> });
@nav.updateSidebar() };
@nav.goHome()
DiscussionRouter.prototype.allThreads = function() {
setActiveThread: => this.nav.updateSidebar();
if @thread return this.nav.goHome();
@nav.setActiveThread(@thread.get("id")) };
else
@nav.goHome DiscussionRouter.prototype.setActiveThread = function() {
if (this.thread) {
showThread: (forum_name, thread_id) -> return this.nav.setActiveThread(this.thread.get("id"));
@thread = @discussion.get(thread_id) } else {
@thread.set("unread_comments_count", 0) return this.nav.goHome;
@thread.set("read", true) }
@setActiveThread() };
@showMain()
DiscussionRouter.prototype.showThread = function(forum_name, thread_id) {
showMain: => this.thread = this.discussion.get(thread_id);
if(@main) this.thread.set("unread_comments_count", 0);
@main.cleanup() this.thread.set("read", true);
@main.undelegateEvents() this.setActiveThread();
unless($(".forum-content").is(":visible")) return this.showMain();
$(".forum-content").fadeIn() };
if(@newPost.is(":visible"))
@newPost.fadeOut() DiscussionRouter.prototype.showMain = function() {
var self = this;
@main = new DiscussionThreadView( 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"), el: $(".forum-content"),
model: @thread, model: this.thread,
mode: "tab", mode: "tab",
course_settings: @course_settings, course_settings: this.course_settings
) });
@main.render() this.main.render();
@main.on "thread:responses:rendered", => this.main.on("thread:responses:rendered", function() {
@nav.updateSidebar() return self.nav.updateSidebar();
@thread.on "thread:thread_type_updated", @showMain });
return this.thread.on("thread:thread_type_updated", this.showMain);
navigateToThread: (thread_id) => };
thread = @discussion.get(thread_id)
@navigate("#{thread.get("commentable_id")}/threads/#{thread_id}", trigger: true) DiscussionRouter.prototype.navigateToThread = function(thread_id) {
var thread;
navigateToAllThreads: => thread = this.discussion.get(thread_id);
@navigate("", trigger: true) return this.navigate("" + (thread.get("commentable_id")) + "/threads/" + thread_id, {
trigger: true
showNewPost: (event) => });
$('.forum-content').fadeOut( };
duration: 200
complete: => DiscussionRouter.prototype.navigateToAllThreads = function() {
@newPost.fadeIn(200).focus() return this.navigate("", {
) trigger: true
});
hideNewPost: => };
@newPost.fadeOut(
duration: 200 DiscussionRouter.prototype.showNewPost = function() {
complete: => var self = this;
$('.forum-content').fadeIn(200).find('.thread-wrapper').focus() 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();
child.__super__ = parent.prototype;
return child;
};
if (typeof Backbone !== "undefined" && Backbone !== null) {
this.DiscussionThreadShowView = (function(_super) {
__extends(DiscussionThreadShowView, _super);
function DiscussionThreadShowView() {
return DiscussionThreadShowView.__super__.constructor.apply(this, arguments);
}
DiscussionThreadShowView.prototype.initialize = function(options) {
var _ref;
DiscussionThreadShowView.__super__.initialize.call(this);
this.mode = options.mode || "inline";
if ((_ref = this.mode) !== "tab" && _ref !== "inline") {
throw new Error("invalid mode: " + this.mode);
}
};
DiscussionThreadShowView.prototype.renderTemplate = function() {
var context;
this.template = _.template($("#thread-show-template").html());
context = $.extend({
mode: this.mode,
flagged: this.model.isFlagged(),
author_display: this.getAuthorDisplay(),
cid: this.model.cid,
readOnly: $('.discussion-module').data('read-only') readOnly: $('.discussion-module').data('read-only')
}, }, this.model.attributes);
@model.attributes, return this.template(context);
) };
@template(context)
DiscussionThreadShowView.prototype.render = function() {
render: -> this.$el.html(this.renderTemplate());
@$el.html(@renderTemplate()) this.delegateEvents();
@delegateEvents() this.renderAttrs();
@renderAttrs() this.$("span.timeago").timeago();
@$("span.timeago").timeago() this.convertMath();
@convertMath() this.highlight(this.$(".post-body"));
@highlight @$(".post-body") this.highlight(this.$("h1,h3"));
@highlight @$("h1,h3") return this;
@ };
convertMath: -> DiscussionThreadShowView.prototype.convertMath = function() {
element = @$(".post-body") var element;
element.html DiscussionUtil.postMathJaxProcessor DiscussionUtil.markdownWithHighlight element.text() element = this.$(".post-body");
if MathJax? element.html(DiscussionUtil.postMathJaxProcessor(DiscussionUtil.markdownWithHighlight(element.text())));
MathJax.Hub.Queue ["Typeset", MathJax.Hub, element[0]] if (typeof MathJax !== "undefined" && MathJax !== null) {
return MathJax.Hub.Queue(["Typeset", MathJax.Hub, element[0]]);
edit: (event) -> }
@trigger "thread:edit", event };
_delete: (event) -> DiscussionThreadShowView.prototype.edit = function(event) {
@trigger "thread:_delete", event return this.trigger("thread:edit", event);
};
highlight: (el) ->
if el.html() DiscussionThreadShowView.prototype._delete = function(event) {
el.html(el.html().replace(/&lt;mark&gt;/g, "<mark>").replace(/&lt;\/mark&gt;/g, "</mark>")) 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';
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.DiscussionUserProfileView = (function(_super) {
__extends(DiscussionUserProfileView, _super);
function DiscussionUserProfileView() {
var self = this;
this.render = function() {
return DiscussionUserProfileView.prototype.render.apply(self, arguments);
};
return DiscussionUserProfileView.__super__.constructor.apply(this, arguments);
}
DiscussionUserProfileView.prototype.events = {
"click .discussion-paginator a": "changePage" "click .discussion-paginator a": "changePage"
};
initialize: (options) -> DiscussionUserProfileView.prototype.initialize = function(options) {
super() DiscussionUserProfileView.__super__.initialize.call(this);
@page = options.page this.page = options.page;
@numPages = options.numPages this.numPages = options.numPages;
@discussion = new Discussion() this.discussion = new Discussion();
@discussion.on("reset", @render) this.discussion.on("reset", this.render);
@discussion.reset(@collection, {silent: false}) return this.discussion.reset(this.collection, {
silent: false
render: () => });
@$el.html(_.template($("#user-profile-template").html())({threads: @discussion.models})) };
@discussion.map (thread) ->
new DiscussionThreadProfileView(el: @$("article#thread_#{thread.id}"), model: thread).render() DiscussionUserProfileView.prototype.render = function() {
baseUri = URI(window.location).removeSearch("page") var baseUri, pageUrlFunc, paginationParams,
pageUrlFunc = (page) -> baseUri.clone().addSearch("page", page) self = this;
paginationParams = DiscussionUtil.getPaginationParams(@page, @numPages, pageUrlFunc) this.$el.html(_.template($("#user-profile-template").html())({
@$el.find(".discussion-pagination").html(_.template($("#pagination-template").html())(paginationParams)) threads: this.discussion.models
}));
changePage: (event) -> this.discussion.map(function(thread) {
event.preventDefault() return new DiscussionThreadProfileView({
url = $(event.target).attr("href") el: self.$("article#thread_" + thread.id),
DiscussionUtil.safeAjax model: thread
$elem: @$el }).render();
$loading: $(event.target) });
takeFocus: true baseUri = URI(window.location).removeSearch("page");
url: url pageUrlFunc = function(page) {
type: "GET" return baseUri.clone().addSearch("page", page);
dataType: "json" };
success: (response, textStatus, xhr) => paginationParams = DiscussionUtil.getPaginationParams(this.page, this.numPages, pageUrlFunc);
@page = response.page this.$el.find(".discussion-pagination")
@numPages = response.num_pages .html(_.template($("#pagination-template").html())(paginationParams));
@discussion.reset(response.discussion_data, {silent: false}) };
history.pushState({}, "", url)
$("html, body").animate({ scrollTop: 0 }); DiscussionUserProfileView.prototype.changePage = function(event) {
error: => var url,
DiscussionUtil.discussionAlert( 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("Sorry"),
gettext("We had some trouble loading the page you requested. Please try again.") 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();
child.__super__ = parent.prototype;
return child;
};
if (typeof Backbone !== "undefined" && Backbone !== null) {
this.ResponseCommentEditView = (function(_super) {
__extends(ResponseCommentEditView, _super);
function ResponseCommentEditView() {
return ResponseCommentEditView.__super__.constructor.apply(this, arguments);
}
ResponseCommentEditView.prototype.events = {
"click .post-update": "update",
"click .post-cancel": "cancel_edit" "click .post-cancel": "cancel_edit"
};
ResponseCommentEditView.prototype.$ = function(selector) {
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;
};
$: (selector) -> ResponseCommentEditView.prototype.update = function(event) {
@$el.find(selector) return this.trigger("comment:update", event);
};
initialize: -> ResponseCommentEditView.prototype.cancel_edit = function(event) {
super() return this.trigger("comment:cancel_edit", event);
};
render: -> return ResponseCommentEditView;
@template = _.template($("#response-comment-edit-template").html())
@$el.html(@template(@model.toJSON()))
@delegateEvents()
DiscussionUtil.makeWmdEditor @$el, $.proxy(@$, @), "edit-comment-body"
@
update: (event) -> })(Backbone.View);
@trigger "comment:update", event }
cancel_edit: (event) -> }).call(window);
@trigger "comment:cancel_edit", event
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;
}
ctor.prototype = parent.prototype;
child.prototype = new ctor();
child.__super__ = parent.prototype;
return child;
};
if (typeof Backbone !== "undefined" && Backbone !== null) {
this.ResponseCommentShowView = (function(_super) {
__extends(ResponseCommentShowView, _super);
function ResponseCommentShowView() {
var self = this;
this.edit = function() {
return ResponseCommentShowView.prototype.edit.apply(self, arguments);
};
this._delete = function() {
return ResponseCommentShowView.prototype._delete.apply(self, arguments);
};
return ResponseCommentShowView.__super__.constructor.apply(this, arguments);
}
ResponseCommentShowView.prototype.tagName = "li";
ResponseCommentShowView.prototype.render = function() {
this.template = _.template($("#response-comment-show-template").html());
this.$el.html(this.template(_.extend({
cid: this.model.cid,
author_display: this.getAuthorDisplay(),
readOnly: $('.discussion-module').data('read-only') readOnly: $('.discussion-module').data('read-only')
}, }, this.model.attributes)));
@model.attributes this.delegateEvents();
) this.renderAttrs();
) this.$el.find(".timeago").timeago();
) this.convertMath();
this.addReplyLink();
@delegateEvents() return this;
@renderAttrs() };
@$el.find(".timeago").timeago()
@convertMath() ResponseCommentShowView.prototype.addReplyLink = function() {
@addReplyLink() var html, name, p, _ref;
@ if (this.model.hasOwnProperty('parent')) {
name = (_ref = this.model.parent.get('username')) !== null ? _ref : gettext("anonymous");
addReplyLink: () -> html = "<a href='#comment_" + this.model.parent.id + "'>@" + name + "</a>: ";
if @model.hasOwnProperty('parent') p = this.$('.response-body p:first');
name = @model.parent.get('username') ? gettext("anonymous") return p.prepend(html);
html = "<a href='#comment_#{@model.parent.id}'>@#{name}</a>: " }
p = @$('.response-body p:first') };
p.prepend(html)
ResponseCommentShowView.prototype.convertMath = function() {
convertMath: -> var body;
body = @$el.find(".response-body") body = this.$el.find(".response-body");
body.html DiscussionUtil.postMathJaxProcessor DiscussionUtil.markdownWithHighlight body.text() body.html(DiscussionUtil.postMathJaxProcessor(DiscussionUtil.markdownWithHighlight(body.text())));
if MathJax? if (typeof MathJax !== "undefined" && MathJax !== null) {
MathJax.Hub.Queue ["Typeset", MathJax.Hub, body[0]] return MathJax.Hub.Queue(["Typeset", MathJax.Hub, body[0]]);
}
_delete: (event) => };
@trigger "comment:_delete", event
ResponseCommentShowView.prototype._delete = function(event) {
edit: (event) => return this.trigger("comment:_delete", event);
@trigger "comment:edit", 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) {
view.setElement(this.$el);
view.render();
return view.delegateEvents();
};
ResponseCommentView.prototype.renderShowView = function() {
if (!this.showView) {
if (this.editView) {
this.editView.undelegateEvents();
this.editView.$el.empty();
this.editView = null;
}
this.showView = new ResponseCommentShowView({
model: this.model
});
this.showView.bind("comment:_delete", this._delete);
this.showView.bind("comment:edit", this.edit);
return this.renderSubView(this.showView);
}
};
ResponseCommentView.prototype.renderEditView = function() {
if (!this.editView) {
if (this.showView) {
this.showView.undelegateEvents();
this.showView.$el.empty();
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("Sorry"),
gettext("We had some trouble deleting this comment. Please try again.") gettext("We had some trouble deleting this comment. Please try again.")
) );
}
cancelEdit: (event) => });
@trigger "comment:cancel_edit", event };
@renderShowView()
ResponseCommentView.prototype.cancelEdit = function(event) {
edit: (event) => this.trigger("comment:cancel_edit", event);
@trigger "comment:edit", event return this.renderShowView();
@renderEditView() };
update: (event) => ResponseCommentView.prototype.edit = function(event) {
newBody = @editView.$(".edit-comment-body textarea").val() this.trigger("comment:edit", event);
url = DiscussionUtil.urlFor("update_comment", @model.id) return this.renderEditView();
DiscussionUtil.safeAjax };
$elem: $(event.target)
$loading: $(event.target) ResponseCommentView.prototype.update = function(event) {
url: url var newBody, url,
type: "POST" self = this;
dataType: "json" newBody = this.editView.$(".edit-comment-body textarea").val();
data: 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 body: newBody
error: DiscussionUtil.formErrorHandler(@$(".edit-comment-form-errors")) },
success: (response, textStatus) => error: DiscussionUtil.formErrorHandler(this.$(".edit-comment-form-errors")),
@model.set("body", newBody) success: function() {
@cancelEdit() 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();
child.__super__ = parent.prototype;
return child;
};
if (typeof Backbone !== "undefined" && Backbone !== null) {
this.ThreadResponseEditView = (function(_super) {
__extends(ThreadResponseEditView, _super);
function ThreadResponseEditView() {
return ThreadResponseEditView.__super__.constructor.apply(this, arguments);
}
ThreadResponseEditView.prototype.events = {
"click .post-update": "update",
"click .post-cancel": "cancel_edit" "click .post-cancel": "cancel_edit"
};
ThreadResponseEditView.prototype.$ = function(selector) {
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;
};
$: (selector) -> ThreadResponseEditView.prototype.update = function(event) {
@$el.find(selector) return this.trigger("response:update", event);
};
initialize: -> ThreadResponseEditView.prototype.cancel_edit = function(event) {
super() return this.trigger("response:cancel_edit", event);
};
render: -> return ThreadResponseEditView;
@template = _.template($("#thread-response-edit-template").html())
@$el.html(@template(@model.toJSON()))
@delegateEvents()
DiscussionUtil.makeWmdEditor @$el, $.proxy(@$, @), "edit-post-body"
@
update: (event) -> })(Backbone.View);
@trigger "response:update", event }
cancel_edit: (event) -> }).call(window);
@trigger "response:cancel_edit", event
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(), }
ctor.prototype = parent.prototype;
child.prototype = new ctor();
child.__super__ = parent.prototype;
return child;
};
if (typeof Backbone !== "undefined" && Backbone !== null) {
this.ThreadResponseShowView = (function(_super) {
__extends(ThreadResponseShowView, _super);
function ThreadResponseShowView() {
return ThreadResponseShowView.__super__.constructor.apply(this, arguments);
}
ThreadResponseShowView.prototype.initialize = function() {
ThreadResponseShowView.__super__.initialize.call(this);
return this.listenTo(this.model, "change", this.render);
};
ThreadResponseShowView.prototype.renderTemplate = function() {
var context;
this.template = _.template($("#thread-response-show-template").html());
context = _.extend({
cid: this.model.cid,
author_display: this.getAuthorDisplay(),
endorser_display: this.getEndorserDisplay(),
readOnly: $('.discussion-module').data('read-only') readOnly: $('.discussion-module').data('read-only')
}, }, this.model.attributes);
@model.attributes return this.template(context);
) };
@template(context)
ThreadResponseShowView.prototype.render = function() {
render: -> this.$el.html(this.renderTemplate());
@$el.html(@renderTemplate()) this.delegateEvents();
@delegateEvents() this.renderAttrs();
@renderAttrs() this.$el.find(".posted-details .timeago").timeago();
@$el.find(".posted-details .timeago").timeago() this.convertMath();
@convertMath() return this;
@ };
convertMath: -> ThreadResponseShowView.prototype.convertMath = function() {
element = @$(".response-body") var element;
element.html DiscussionUtil.postMathJaxProcessor DiscussionUtil.markdownWithHighlight element.text() element = this.$(".response-body");
if MathJax? element.html(DiscussionUtil.postMathJaxProcessor(DiscussionUtil.markdownWithHighlight(element.text())));
MathJax.Hub.Queue ["Typeset", MathJax.Hub, element[0]] if (typeof MathJax !== "undefined" && MathJax !== null) {
return MathJax.Hub.Queue(["Typeset", MathJax.Hub, element[0]]);
edit: (event) -> }
@trigger "response:edit", event };
_delete: (event) -> ThreadResponseShowView.prototype.edit = function(event) {
@trigger "response:_delete", 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() {
@threadData = { DiscussionSpecHelper.setUpGlobals();
DiscussionSpecHelper.setUnderscoreFixtures();
this.threadData = {
id: '01234567', id: '01234567',
user_id: '567', user_id: '567',
course_id: 'edX/999/test', course_id: 'edX/999/test',
body: 'this is a thread', body: 'this is a thread',
created_at: '2013-04-03T20:08:39Z', created_at: '2013-04-03T20:08:39Z',
abuse_flaggers: ['123'], abuse_flaggers: ['123'],
votes: {up_count: '42'}, votes: {
up_count: '42'
},
type: "thread", type: "thread",
roles: [] roles: []
} };
@thread = new Thread(@threadData) this.thread = new Thread(this.threadData);
@view = new DiscussionContentView({ model: @thread }) this.view = new DiscussionContentView({
@view.setElement($('#fixture-element')) model: this.thread
@view.render() });
this.view.setElement($('#fixture-element'));
return this.view.render();
});
it 'defines the tag', -> it('defines the tag', function() {
expect($('#jasmine-fixtures')).toExist expect($('#jasmine-fixtures')).toExist();
expect(@view.tagName).toBeDefined expect(this.view.tagName).toBeDefined();
expect(@view.el.tagName.toLowerCase()).toBe 'div' return expect(this.view.el.tagName.toLowerCase()).toBe('div');
});
it "defines the class", -> it("defines the class", function() {
# spyOn @content, 'initialize' return expect(this.view.model).toBeDefined();
expect(@view.model).toBeDefined(); });
it 'is tied to the model', -> it('is tied to the model', function() {
expect(@view.model).toBeDefined(); return expect(this.view.model).toBeDefined();
});
it 'can be flagged for abuse', -> it('can be flagged for abuse', function() {
@thread.flagAbuse() this.thread.flagAbuse();
expect(@thread.get 'abuse_flaggers').toEqual ['123', '567'] return expect(this.thread.get('abuse_flaggers')).toEqual(['123', '567']);
});
it 'can be unflagged for abuse', -> it('can be unflagged for abuse', function() {
temp_array = [] var temp_array;
temp_array.push(window.user.get('id')) temp_array = [];
@thread.set("abuse_flaggers",temp_array) temp_array.push(window.user.get('id'));
@thread.unflagAbuse() this.thread.set("abuse_flaggers", temp_array);
expect(@thread.get 'abuse_flaggers').toEqual [] this.thread.unflagAbuse();
return expect(this.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';
var __indexOf = [].indexOf || function(item) {
for (var i = 0, l = this.length; i < l; i++) {
if (i in this && this[i] === item) {
return i;
}
}
return -1;
};
this.DiscussionViewSpecHelper = (function() {
var triggerVoteEvent;
function DiscussionViewSpecHelper() {
}
DiscussionViewSpecHelper.makeThreadWithProps = function(props) {
var thread;
thread = { thread = {
id: "dummy_id", id: "dummy_id",
thread_type: "discussion", thread_type: "discussion",
pinned: false, pinned: false,
endorsed: false, endorsed: false,
votes: {up_count: '0'}, votes: {
up_count: '0'
},
read: false, read: false,
unread_comments_count: 0, unread_comments_count: 0,
comments_count: 0, comments_count: 0,
abuse_flaggers: [], abuse_flaggers: [],
body: "", body: "",
title: "dummy title", title: "dummy title",
created_at: "2014-08-18T01:02:03Z" created_at: "2014-08-18T01:02:03Z",
ability: { ability: {
can_delete: false, can_delete: false,
can_reply: true, can_reply: true,
can_vote: false, can_vote: false,
editable: false, editable: false
}
} }
$.extend(thread, props) };
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; ');
};
@checkVoteClasses = (view) -> DiscussionViewSpecHelper.expectVoteRendered = function(view, model, user) {
view.render() var button;
display_button = view.$el.find(".display-vote") button = view.$el.find(".action-vote");
expect(display_button.hasClass("is-hidden")).toBe(true) expect(button.hasClass("is-checked")).toBe(user.voted(model));
action_button = view.$el.find(".action-vote") expect(button.attr("aria-checked")).toEqual(user.voted(model).toString());
# Check that inline css is not applied to the ".action-vote" expect(button.find(".vote-count").text()).toMatch("^" + (model.get('votes').up_count) + " Votes?$");
expect(action_button).not.toHaveAttr('style','display: inline; '); return expect(button.find(".sr.js-sr-vote-count").text())
.toMatch("^there are currently " + (model.get('votes').up_count) + " votes?$");
};
@expectVoteRendered = (view, model, user) -> DiscussionViewSpecHelper.checkRenderVote = function(view, model) {
button = view.$el.find(".action-vote") view.render();
expect(button.hasClass("is-checked")).toBe(user.voted(model)) DiscussionViewSpecHelper.expectVoteRendered(view, model, window.user);
expect(button.attr("aria-checked")).toEqual(user.voted(model).toString()) window.user.vote(model);
expect(button.find(".vote-count").text()).toMatch("^#{model.get('votes').up_count} Votes?$") view.render();
expect(button.find(".sr.js-sr-vote-count").text()).toMatch("^there are currently #{model.get('votes').up_count} votes?$") DiscussionViewSpecHelper.expectVoteRendered(view, model, window.user);
window.user.unvote(model);
view.render();
return DiscussionViewSpecHelper.expectVoteRendered(view, model, window.user);
};
@checkRenderVote = (view, model) -> triggerVoteEvent = function(view, event, expectedUrl) {
view.render() var deferred;
DiscussionViewSpecHelper.expectVoteRendered(view, model, window.user) deferred = $.Deferred();
window.user.vote(model) spyOn($, "ajax").and.callFake(function(params) {
view.render() expect(params.url.toString()).toEqual(expectedUrl);
DiscussionViewSpecHelper.expectVoteRendered(view, model, window.user) return deferred;
window.user.unvote(model) });
view.render() view.render();
DiscussionViewSpecHelper.expectVoteRendered(view, model, window.user) view.$el.find(".action-vote").trigger(event);
expect($.ajax).toHaveBeenCalled();
return deferred.resolve();
};
triggerVoteEvent = (view, event, expectedUrl) -> DiscussionViewSpecHelper.checkUpvote = function(view, model, user, event) {
deferred = $.Deferred() var initialVoteCount, _ref, _ref1;
spyOn($, "ajax").and.callFake((params) => expect((_ref = model.id, __indexOf.call(user.get('upvoted_ids'), _ref) >= 0)).toBe(false);
expect(params.url.toString()).toEqual(expectedUrl) initialVoteCount = model.get('votes').up_count;
return deferred triggerVoteEvent(view, event, DiscussionUtil.urlFor("upvote_" + (model.get('type')), model.id) + "?ajax=1");
) expect((_ref1 = model.id, __indexOf.call(user.get('upvoted_ids'), _ref1) >= 0)).toBe(true);
view.render() return expect(model.get('votes').up_count).toEqual(initialVoteCount + 1);
view.$el.find(".action-vote").trigger(event) };
expect($.ajax).toHaveBeenCalled()
deferred.resolve()
@checkUpvote = (view, model, user, event) -> DiscussionViewSpecHelper.checkUnvote = function(view, model, user, event) {
expect(model.id in user.get('upvoted_ids')).toBe(false) var initialVoteCount, _ref;
initialVoteCount = model.get('votes').up_count user.vote(model);
triggerVoteEvent(view, event, DiscussionUtil.urlFor("upvote_#{model.get('type')}", model.id) + "?ajax=1") expect((_ref = model.id, __indexOf.call(user.get('upvoted_ids'), _ref) >= 0)).toBe(true);
expect(model.id in user.get('upvoted_ids')).toBe(true) initialVoteCount = model.get('votes').up_count;
expect(model.get('votes').up_count).toEqual(initialVoteCount + 1) triggerVoteEvent(
view, event, DiscussionUtil.urlFor("undo_vote_for_" + (model.get('type')), model.id) + "?ajax=1"
);
expect(user.get('upvoted_ids')).toEqual([]);
return expect(model.get('votes').up_count).toEqual(initialVoteCount - 1);
};
@checkUnvote = (view, model, user, event) -> DiscussionViewSpecHelper.checkButtonEvents = function(view, viewFunc, buttonSelector) {
user.vote(model) var button, spy;
expect(model.id in user.get('upvoted_ids')).toBe(true) spy = spyOn(view, viewFunc);
initialVoteCount = model.get('votes').up_count button = view.$el.find(buttonSelector);
triggerVoteEvent(view, event, DiscussionUtil.urlFor("undo_vote_for_#{model.get('type')}", model.id) + "?ajax=1") button.click();
expect(user.get('upvoted_ids')).toEqual([]) expect(spy).toHaveBeenCalled();
expect(model.get('votes').up_count).toEqual(initialVoteCount - 1) 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();
};
@checkButtonEvents = (view, viewFunc, buttonSelector) -> DiscussionViewSpecHelper.checkVoteButtonEvents = function(view) {
spy = spyOn(view, viewFunc) return this.checkButtonEvents(view, "toggleVote", ".action-vote");
button = view.$el.find(buttonSelector) };
DiscussionViewSpecHelper.setNextResponseContent = function(content) {
return $.ajax.and.callFake(function(params) {
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: []
});
this.view = new ResponseCommentShowView({
model: this.comment
});
return spyOn(this.view, "convertMath");
});
it('defines the tag', function() {
expect($('#jasmine-fixtures')).toExist();
expect(this.view.tagName).toBeDefined();
return expect(this.view.el.tagName.toLowerCase()).toBe('li');
});
it('is tied to the model', function() {
return expect(this.view.model).toBeDefined();
});
describe('rendering', function() {
beforeEach(function() {
return spyOn(this.view, 'renderAttrs');
});
it('can be flagged for abuse', function() {
this.comment.flagAbuse();
return expect(this.comment.get('abuse_flaggers')).toEqual(['123', '567']);
});
it('can be unflagged for abuse', function() {
var temp_array;
temp_array = [];
temp_array.push(window.user.get('id'));
this.comment.set("abuse_flaggers", temp_array);
this.comment.unflagAbuse();
return expect(this.comment.get('abuse_flaggers')).toEqual([]);
});
});
describe('_delete', function() {
it('triggers on the correct events', function() {
DiscussionUtil.loadRoles([]);
this.comment.updateInfo({
ability: {
'can_delete': true
} }
@view = new ResponseCommentShowView({ model: @comment }) });
spyOn(@view, "convertMath") this.view.render();
return DiscussionViewSpecHelper.checkButtonEvents(this.view, "_delete", ".action-delete");
it 'defines the tag', -> });
expect($('#jasmine-fixtures')).toExist it('triggers the delete event', function() {
expect(@view.tagName).toBeDefined var triggerTarget;
expect(@view.el.tagName.toLowerCase()).toBe 'li' triggerTarget = jasmine.createSpy();
this.view.bind("comment:_delete", triggerTarget);
it 'is tied to the model', -> this.view._delete();
expect(@view.model).toBeDefined() return expect(triggerTarget).toHaveBeenCalled();
});
describe 'rendering', -> });
describe('edit', function() {
beforeEach -> it('triggers on the correct events', function() {
spyOn(@view, 'renderAttrs') DiscussionUtil.loadRoles([]);
this.comment.updateInfo({
it 'can be flagged for abuse', -> ability: {
@comment.flagAbuse() 'can_edit': true
expect(@comment.get 'abuse_flaggers').toEqual ['123', '567'] }
});
it 'can be unflagged for abuse', -> this.view.render();
temp_array = [] return DiscussionViewSpecHelper.checkButtonEvents(this.view, "edit", ".action-edit");
temp_array.push(window.user.get('id')) });
@comment.set("abuse_flaggers",temp_array) it('triggers comment:edit when the edit button is clicked', function() {
@comment.unflagAbuse() var triggerTarget;
expect(@comment.get 'abuse_flaggers').toEqual [] triggerTarget = jasmine.createSpy();
this.view.bind("comment:edit", triggerTarget);
describe '_delete', -> this.view.edit();
return expect(triggerTarget).toHaveBeenCalled();
it 'triggers on the correct events', -> });
DiscussionUtil.loadRoles [] });
@comment.updateInfo {ability: {'can_delete': true}} describe("labels", function() {
@view.render() var expectOneElement;
DiscussionViewSpecHelper.checkButtonEvents(@view, "_delete", ".action-delete") expectOneElement = function(view, selector, visible) {
var elements;
it 'triggers the delete event', -> if (typeof visible === "undefined" || visible === null) {
triggerTarget = jasmine.createSpy() visible = true;
@view.bind "comment:_delete", triggerTarget }
@view._delete() view.render();
expect(triggerTarget).toHaveBeenCalled() elements = view.$el.find(selector);
expect(elements.length).toEqual(1);
describe 'edit', -> if (visible) {
return expect(elements).not.toHaveClass("is-hidden");
it 'triggers on the correct events', -> } else {
DiscussionUtil.loadRoles [] return expect(elements).toHaveClass("is-hidden");
@comment.updateInfo {ability: {'can_edit': true}} }
@view.render() };
DiscussionViewSpecHelper.checkButtonEvents(@view, "edit", ".action-edit") it('displays the reported label when appropriate for a non-staff user', function() {
this.comment.set('abuse_flaggers', []);
it 'triggers comment:edit when the edit button is clicked', -> expectOneElement(this.view, '.post-label-reported', false);
triggerTarget = jasmine.createSpy() this.comment.set('abuse_flaggers', [DiscussionUtil.getUser().id]);
@view.bind "comment:edit", triggerTarget expectOneElement(this.view, '.post-label-reported');
@view.edit() this.comment.set('abuse_flaggers', [DiscussionUtil.getUser().id + 1]);
expect(triggerTarget).toHaveBeenCalled() return expectOneElement(this.view, '.post-label-reported', false);
});
describe "labels", -> it('displays the reported label when appropriate for a flag moderator', function() {
DiscussionSpecHelper.makeModerator();
expectOneElement = (view, selector, visible=true) => this.comment.set('abuse_flaggers', []);
view.render() expectOneElement(this.view, '.post-label-reported', false);
elements = view.$el.find(selector) this.comment.set('abuse_flaggers', [DiscussionUtil.getUser().id]);
expect(elements.length).toEqual(1) expectOneElement(this.view, '.post-label-reported');
if visible this.comment.set('abuse_flaggers', [DiscussionUtil.getUser().id + 1]);
expect(elements).not.toHaveClass("is-hidden") return expectOneElement(this.view, '.post-label-reported');
else });
expect(elements).toHaveClass("is-hidden") });
});
it 'displays the reported label when appropriate for a non-staff user', ->
@comment.set('abuse_flaggers', []) }).call(this);
expectOneElement(@view, '.post-label-reported', false)
# flagged by current user - should be labelled
@comment.set('abuse_flaggers', [DiscussionUtil.getUser().id])
expectOneElement(@view, '.post-label-reported')
# flagged by some other user but not the current one - should not be labelled
@comment.set('abuse_flaggers', [DiscussionUtil.getUser().id + 1])
expectOneElement(@view, '.post-label-reported', false)
it 'displays the reported label when appropriate for a flag moderator', ->
DiscussionSpecHelper.makeModerator()
@comment.set('abuse_flaggers', [])
expectOneElement(@view, '.post-label-reported', false)
# flagged by current user - should be labelled
@comment.set('abuse_flaggers', [DiscussionUtil.getUser().id])
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])
expectOneElement(@view, '.post-label-reported')
describe 'ThreadResponseView', -> /* globals DiscussionSpecHelper, ResponseCommentView, Thread, ThreadResponseView, ThreadResponseShowView */
beforeEach -> (function() {
DiscussionSpecHelper.setUpGlobals() 'use strict';
DiscussionSpecHelper.setUnderscoreFixtures() describe('ThreadResponseView', function() {
beforeEach(function() {
@thread = new Thread({"thread_type": "discussion"}) DiscussionSpecHelper.setUpGlobals();
@response = new Comment { DiscussionSpecHelper.setUnderscoreFixtures();
this.thread = new Thread({
"thread_type": "discussion"
});
this.response = new Comment({
children: [{}, {}], children: [{}, {}],
thread: @thread, thread: this.thread
} });
@view = new ThreadResponseView({model: @response, el: $("#fixture-element")}) this.view = new ThreadResponseView({
spyOn(ThreadResponseShowView.prototype, "render") model: this.response,
spyOn(ResponseCommentView.prototype, "render") el: $("#fixture-element")
});
describe 'closed and open Threads', -> spyOn(ThreadResponseShowView.prototype, "render");
checkCommentForm = (closed) -> return spyOn(ResponseCommentView.prototype, "render");
thread = new Thread({"thread_type": "discussion", "closed": closed}) });
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 = { commentData = {
id: "dummy", id: "dummy",
user_id: "567", user_id: "567",
...@@ -24,69 +36,88 @@ describe 'ThreadResponseView', -> ...@@ -24,69 +36,88 @@ describe 'ThreadResponseView', ->
abuse_flaggers: [], abuse_flaggers: [],
type: "comment", type: "comment",
children: [], children: [],
thread: thread, thread: thread
} };
comment = new Comment(commentData) comment = new Comment(commentData);
view = new ThreadResponseView({ view = new ThreadResponseView({
model: comment, el: $("#fixture-element"), model: comment,
}) el: $("#fixture-element")
view.render() });
expect(view.$('.comment-form').closest('li').is(":visible")).toBe(not closed) view.render();
return expect(view.$('.comment-form').closest('li').is(":visible")).toBe(!closed);
it 'hides comment form when thread is closed', -> };
checkCommentForm(true) it('hides comment form when thread is closed', function() {
return checkCommentForm(true);
it 'show comment form when thread is open', -> });
checkCommentForm(false) it('show comment form when thread is open', function() {
return checkCommentForm(false);
describe 'renderComments', -> });
it 'hides "show comments" link if collapseComments is not set', -> });
@view.render() describe('renderComments', function() {
expect(@view.$(".comments")).toBeVisible() it('hides "show comments" link if collapseComments is not set', function() {
expect(@view.$(".action-show-comments")).not.toBeVisible() this.view.render();
expect(this.view.$(".comments")).toBeVisible();
it 'hides "show comments" link if collapseComments is set but response has no comments', -> return expect(this.view.$(".action-show-comments")).not.toBeVisible();
@response = new Comment { children: [], thread: @thread } });
@view = new ThreadResponseView({ it('hides "show comments" link if collapseComments is set but response has no comments', function() {
model: @response, el: $("#fixture-element"), this.response = new Comment({
children: [],
thread: this.thread
});
this.view = new ThreadResponseView({
model: this.response,
el: $("#fixture-element"),
collapseComments: true collapseComments: true
}) });
@view.render() this.view.render();
expect(@view.$(".comments")).toBeVisible() expect(this.view.$(".comments")).toBeVisible();
expect(@view.$(".action-show-comments")).not.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', -> it(
@view = new ThreadResponseView({ 'hides comments if collapseComments is set and shows them when "show comments" link is clicked',
model: @response, el: $("#fixture-element"), function() {
this.view = new ThreadResponseView({
model: this.response,
el: $("#fixture-element"),
collapseComments: true collapseComments: true
}) });
@view.render() this.view.render();
expect(@view.$(".comments")).not.toBeVisible() expect(this.view.$(".comments")).not.toBeVisible();
expect(@view.$(".action-show-comments")).toBeVisible() expect(this.view.$(".action-show-comments")).toBeVisible();
@view.$(".action-show-comments").click() this.view.$(".action-show-comments").click();
expect(@view.$(".comments")).toBeVisible() expect(this.view.$(".comments")).toBeVisible();
expect(@view.$(".action-show-comments")).not.toBeVisible() return expect(this.view.$(".action-show-comments")).not.toBeVisible();
}
it 'populates commentViews and binds events', -> );
# Ensure that edit view is set to test invocation of cancelEdit it('populates commentViews and binds events', function() {
@view.createEditView() this.view.createEditView();
spyOn(@view, 'cancelEdit') spyOn(this.view, 'cancelEdit');
spyOn(@view, 'cancelCommentEdits') spyOn(this.view, 'cancelCommentEdits');
spyOn(@view, 'hideCommentForm') spyOn(this.view, 'hideCommentForm');
spyOn(@view, 'showCommentForm') spyOn(this.view, 'showCommentForm');
@view.renderComments() this.view.renderComments();
expect(@view.commentViews.length).toEqual(2) expect(this.view.commentViews.length).toEqual(2);
@view.commentViews[0].trigger "comment:edit", jasmine.createSpyObj("event", ["preventDefault"]) this.view.commentViews[0].trigger("comment:edit", jasmine.createSpyObj("event", ["preventDefault"]));
expect(@view.cancelEdit).toHaveBeenCalled() expect(this.view.cancelEdit).toHaveBeenCalled();
expect(@view.cancelCommentEdits).toHaveBeenCalled() expect(this.view.cancelCommentEdits).toHaveBeenCalled();
expect(@view.hideCommentForm).toHaveBeenCalled() expect(this.view.hideCommentForm).toHaveBeenCalled();
@view.commentViews[0].trigger "comment:cancel_edit" this.view.commentViews[0].trigger("comment:cancel_edit");
expect(@view.showCommentForm).toHaveBeenCalled() 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();
});
});
});
});
describe 'cancelCommentEdits', -> }).call(this);
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 };
});
};
DiscussionSpecHelper.makeEventSpy = function () {
return jasmine.createSpyObj('event', ['preventDefault', 'target']);
};
DiscussionSpecHelper.makeCourseSettings = function (is_cohorted) {
if (typeof is_cohorted === 'undefined' || is_cohorted === null) {
is_cohorted = true;
}
return new DiscussionCourseSettings({
category_map: {
children: ['Test Topic', 'Other Topic'],
entries: {
'Test Topic': {
is_cohorted: is_cohorted,
id: 'test_topic' id: 'test_topic'
'Other Topic': },
is_cohorted: is_cohorted 'Other Topic': {
is_cohorted: is_cohorted,
id: 'other_topic' id: 'other_topic'
}
}
},
is_cohorted: is_cohorted is_cohorted: is_cohorted
) });
};
@setUnderscoreFixtures = -> DiscussionSpecHelper.setUnderscoreFixtures = function () {
var templateFixture, templateName, templateNames, templateNamesNoTrailingTemplate, _i, _j, _len, _len1;
templateNames = [ templateNames = [
'thread', 'thread-show', 'thread-edit', 'thread', 'thread-show', 'thread-edit', 'thread-response', 'thread-response-show',
'thread-response', 'thread-response-show', 'thread-response-edit', 'thread-response-edit', 'response-comment-show', 'response-comment-edit', 'thread-list-item',
'response-comment-show', 'response-comment-edit', 'discussion-home', 'search-alert', 'new-post', 'thread-type', 'new-post-menu-entry',
'thread-list-item', 'discussion-home', 'search-alert', 'new-post-menu-category', 'topic', 'post-user-display', 'inline-discussion', 'pagination',
'new-post', 'thread-type', 'new-post-menu-entry', 'user-profile', 'profile-thread'
'new-post-menu-category', 'topic', 'post-user-display', ];
'inline-discussion', 'pagination', 'user-profile', 'profile-thread'
]
templateNamesNoTrailingTemplate = [ templateNamesNoTrailingTemplate = [
'forum-action-endorse', 'forum-action-answer', 'forum-action-follow', 'forum-action-endorse', 'forum-action-answer', 'forum-action-follow', 'forum-action-vote',
'forum-action-vote', 'forum-action-report', 'forum-action-pin', 'forum-action-report', 'forum-action-pin', 'forum-action-close', 'forum-action-edit',
'forum-action-close', 'forum-action-edit', 'forum-action-delete', 'forum-action-delete', 'forum-actions'
'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;
})();
for templateName in templateNames }).call(this);
templateFixture = readFixtures('common/templates/discussion/' + templateName + '.underscore')
appendSetFixtures($('<script>', { id: templateName + '-template', type: 'text/template' })
.text(templateFixture))
for templateName in templateNamesNoTrailingTemplate
templateFixture = readFixtures('common/templates/discussion/' + templateName + '.underscore')
appendSetFixtures($('<script>', { id: templateName, type: 'text/template' })
.text(templateFixture))
appendSetFixtures("""
<div id="fixture-element"></div>
<div id="discussion-container"
data-course-name="Fake Course"
data-user-create-comment="true"
data-user-create-subcomment="true"
data-read-only="false"
></div>
""")
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