Skip to content

Commit

Permalink
Convert course list to React.
Browse files Browse the repository at this point in the history
EDUCATOR-625, AC-620
  • Loading branch information
cahrens committed Aug 9, 2017
1 parent f0c6853 commit e1e57b5
Show file tree
Hide file tree
Showing 20 changed files with 277 additions and 269 deletions.
3 changes: 2 additions & 1 deletion .babelrc
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
},
"modules": false
}
]
],
"babel-preset-react"
]
}
42 changes: 3 additions & 39 deletions cms/djangoapps/contentstore/tests/test_contentstore.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@
from opaque_keys.edx.locations import AssetLocation, CourseLocator
from path import Path as path

from common.test.utils import XssTestMixin
from contentstore.tests.utils import AjaxEnabledTestClient, CourseTestCase, get_url, parse_json
from contentstore.utils import delete_course, reverse_course_url, reverse_url
from contentstore.views.component import ADVANCED_COMPONENT_TYPES
Expand Down Expand Up @@ -1138,7 +1137,7 @@ def _check_verticals(self, locations):


@ddt.ddt
class ContentStoreTest(ContentStoreTestCase, XssTestMixin):
class ContentStoreTest(ContentStoreTestCase):
"""
Tests for the CMS ContentStore application.
"""
Expand Down Expand Up @@ -1473,33 +1472,6 @@ def test_item_factory(self):
item = ItemFactory.create(parent_location=course.location)
self.assertIsInstance(item, SequenceDescriptor)

def test_course_index_view_with_course(self):
"""Test viewing the index page with an existing course"""
CourseFactory.create(display_name='Robot Super Educational Course')
resp = self.client.get_html('/home/')
self.assertContains(
resp,
'<h3 class="course-title">Robot Super Educational Course</h3>',
status_code=200,
html=True
)

def test_course_index_view_xss(self):
"""Test that the index page correctly escapes course names with script
tags."""
CourseFactory.create(
display_name='<script>alert("course XSS")</script>'
)

LibraryFactory.create(display_name='<script>alert("library XSS")</script>')

resp = self.client.get_html('/home/')
for xss in ('course', 'library'):
html = '<script>alert("{name} XSS")</script>'.format(
name=xss
)
self.assert_no_xss(resp, html)

