Repository: learningequality/kolibri

Issue:
When finishing a resource in a lesson, the completion modal does not move you on to the next item in the lesson
## Observed behavior
When finishing a resource in a lesson, the completion modal does not move you on to the next item in the lesson.

First it doesn't render the content, but when I refresh, it does.

## Expected behavior
Should just move the learner on to the next resource in the lesson.



Discussion:
This seems to have been resolved, never mind!
Initially thought to be only in the past-papers project, but actually extant in the general 0.15 branch.

Clicking the <kbd>Move on</kbd> button in the lesson completion modal _moves on_ (displays) to the next resource in the same folder as the completed resource, and NOT to the next resource in the lesson as set by the coach.

Moving on to _Intro to addition_ (next in the Kolibri QA channel, after the _Welcome_) | Moving on to _Where the babies come from_ (next in the Kolibri QA channel, after the _Seven rivers..._)
--- | ---
![move-on-skipps-ppp](https://user-images.githubusercontent.com/1457929/142703380-a451109f-fe1d-4093-bf0c-f58393e98e5b.gif) | ![move-on-next in folder](https://user-images.githubusercontent.com/1457929/142703375-e4c53f0e-cdae-4290-9c04-f4d96a3d947b.gif)



Resolving patch:
diff --git a/kolibri/plugins/learn/viewsets.py b/kolibri/plugins/learn/viewsets.py
--- a/kolibri/plugins/learn/viewsets.py
+++ b/kolibri/plugins/learn/viewsets.py
@@ -49,6 +49,49 @@ def get(self, request, format=None):
         )
 
 
+def _consolidate_lessons_data(request, lessons):
+    lesson_contentnode_ids = set()
+    for lesson in lessons:
+        lesson_contentnode_ids |= {
+            resource["contentnode_id"] for resource in lesson["resources"]
+        }
+
+    contentnode_progress = (
+        contentnode_progress_viewset.serialize_list(
+            request, {"ids": lesson_contentnode_ids}
+        )
+        if lesson_contentnode_ids
+        else []
+    )
+
+    contentnodes = (
+        contentnode_viewset.serialize_list(request, {"ids": lesson_contentnode_ids})
+        if lesson_contentnode_ids
+        else []
+    )
+
+    progress_map = {l["content_id"]: l["progress"] for l in contentnode_progress}
+
+    contentnode_map = {c["id"]: c for c in contentnodes}
+
+    for lesson in lessons:
+        lesson["progress"] = {
+            "resource_progress": sum(
+                (
+                    progress_map[resource["content_id"]]
+                    for resource in lesson["resources"]
+                    if resource["content_id"] in progress_map
+                )
+            ),
+            "total_resources": len(lesson["resources"]),
+        }
+        for resource in lesson["resources"]:
+            resource["progress"] = progress_map.get(resource["content_id"], 0)
+            resource["contentnode"] = contentnode_map.get(
+                resource["contentnode_id"], None
+            )
+
+
 class LearnerClassroomViewset(ReadOnlyValuesViewset):
     """
     Returns all Classrooms for which the requesting User is a member,
@@ -79,48 +122,7 @@ def consolidate(self, items, queryset):
                 "description", "id", "is_active", "title", "resources", "collection"
             )
         )
-        lesson_contentnode_ids = set()
-        for lesson in lessons:
-            lesson_contentnode_ids |= {
-                resource["contentnode_id"] for resource in lesson["resources"]
-            }
-
-        contentnode_progress = (
-            contentnode_progress_viewset.serialize_list(
-                self.request, {"ids": lesson_contentnode_ids}
-            )
-            if lesson_contentnode_ids
-            else []
-        )
-
-        contentnodes = (
-            contentnode_viewset.serialize_list(
-                self.request, {"ids": lesson_contentnode_ids}
-            )
-            if lesson_contentnode_ids
-            else []
-        )
-
-        progress_map = {l["content_id"]: l["progress"] for l in contentnode_progress}
-
-        contentnode_map = {c["id"]: c for c in contentnodes}
-
-        for lesson in lessons:
-            lesson["progress"] = {
-                "resource_progress": sum(
-                    (
-                        progress_map[resource["content_id"]]
-                        for resource in lesson["resources"]
-                        if resource["content_id"] in progress_map
-                    )
-                ),
-                "total_resources": len(lesson["resources"]),
-            }
-            for resource in lesson["resources"]:
-                resource["progress"] = progress_map.get(resource["content_id"], 0)
-                resource["contentnode"] = contentnode_map.get(
-                    resource["contentnode_id"], None
-                )
+        _consolidate_lessons_data(self.request, lessons)
 
         user_masterylog_content_ids = MasteryLog.objects.filter(
             user=self.request.user
@@ -280,3 +282,11 @@ def get_queryset(self):
             lesson_assignments__collection__membership__user=self.request.user,
             is_active=True,
         )
+
+    def consolidate(self, items, queryset):
+        if not items:
+            return items
+
+        _consolidate_lessons_data(self.request, items)
+
+        return items


Test patch:
diff --git a/kolibri/plugins/learn/assets/test/views/completion-modal.spec.js b/kolibri/plugins/learn/assets/test/views/completion-modal.spec.js
--- a/kolibri/plugins/learn/assets/test/views/completion-modal.spec.js
+++ b/kolibri/plugins/learn/assets/test/views/completion-modal.spec.js
@@ -1,4 +1,4 @@
-import { createLocalVue, shallowMount, mount } from '@vue/test-utils';
+import { createLocalVue, mount } from '@vue/test-utils';
 import VueRouter from 'vue-router';
 
 import CompletionModal from '../../src/views/CompletionModal';
@@ -28,20 +28,16 @@ function makeWrapper({ propsData, data } = {}) {
     propsData,
     data,
     methods: {
-      loadNextContent: jest.fn(),
-      loadRecommendedContent: jest.fn(),
+      loadNextContent: jest.fn().mockResolvedValue(null),
+      loadRecommendedContent: jest.fn().mockResolvedValue([]),
+      loadNextLessonContent: jest.fn().mockResolvedValue(null),
     },
   });
 }
 
 describe('CompletionModal', () => {
   it('smoke test', () => {
-    const wrapper = shallowMount(CompletionModal, {
-      methods: {
-        loadNextContent: jest.fn(),
-        loadRecommendedContent: jest.fn(),
-      },
-    });
+    const wrapper = makeWrapper();
 
     expect(wrapper.exists()).toBe(true);
   });
@@ -100,15 +96,16 @@ describe('CompletionModal', () => {
     });
   });
 
-  it("displays 'Stay and practice' section with 'Stay here' button", () => {
+  it("displays 'Stay and practice' section with 'Stay here' button", async () => {
     const wrapper = makeWrapper();
-
+    await wrapper.vm.$nextTick();
     expect(wrapper.text()).toContain('Stay and practice');
     expect(getStayHereButton(wrapper).exists()).toBe(true);
   });
 
-  it("emits `close` even on 'Stay here' button click", () => {
+  it("emits `close` even on 'Stay here' button click", async () => {
     const wrapper = makeWrapper();
+    await wrapper.vm.$nextTick();
     getStayHereButton(wrapper).trigger('click');
 
     expect(wrapper.emitted().close).toBeTruthy();