diff --git a/cms/djangoapps/contentstore/features/textbooks.feature b/cms/djangoapps/contentstore/features/textbooks.feature new file mode 100644 index 0000000000..07b7868c41 --- /dev/null +++ b/cms/djangoapps/contentstore/features/textbooks.feature @@ -0,0 +1,20 @@ +Feature: Textbooks + + Scenario: No textbooks + Given I have opened a new course in Studio + When I go to the textbooks page + Then I should see a message telling me to create a new textbook + + Scenario: Create a textbook + Given I have opened a new course in Studio + And I go to the textbooks page + When I click on the New Textbook button + And I name my textbook "Economics" + And I name the first chapter "Chapter 1" + And I click the Upload Asset link for the first chapter + And I upload the textbook "textbook.pdf" + And I wait for "2" seconds + And I save the textbook + Then I should see a textbook named "Economics" with a chapter path containing "/c4x/MITx/999/asset/textbook.pdf" + And I reload the page + Then I should see a textbook named "Economics" with a chapter path containing "/c4x/MITx/999/asset/textbook.pdf" diff --git a/cms/djangoapps/contentstore/features/textbooks.py b/cms/djangoapps/contentstore/features/textbooks.py new file mode 100644 index 0000000000..69ed26ed46 --- /dev/null +++ b/cms/djangoapps/contentstore/features/textbooks.py @@ -0,0 +1,70 @@ +#pylint: disable=C0111 +#pylint: disable=W0621 + +from lettuce import world, step +from django.conf import settings +import requests +import string +import random +import os + +TEST_ROOT = settings.COMMON_TEST_DATA_ROOT +HTTP_PREFIX = "http://localhost:8001" + + +@step(u'I go to the textbooks page') +def go_to_uploads(_step): + world.click_course_content() + menu_css = 'li.nav-course-courseware-textbooks' + world.css_find(menu_css).click() + +@step(u'I should see a message telling me to create a new textbook') +def assert_create_new_textbook_msg(_step): + css = ".wrapper-content .no-textbook-content" + assert world.is_css_present(css) + no_tb = world.css_find(css) + assert "You haven't added any textbooks" in no_tb.text + +@step(u'I upload the textbook "([^"]*)"$') +def upload_file(_step, file_name): + file_css = '.upload-dialog input[type=file]' + upload = world.css_find(file_css) + # uploading the file itself + path = os.path.join(TEST_ROOT, 'uploads', file_name) + upload._element.send_keys(os.path.abspath(path)) + button_css = ".upload-dialog .action-upload" + world.css_click(button_css) + +@step(u'I click (on )?the New Textbook button') +def click_new_textbook(_step, on): + button_css = ".nav-actions .new-button" + button = world.css_find(button_css) + button.click() + +@step(u'I name my textbook "([^"]*)"') +def name_textbook(_step, name): + input_css = ".textbook input[name=textbook-name]" + world.css_fill(input_css, name) + +@step(u'I name the first chapter "([^"]*)"') +def name_chapter(_step, name): + input_css = ".textbook input.chapter-name" + world.css_fill(input_css, name) + +@step(u'I click the Upload Asset link for the first chapter') +def click_upload_asset(_step): + button_css = ".chapter .action-upload" + world.css_click(button_css) + +@step(u'I save the textbook') +def save_textbook(_step): + submit_css = "form.edit-textbook button[type=submit]" + world.css_click(submit_css) + +@step(u'I should see a textbook named "([^"]*)" with a chapter path containing "([^"]*)"') +def check_textbook(step, textbook_name, chapter_name): + title = world.css_find(".textbook h3.textbook-title") + chapter = world.css_find(".textbook .wrap-textbook p") + assert title.text == textbook_name, "{} != {}".format(title.text, textbook_name) + assert chapter.text == chapter_name, "{} != {}".format(chapter.text, chapter_name) + diff --git a/cms/djangoapps/contentstore/views/course.py b/cms/djangoapps/contentstore/views/course.py index 0405112ea1..3d0d9ad250 100644 --- a/cms/djangoapps/contentstore/views/course.py +++ b/cms/djangoapps/contentstore/views/course.py @@ -511,7 +511,7 @@ def textbook_index(request, org, course, name): def create_textbook(request, org, course, name): location = get_location_and_verify_access(request, org, course, name) store = get_modulestore(location) - course_module = store.get_item(location, depth=3) + course_module = store.get_item(location, depth=0) try: textbook = validate_textbook_json(request.body) @@ -520,7 +520,13 @@ def create_textbook(request, org, course, name): if not textbook.get("id"): tids = set(t["id"] for t in course_module.pdf_textbooks if "id" in t) textbook["id"] = assign_textbook_id(textbook, tids) - course_module.pdf_textbooks.append(textbook) + existing = course_module.pdf_textbooks + existing.append(textbook) + course_module.pdf_textbooks = existing + if not any(tab['type'] == 'pdf_textbooks' for tab in course_module.tabs): + tabs = course_module.tabs + tabs.append({"type": "pdf_textbooks"}) + course_module.tabs = tabs store.update_metadata(course_module.location, own_metadata(course_module)) resp = JsonResponse(textbook, status=201) resp["Location"] = reverse("textbook_by_id", kwargs={ diff --git a/cms/static/js/views/textbook.js b/cms/static/js/views/textbook.js index 89d2b0cb5c..b9fabae6f2 100644 --- a/cms/static/js/views/textbook.js +++ b/cms/static/js/views/textbook.js @@ -227,12 +227,16 @@ CMS.Views.EditChapter = Backbone.View.extend({ }, changeName: function(e) { if(e && e.preventDefault) { e.preventDefault(); } - this.model.set("name", this.$(".chapter-name").val()); + this.model.set({ + name: this.$(".chapter-name").val() + }, {silent: true}); return this; }, changeAssetPath: function(e) { if(e && e.preventDefault) { e.preventDefault(); } - this.model.set("asset_path", this.$(".chapter-asset-path").val()); + this.model.set({ + asset_path: this.$(".chapter-asset-path").val() + }, {silent: true}); return this; }, removeChapter: function(e) { diff --git a/common/djangoapps/terrain/ui_helpers.py b/common/djangoapps/terrain/ui_helpers.py index ccf5cc12e8..d6f64ea382 100644 --- a/common/djangoapps/terrain/ui_helpers.py +++ b/common/djangoapps/terrain/ui_helpers.py @@ -69,24 +69,24 @@ def css_click(css_selector, index=0, max_attempts=5, success_condition=lambda: T This function will return True if the click worked (taking into account both errors and the optional success_condition). """ - assert is_css_present(css_selector) + assert is_css_present(css_selector), "{} is not present".format(css_selector) attempt = 0 result = False - while attempt < max_attempts: + for attempt in range(max_attempts): try: world.css_find(css_selector)[index].click() if success_condition(): - result = True - break + return except WebDriverException: # Occasionally, MathJax or other JavaScript can cover up # an element temporarily. # If this happens, wait a second, then try again world.wait(1) - attempt += 1 except: - attempt += 1 - return result + pass + else: + # try once more, letting execptions raise + world.css_find(css_selector)[index].click() @world.absorb @@ -101,24 +101,24 @@ def css_check(css_selector, index=0, max_attempts=5, success_condition=lambda: T This function will return True if the check worked (taking into account both errors and the optional success_condition). """ - assert is_css_present(css_selector) + assert is_css_present(css_selector), "{} is not present".format(css_selector) attempt = 0 result = False - while attempt < max_attempts: + for attempt in range(max_attempts): try: world.css_find(css_selector)[index].check() if success_condition(): - result = True - break + return except WebDriverException: # Occasionally, MathJax or other JavaScript can cover up # an element temporarily. # If this happens, wait a second, then try again world.wait(1) - attempt += 1 except: - attempt += 1 - return result + pass + else: + # try once more, letting exceptions raise + world.css_find(css_selector)[index].check() @world.absorb @@ -143,7 +143,7 @@ def id_click(elem_id): @world.absorb def css_fill(css_selector, text): - assert is_css_present(css_selector) + assert is_css_present(css_selector), "{} is not present".format(css_selector) world.browser.find_by_css(css_selector).first.fill(text) @@ -184,7 +184,7 @@ def css_html(css_selector, index=0, max_attempts=5): @world.absorb def css_visible(css_selector): - assert is_css_present(css_selector) + assert is_css_present(css_selector), "{} is not present".format(css_selector) return world.browser.find_by_css(css_selector).visible @@ -203,11 +203,15 @@ def dialogs_closed(): def save_the_html(path='/tmp'): url = world.browser.url html = world.browser.html.encode('ascii', 'ignore') - filename = '%s.html' % quote_plus(url) - file = open('%s/%s' % (path, filename), 'w') - file.write(html) - file.close() + filename = "{path}/{name}.html".format(path=path, name=quote_plus(url)) + with open(filename, "w") as f: + f.write(html) +@world.absorb +def click_course_content(): + course_content_css = 'li.nav-course-courseware' + if world.browser.is_element_present_by_css(course_content_css): + world.css_click(course_content_css) @world.absorb def click_course_settings(): diff --git a/common/test/data/uploads/textbook.pdf b/common/test/data/uploads/textbook.pdf new file mode 100644 index 0000000000..e6e7a031ce Binary files /dev/null and b/common/test/data/uploads/textbook.pdf differ