diff --git a/cms/djangoapps/contentstore/features/advanced-settings.feature b/cms/djangoapps/contentstore/features/advanced-settings.feature index 779d44e4b2..af97709ad0 100644 --- a/cms/djangoapps/contentstore/features/advanced-settings.feature +++ b/cms/djangoapps/contentstore/features/advanced-settings.feature @@ -2,53 +2,41 @@ Feature: Advanced (manual) course policy In order to specify course policy settings for which no custom user interface exists I want to be able to manually enter JSON key/value pairs - Scenario: A course author sees only display_name on a newly created course + Scenario: A course author sees default advanced settings Given I have opened a new course in Studio When I select the Advanced Settings - Then I see only the display name + Then I see default advanced settings - @skip-phantom - Scenario: Test if there are no policy settings without existing UI controls + Scenario: Add new entries, and they appear alphabetically after save Given I am on the Advanced Course Settings page in Studio - When I delete the display name - Then there are no advanced policy settings - And I reload the page - Then there are no advanced policy settings - - @skip-phantom - Scenario: Test cancel editing key name - Given I am on the Advanced Course Settings page in Studio - When I edit the name of a policy key - And I press the "Cancel" notification button - Then the policy key name is unchanged - - Scenario: Test editing key name - Given I am on the Advanced Course Settings page in Studio - When I edit the name of a policy key - And I press the "Save" notification button - Then the policy key name is changed + Then the settings are alphabetized Scenario: Test cancel editing key value Given I am on the Advanced Course Settings page in Studio When I edit the value of a policy key And I press the "Cancel" notification button Then the policy key value is unchanged + And I reload the page + Then the policy key value is unchanged - @skip-phantom Scenario: Test editing key value Given I am on the Advanced Course Settings page in Studio When I edit the value of a policy key And I press the "Save" notification button Then the policy key value is changed - - Scenario: Add new entries, and they appear alphabetically after save - Given I am on the Advanced Course Settings page in Studio - When I create New Entries - Then they are alphabetized And I reload the page - Then they are alphabetized + Then the policy key value is changed Scenario: Test how multi-line input appears Given I am on the Advanced Course Settings page in Studio - When I create a JSON object + When I create a JSON object as a value Then it is displayed as formatted + And I reload the page + Then it is displayed as formatted + + Scenario: Test automatic quoting of non-JSON values + Given I am on the Advanced Course Settings page in Studio + When I create a non-JSON value not in quotes + Then it is displayed as a string + And I reload the page + Then it is displayed as a string diff --git a/cms/djangoapps/contentstore/features/advanced-settings.py b/cms/djangoapps/contentstore/features/advanced-settings.py index 1024579b45..7e86e94a31 100644 --- a/cms/djangoapps/contentstore/features/advanced-settings.py +++ b/cms/djangoapps/contentstore/features/advanced-settings.py @@ -1,6 +1,7 @@ from lettuce import world, step from common import * import time +from terrain.steps import reload_the_page from selenium.common.exceptions import WebDriverException from selenium.webdriver.support import expected_conditions as EC @@ -11,6 +12,10 @@ http://selenium.googlecode.com/svn/trunk/docs/api/py/webdriver/selenium.webdrive """ from selenium.webdriver.common.keys import Keys +KEY_CSS = '.key input.policy-key' +VALUE_CSS = 'textarea.json' +DISPLAY_NAME_KEY = "display_name" +DISPLAY_NAME_VALUE = '"Robot Super Course"' ############### ACTIONS #################### @step('I select the Advanced Settings$') @@ -20,7 +25,6 @@ def i_select_advanced_settings(step): css_click(expand_icon_css) link_css = 'li.nav-course-settings-advanced a' css_click(link_css) - # world.browser.click_link_by_text('Advanced Settings') @step('I am on the Advanced Course Settings page in Studio$') @@ -29,35 +33,27 @@ def i_am_on_advanced_course_settings(step): step.given('I select the Advanced Settings') -# TODO: this is copied from terrain's step.py. Need to figure out how to share that code. -@step('I reload the page$') -def reload_the_page(step): - world.browser.reload() - - -@step(u'I edit the name of a policy key$') -def edit_the_name_of_a_policy_key(step): - policy_key_css = 'input.policy-key' - e = css_find(policy_key_css).first - e.type('_new') - - @step(u'I press the "([^"]*)" notification button$') def press_the_notification_button(step, name): def is_visible(driver): - return EC.visibility_of_element_located((By.CSS_SELECTOR,css,)) - def is_invisible(driver): - return EC.invisibility_of_element_located((By.CSS_SELECTOR,css,)) + return EC.visibility_of_element_located((By.CSS_SELECTOR, css,)) + + # def is_invisible(driver): + # return EC.invisibility_of_element_located((By.CSS_SELECTOR,css,)) css = 'a.%s-button' % name.lower() wait_for(is_visible) + time.sleep(float(1)) + css_click_at(css) + +# is_invisible is not returning a boolean, not working +# try: +# css_click_at(css) +# wait_for(is_invisible) +# except WebDriverException, e: +# css_click_at(css) +# wait_for(is_invisible) - try: - css_click_at(css) - wait_for(is_invisible) - except WebDriverException, e: - css_click_at(css) - wait_for(is_invisible) @step(u'I edit the value of a policy key$') def edit_the_value_of_a_policy_key(step): @@ -65,133 +61,86 @@ def edit_the_value_of_a_policy_key(step): It is hard to figure out how to get into the CodeMirror area, so cheat and do it from the policy key field :) """ - policy_key_css = 'input.policy-key' - e = css_find(policy_key_css).first + e = css_find(KEY_CSS)[get_index_of(DISPLAY_NAME_KEY)] e._element.send_keys(Keys.TAB, Keys.END, Keys.ARROW_LEFT, ' ', 'X') -@step('I delete the display name$') -def delete_the_display_name(step): - delete_entry(0) - click_save() - - -@step('create New Entries$') -def create_new_entries(step): - create_entry("z", "apple") - create_entry("a", "zebra") - click_save() - - -@step('I create a JSON object$') +@step('I create a JSON object as a value$') def create_JSON_object(step): - create_entry("json", '{"key": "value", "key_2": "value_2"}') - click_save() + change_display_name_value(step, '{"key": "value", "key_2": "value_2"}') + + +@step('I create a non-JSON value not in quotes$') +def create_value_not_in_quotes(step): + change_display_name_value(step, 'quote me') ############### RESULTS #################### -@step('I see only the display name$') -def i_see_only_display_name(step): - assert_policy_entries(["display_name"], ['"Robot Super Course"']) +@step('I see default advanced settings$') +def i_see_default_advanced_settings(step): + # Test only a few of the existing properties (there are around 34 of them) + assert_policy_entries( + ["advanced_modules", DISPLAY_NAME_KEY, "show_calculator"], ["[]", DISPLAY_NAME_VALUE, "false"]) -@step('there are no advanced policy settings$') -def no_policy_settings(step): - keys_css = 'input.policy-key' - val_css = 'textarea.json' - k = world.browser.is_element_not_present_by_css(keys_css, 5) - v = world.browser.is_element_not_present_by_css(val_css, 5) - assert_true(k) - assert_true(v) - - -@step('they are alphabetized$') +@step('the settings are alphabetized$') def they_are_alphabetized(step): - assert_policy_entries(["a", "display_name", "z"], ['"zebra"', '"Robot Super Course"', '"apple"']) + key_elements = css_find(KEY_CSS) + all_keys = [] + for key in key_elements: + all_keys.append(key.value) + + assert_equal(sorted(all_keys), all_keys, "policy keys were not sorted") @step('it is displayed as formatted$') def it_is_formatted(step): - assert_policy_entries(["display_name", "json"], ['"Robot Super Course"', '{\n "key": "value",\n "key_2": "value_2"\n}']) + assert_policy_entries([DISPLAY_NAME_KEY], ['{\n "key": "value",\n "key_2": "value_2"\n}']) -@step(u'the policy key name is unchanged$') -def the_policy_key_name_is_unchanged(step): - policy_key_css = 'input.policy-key' - val = css_find(policy_key_css).first.value - assert_equal(val, 'display_name') - - -@step(u'the policy key name is changed$') -def the_policy_key_name_is_changed(step): - policy_key_css = 'input.policy-key' - val = css_find(policy_key_css).first.value - assert_equal(val, 'display_name_new') +@step('it is displayed as a string') +def it_is_formatted(step): + assert_policy_entries([DISPLAY_NAME_KEY], ['"quote me"']) @step(u'the policy key value is unchanged$') def the_policy_key_value_is_unchanged(step): - policy_value_css = 'li.course-advanced-policy-list-item div.value textarea' - val = css_find(policy_value_css).first.value - assert_equal(val, '"Robot Super Course"') + assert_equal(get_display_name_value(), DISPLAY_NAME_VALUE) @step(u'the policy key value is changed$') -def the_policy_key_value_is_unchanged(step): - policy_value_css = 'li.course-advanced-policy-list-item div.value textarea' - val = css_find(policy_value_css).first.value - assert_equal(val, '"Robot Super Course X"') +def the_policy_key_value_is_changed(step): + assert_equal(get_display_name_value(), '"Robot Super Course X"') ############# HELPERS ############### -def create_entry(key, value): - # Scroll down the page so the button is visible - world.scroll_to_bottom() - css_click_at('a.new-advanced-policy-item', 10, 10) - new_key_css = 'div#__new_advanced_key__ input' - new_key_element = css_find(new_key_css).first - new_key_element.fill(key) -# For some reason have to get the instance for each command -# (get error that it is no longer attached to the DOM) -# Have to do all this because Selenium fill does not remove existing text - new_value_css = 'div.CodeMirror textarea' - css_find(new_value_css).last.fill("") - css_find(new_value_css).last._element.send_keys(Keys.DELETE, Keys.DELETE) - css_find(new_value_css).last.fill(value) - # Add in a TAB key press because intermittently on ubuntu the - # last character of "value" above was not getting typed in - css_find(new_value_css).last._element.send_keys(Keys.TAB) - - -def delete_entry(index): - """ - Delete the nth entry where index is 0-based - """ - css = 'a.delete-button' - assert_true(world.browser.is_element_present_by_css(css, 5)) - delete_buttons = css_find(css) - assert_true(len(delete_buttons) > index, "no delete button exists for entry " + str(index)) - delete_buttons[index].click() - - def assert_policy_entries(expected_keys, expected_values): - assert_entries('.key input.policy-key', expected_keys) - assert_entries('textarea.json', expected_values) + for counter in range(len(expected_keys)): + index = get_index_of(expected_keys[counter]) + assert_false(index == -1, "Could not find key: " + expected_keys[counter]) + assert_equal(expected_values[counter], css_find(VALUE_CSS)[index].value, "value is incorrect") -def assert_entries(css, expected_values): - webElements = css_find(css) - assert_equal(len(expected_values), len(webElements)) -# Sometimes get stale reference if I hold on to the array of elements - for counter in range(len(expected_values)): - assert_equal(expected_values[counter], css_find(css)[counter].value) +def get_index_of(expected_key): + for counter in range(len(css_find(KEY_CSS))): + # Sometimes get stale reference if I hold on to the array of elements + key = css_find(KEY_CSS)[counter].value + if key == expected_key: + return counter + + return -1 -def click_save(): - css = "a.save-button" - css_click_at(css) +def get_display_name_value(): + index = get_index_of(DISPLAY_NAME_KEY) + return css_find(VALUE_CSS)[index].value -def fill_last_field(value): - newValue = css_find('#__new_advanced_key__ input').first - newValue.fill(value) +def change_display_name_value(step, new_value): + e = css_find(KEY_CSS)[get_index_of(DISPLAY_NAME_KEY)] + display_name = get_display_name_value() + for count in range(len(display_name)): + e._element.send_keys(Keys.TAB, Keys.END, Keys.BACK_SPACE) + # Must delete "" before typing the JSON value + e._element.send_keys(Keys.TAB, Keys.END, Keys.BACK_SPACE, Keys.BACK_SPACE, new_value) + press_the_notification_button(step, "Save") \ No newline at end of file diff --git a/cms/djangoapps/contentstore/views.py b/cms/djangoapps/contentstore/views.py index d9fc562f17..2d9a1eaf61 100644 --- a/cms/djangoapps/contentstore/views.py +++ b/cms/djangoapps/contentstore/views.py @@ -1228,7 +1228,6 @@ def course_config_advanced_page(request, org, course, name): return render_to_response('settings_advanced.html', { 'context_course': course_module, 'course_location' : location, - 'advanced_blacklist' : json.dumps(CourseMetadata.FILTERED_LIST), 'advanced_dict' : json.dumps(CourseMetadata.fetch(location)), }) diff --git a/cms/djangoapps/models/settings/course_metadata.py b/cms/djangoapps/models/settings/course_metadata.py index 3972751082..ed11a6d7a4 100644 --- a/cms/djangoapps/models/settings/course_metadata.py +++ b/cms/djangoapps/models/settings/course_metadata.py @@ -10,8 +10,7 @@ class CourseMetadata(object): For CRUD operations on metadata fields which do not have specific editors on the other pages including any user generated ones. The objects have no predefined attrs but instead are obj encodings of the editable metadata. ''' - # __new_advanced_key__ is used by client not server; so, could argue against it being here - FILTERED_LIST = XModuleDescriptor.system_metadata_fields + ['start', 'end', 'enrollment_start', 'enrollment_end', 'tabs', 'graceperiod', '__new_advanced_key__'] + FILTERED_LIST = XModuleDescriptor.system_metadata_fields + ['start', 'end', 'enrollment_start', 'enrollment_end', 'tabs', 'graceperiod'] @classmethod def fetch(cls, course_location): diff --git a/cms/static/client_templates/advanced_entry.html b/cms/static/client_templates/advanced_entry.html index 0312fdd344..6be22e2116 100644 --- a/cms/static/client_templates/advanced_entry.html +++ b/cms/static/client_templates/advanced_entry.html @@ -1,16 +1,11 @@
Warning: Add only manual policy data that you are familiar - with.
+Warning: Do not modify these policies unless you are familiar with their purpose.