From 2a92a14054143f5f79ce11121d189c455326bbfe Mon Sep 17 00:00:00 2001 From: David Baumgold Date: Mon, 1 Jul 2013 14:31:40 -0400 Subject: [PATCH] Wrote simple lettuce tests for pdf textbooks --- .../contentstore/features/textbooks.feature | 20 +++++ .../contentstore/features/textbooks.py | 70 ++++++++++++++++++ cms/djangoapps/contentstore/views/course.py | 10 ++- cms/static/js/views/textbook.js | 8 +- common/djangoapps/terrain/ui_helpers.py | 44 ++++++----- common/test/data/uploads/textbook.pdf | Bin 0 -> 9478 bytes 6 files changed, 128 insertions(+), 24 deletions(-) create mode 100644 cms/djangoapps/contentstore/features/textbooks.feature create mode 100644 cms/djangoapps/contentstore/features/textbooks.py create mode 100644 common/test/data/uploads/textbook.pdf 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 0000000000000000000000000000000000000000..e6e7a031ce590307d2233cc3f1176ac2309cde27 GIT binary patch literal 9478 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 qAP`&tDr^Y>T0)Sg=l}W2FLKb;4f$Vqy@