diff --git a/common/static/coffee/spec/discussion/view/discussion_thread_view_spec.coffee b/common/static/coffee/spec/discussion/view/discussion_thread_view_spec.coffee new file mode 100644 index 0000000000..bd251de39f --- /dev/null +++ b/common/static/coffee/spec/discussion/view/discussion_thread_view_spec.coffee @@ -0,0 +1,88 @@ +describe "DiscussionThreadView", -> + beforeEach -> + setFixtures( + """ + +
+ """ + ) + + jasmine.Clock.useMock() + @threadData = { + id: "dummy" + } + @thread = new Thread(@threadData) + @view = new DiscussionThreadView({ model: @thread }) + @view.setElement($(".thread-fixture")) + spyOn($, "ajax") + # Avoid unnecessary boilerplate + spyOn(@view.showView, "render") + spyOn(@view, "makeWmdEditor") + spyOn(DiscussionThreadView.prototype, "renderResponse") + + describe "response count and pagination", -> + + setNextResponseContent = (content) -> + $.ajax.andCallFake( + (params) => + params.success({"content": content}) + {always: ->} + ) + + renderWithContent = (view, content) -> + setNextResponseContent(content) + view.render() + jasmine.Clock.tick(100) + + assertRenderedCorrectly = (view, countText, displayCountText, buttonText) -> + expect(view.$el.find(".response-count").text()).toEqual(countText) + if displayCountText + expect(view.$el.find(".response-display-count").text()).toEqual(displayCountText) + else + expect(view.$el.find(".response-display-count").length).toEqual(0) + if buttonText + expect(view.$el.find(".load-response-button").text()).toEqual(buttonText) + else + expect(view.$el.find(".load-response-button").length).toEqual(0) + + it "correctly render for a thread with no responses", -> + renderWithContent(@view, {resp_total: 0, children: []}) + assertRenderedCorrectly(@view, "0 responses", null, null) + + it "correctly render for a thread with one response", -> + renderWithContent(@view, {resp_total: 1, children: [{}]}) + assertRenderedCorrectly(@view, "1 response", "Showing all responses", null) + + it "correctly render for a thread with one additional page", -> + renderWithContent(@view, {resp_total: 2, children: [{}]}) + assertRenderedCorrectly(@view, "2 responses", "Showing first response", "Load all responses") + + it "correctly render for a thread with multiple additional pages", -> + renderWithContent(@view, {resp_total: 111, children: [{}, {}]}) + assertRenderedCorrectly(@view, "111 responses", "Showing first 2 responses", "Load next 100 responses") + + describe "on clicking the load more button", -> + beforeEach -> + renderWithContent(@view, {resp_total: 5, children: [{}]}) + assertRenderedCorrectly(@view, "5 responses", "Showing first response", "Load all responses") + + it "correctly re-render when all threads have loaded", -> + setNextResponseContent({resp_total: 5, children: [{}, {}, {}, {}]}) + @view.$el.find(".load-response-button").click() + assertRenderedCorrectly(@view, "5 responses", "Showing all responses", null) + + it "correctly re-render when one page remains", -> + setNextResponseContent({resp_total: 42, children: [{}, {}]}) + @view.$el.find(".load-response-button").click() + assertRenderedCorrectly(@view, "42 responses", "Showing first 3 responses", "Load all responses") + + it "correctly re-render when multiple pages remain", -> + setNextResponseContent({resp_total: 111, children: [{}, {}]}) + @view.$el.find(".load-response-button").click() + assertRenderedCorrectly(@view, "111 responses", "Showing first 3 responses", "Load next 100 responses") diff --git a/common/static/coffee/src/discussion/views/discussion_thread_view.coffee b/common/static/coffee/src/discussion/views/discussion_thread_view.coffee index e79dafacdd..95cda6b255 100644 --- a/common/static/coffee/src/discussion/views/discussion_thread_view.coffee +++ b/common/static/coffee/src/discussion/views/discussion_thread_view.coffee @@ -1,6 +1,9 @@ if Backbone? class @DiscussionThreadView extends DiscussionContentView + INITIAL_RESPONSE_PAGE_SIZE = 25 + SUBSEQUENT_RESPONSE_PAGE_SIZE = 100 + events: "click .discussion-submit-post": "submitComment" "click .add-response-btn": "scrollToAddResponse" @@ -11,6 +14,7 @@ if Backbone? initialize: -> super() @createShowView() + @responses = new Comments() renderTemplate: -> @template = _.template($("#thread-template").html()) @@ -18,7 +22,6 @@ if Backbone? render: -> @$el.html(@renderTemplate()) - @$el.find(".loading").hide() @delegateEvents() @renderShowView() @@ -27,26 +30,95 @@ if Backbone? @$("span.timeago").timeago() @makeWmdEditor "reply-body" @renderAddResponseButton() - @renderResponses() + @responses.on("add", @renderResponse) + # Without a delay, jQuery doesn't add the loading extension defined in + # utils.coffee before safeAjax is invoked, which results in an error + setTimeout( + => @loadResponses(INITIAL_RESPONSE_PAGE_SIZE, @$el.find(".responses"), true), + 100 + ) @ cleanup: -> if @responsesRequest? @responsesRequest.abort() - renderResponses: -> - setTimeout(=> - @$el.find(".loading").show() - , 200) + loadResponses: (responseLimit, elem, firstLoad) -> @responsesRequest = DiscussionUtil.safeAjax url: DiscussionUtil.urlFor('retrieve_single_thread', @model.get('commentable_id'), @model.id) + data: + resp_skip: @responses.size() + resp_limit: responseLimit if responseLimit + $elem: elem + $loading: elem + takeFocus: true + complete: => + @responseRequest = null success: (data, textStatus, xhr) => - @responsesRequest = null - @$el.find(".loading").remove() Content.loadContentInfos(data['annotated_content_info']) - comments = new Comments(data['content']['children']) - comments.each @renderResponse + @responses.add(data['content']['children']) + @renderResponseCountAndPagination(data['content']['resp_total']) @trigger "thread:responses:rendered" + error: => + if firstLoad + DiscussionUtil.discussionAlert( + gettext("Sorry"), + gettext("We had some trouble loading responses. Please reload the page.") + ) + else + DiscussionUtil.discussionAlert( + gettext("Sorry"), + gettext("We had some trouble loading more responses. Please try again.") + ) + + renderResponseCountAndPagination: (responseTotal) => + @$el.find(".response-count").html( + interpolate( + ngettext( + "%(numResponses)s response", + "%(numResponses)s responses", + responseTotal + ), + {numResponses: responseTotal}, + true + ) + ) + responsePagination = @$el.find(".response-pagination") + responsePagination.empty() + if responseTotal > 0 + responsesRemaining = responseTotal - @responses.size() + showingResponsesText = + if responsesRemaining == 0 + gettext("Showing all responses") + else + interpolate( + ngettext( + "Showing first response", + "Showing first %(numResponses)s responses", + @responses.size() + ), + {numResponses: @responses.size()}, + true + ) + responsePagination.append($("").addClass("response-display-count").html( + _.escape(showingResponsesText) + )) + if responsesRemaining > 0 + if responsesRemaining < SUBSEQUENT_RESPONSE_PAGE_SIZE + responseLimit = null + buttonText = gettext("Load all responses") + else + responseLimit = SUBSEQUENT_RESPONSE_PAGE_SIZE + buttonText = interpolate( + gettext("Load next %(numResponses)s responses"), + {numResponses: responseLimit}, + true + ) + loadMoreButton = $("