def test_course_overview_view_with_course(self):
"""Test viewing the course overview page with an existing course"""
course = CourseFactory.create()
Expand Down Expand Up @@ -1911,30 +1883,22 @@ def post_rerun_request(
destination_course_key = CourseKey.from_string(json_resp['destination_course_key'])
return destination_course_key

def get_course_listing_elements(self, html, course_key):
"""Returns the elements in the course listing section of html that have the given course_key"""
return html.cssselect('.course-item[data-course-key="{}"]'.format(unicode(course_key)))

def get_unsucceeded_course_action_elements(self, html, course_key):
"""Returns the elements in the unsucceeded course action section that have the given course_key"""
return html.cssselect('.courses-processing li[data-course-key="{}"]'.format(unicode(course_key)))

def assertInCourseListing(self, course_key):
"""
Asserts that the given course key is in the accessible course listing section of the html
and NOT in the unsucceeded course action section of the html.
Asserts that the given course key is NOT in the unsucceeded course action section of the html.
"""
course_listing = lxml.html.fromstring(self.client.get_html('/home/').content)
self.assertEqual(len(self.get_course_listing_elements(course_listing, course_key)), 1)
self.assertEqual(len(self.get_unsucceeded_course_action_elements(course_listing, course_key)), 0)

def assertInUnsucceededCourseActions(self, course_key):
"""
Asserts that the given course key is in the unsucceeded course action section of the html
and NOT in the accessible course listing section of the html.
Asserts that the given course key is in the unsucceeded course action section of the html.
"""
course_listing = lxml.html.fromstring(self.client.get_html('/home/').content)
self.assertEqual(len(self.get_course_listing_elements(course_listing, course_key)), 0)
self.assertEqual(len(self.get_unsucceeded_course_action_elements(course_listing, course_key)), 1)

def verify_rerun_course(self, source_course_key, destination_course_key, destination_display_name):
Expand Down
28 changes: 1 addition & 27 deletions cms/djangoapps/contentstore/tests/test_course_listing.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,9 @@
from chrono import Timer
from django.conf import settings
from django.test import RequestFactory
from django.test.client import Client
from mock import Mock, patch
from opaque_keys.edx.locations import CourseLocator

from common.test.utils import XssTestMixin
from contentstore.tests.utils import AjaxEnabledTestClient
from contentstore.utils import delete_course
from contentstore.views.course import (
Expand Down Expand Up @@ -44,7 +42,7 @@


@ddt.ddt
class TestCourseListing(ModuleStoreTestCase, XssTestMixin):
class TestCourseListing(ModuleStoreTestCase):
"""
Unit tests for getting the list of courses for a logged in user
"""
Expand Down Expand Up @@ -88,30 +86,6 @@ def tearDown(self):
self.client.logout()
ModuleStoreTestCase.tearDown(self)

def test_course_listing_is_escaped(self):
"""
Tests course listing returns escaped data.
"""
escaping_content = "<script>alert('ESCAPE')</script>"

# Make user staff to access course listing
self.user.is_staff = True
self.user.save() # pylint: disable=no-member

self.client = Client()
self.client.login(username=self.user.username, password='test')

# Change 'display_coursenumber' field and update the course.
course = CourseFactory.create()
course.display_coursenumber = escaping_content
course = self.store.update_item(course, self.user.id) # pylint: disable=no-member
self.assertEqual(course.display_coursenumber, escaping_content)

# Check if response is escaped
response = self.client.get('/home')
self.assertEqual(response.status_code, 200)
self.assert_no_xss(response, escaping_content)

def test_empty_course_listing(self):
"""
Test on empty course listing, studio name is properly displayed
Expand Down
74 changes: 15 additions & 59 deletions cms/djangoapps/contentstore/views/tests/test_course_index.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,55 +52,34 @@ def setUp(self):
display_name='dotted.course.name-2',
)

def check_index_and_outline(self, authed_client):
def check_courses_on_index(self, authed_client):
"""
Test getting the list of courses and then pulling up their outlines
Test that the React course listing is present.
"""
index_url = '/home/'
index_response = authed_client.get(index_url, {}, HTTP_ACCEPT='text/html')
parsed_html = lxml.html.fromstring(index_response.content)
course_link_eles = parsed_html.find_class('course-link')
self.assertGreaterEqual(len(course_link_eles), 2)
for link in course_link_eles:
self.assertRegexpMatches(
link.get("href"),
'course/{}'.format(settings.COURSE_KEY_PATTERN)
)
# now test that url
outline_response = authed_client.get(link.get("href"), {}, HTTP_ACCEPT='text/html')
# ensure it has the expected 2 self referential links
outline_parsed = lxml.html.fromstring(outline_response.content)
outline_link = outline_parsed.find_class('course-link')[0]
self.assertEqual(outline_link.get("href"), link.get("href"))
course_menu_link = outline_parsed.find_class('nav-course-courseware-outline')[0]
self.assertEqual(course_menu_link.find("a").get("href"), link.get("href"))
courses_tab = parsed_html.find_class('react-course-listing')
self.assertEqual(len(courses_tab), 1)

def test_libraries_on_course_index(self):
def test_libraries_on_index(self):
"""
Test getting the list of libraries from the course listing page
Test that the library tab is present.
"""
def _assert_library_link_present(response, library):
def _assert_library_tab_present(response):
"""
Asserts there's a valid library link on libraries tab.
Asserts there's a library tab.
"""
parsed_html = lxml.html.fromstring(response.content)
library_link_elements = parsed_html.find_class('library-link')
self.assertEqual(len(library_link_elements), 1)
link = library_link_elements[0]
self.assertEqual(
link.get("href"),
reverse_library_url('library_handler', library.location.library_key),
)
# now test that url
outline_response = self.client.get(link.get("href"), {}, HTTP_ACCEPT='text/html')
self.assertEqual(outline_response.status_code, 200)
library_tab = parsed_html.find_class('react-library-listing')
self.assertEqual(len(library_tab), 1)

# Add a library:
lib1 = LibraryFactory.create()

index_url = '/home/'
index_response = self.client.get(index_url, {}, HTTP_ACCEPT='text/html')
_assert_library_link_present(index_response, lib1)
_assert_library_tab_present(index_response)

# Make sure libraries are visible to non-staff users too
self.client.logout()
Expand All @@ -109,13 +88,13 @@ def _assert_library_link_present(response, library):
LibraryUserRole(lib2.location.library_key).add_users(non_staff_user)
self.client.login(username=non_staff_user.username, password=non_staff_userpassword)
index_response = self.client.get(index_url, {}, HTTP_ACCEPT='text/html')
_assert_library_link_present(index_response, lib2)
_assert_library_tab_present(index_response)

def test_is_staff_access(self):
"""
Test that people with is_staff see the courses and can navigate into them
"""
self.check_index_and_outline(self.client)
self.check_courses_on_index(self.client)

def test_negative_conditions(self):
"""
Expand Down Expand Up @@ -143,7 +122,7 @@ def test_course_staff_access(self):
)

# test access
self.check_index_and_outline(course_staff_client)
self.check_courses_on_index(course_staff_client)

def test_json_responses(self):
outline_url = reverse_course_url('course_handler', self.course.id)
Expand Down Expand Up @@ -402,31 +381,8 @@ def check_index_page(self, separate_archived_courses, org):
parsed_html = lxml.html.fromstring(index_response.content)
course_tab = parsed_html.find_class('courses')
self.assertEqual(len(course_tab), 1)
course_links = course_tab[0].find_class('course-link')
course_titles = course_tab[0].find_class('course-title')
archived_course_tab = parsed_html.find_class('archived-courses')

if separate_archived_courses:
# Archived courses should be separated from the main course list
self.assertEqual(len(archived_course_tab), 1)
archived_course_links = archived_course_tab[0].find_class('course-link')
archived_course_titles = archived_course_tab[0].find_class('course-title')
self.assertEqual(len(archived_course_links), 1)
self.assertEqual(len(archived_course_titles), 1)
self.assertEqual(archived_course_titles[0].text, 'Archived Course')

self.assertEqual(len(course_links), 2)
self.assertEqual(len(course_titles), 2)
self.assertEqual(course_titles[0].text, 'Active Course 1')
self.assertEqual(course_titles[1].text, 'Active Course 2')
else:
# Archived courses should be included in the main course list
self.assertEqual(len(archived_course_tab), 0)
self.assertEqual(len(course_links), 3)
self.assertEqual(len(course_titles), 3)
self.assertEqual(course_titles[0].text, 'Active Course 1')
self.assertEqual(course_titles[1].text, 'Active Course 2')
self.assertEqual(course_titles[2].text, 'Archived Course')
self.assertEqual(len(archived_course_tab), 1 if separate_archived_courses else 0)

@ddt.data(
# Staff user has course staff access
Expand Down
4 changes: 4 additions & 0 deletions cms/envs/acceptance.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,10 @@ def seed():
# We do not yet understand why this occurs. Setting this to true is a stopgap measure
USE_I18N = True

# Override the test stub webpack_loader that is installed in test.py.
INSTALLED_APPS = tuple(app for app in INSTALLED_APPS if app != 'openedx.tests.util.webpack_loader')
INSTALLED_APPS += ('webpack_loader',)

# Include the lettuce app for acceptance testing, including the 'harvest' django-admin command
# django.contrib.staticfiles used to be loaded by lettuce, now we must add it ourselves
# django.contrib.staticfiles is not added to lms as there is a ^/static$ route built in to the app
Expand Down
4 changes: 4 additions & 0 deletions cms/envs/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,10 @@

# Whether or not the dynamic EnrollmentTrackUserPartition should be registered.
'ENABLE_ENROLLMENT_TRACK_USER_PARTITION': True,

# Whether archived courses (courses with end dates in the past) should be
# shown in Studio in a separate list.
'ENABLE_SEPARATE_ARCHIVED_COURSES': True
}

ENABLE_JASMINE = False
Expand Down
7 changes: 7 additions & 0 deletions cms/static/js/features_jsx/.eslintrc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
module.exports = {
extends: 'eslint-config-edx',
root: true,
settings: {
'import/resolver': 'webpack',
},
};
Loading

0 comments on commit e1e57b5

Please sign in to comment.