diff --git a/cms/djangoapps/contentstore/features/component_settings_editor_helpers.py b/cms/djangoapps/contentstore/features/component_settings_editor_helpers.py new file mode 100644 index 0000000000..6e35d1711f --- /dev/null +++ b/cms/djangoapps/contentstore/features/component_settings_editor_helpers.py @@ -0,0 +1,80 @@ +#pylint: disable=C0111 +#pylint: disable=W0621 + +from lettuce import world +from nose.tools import assert_equal +from terrain.steps import reload_the_page + +import time + +@world.absorb +def create_component_instance(step, component_button_css, instance_id, expected_css): + click_new_component_button(step, component_button_css) + click_component_from_menu(instance_id, expected_css) + +@world.absorb +def click_new_component_button(step, component_button_css): + step.given('I have opened a new course section in Studio') + step.given('I have added a new subsection') + step.given('I expand the first section') + world.css_click('a.new-unit-item') + world.css_click(component_button_css) + +@world.absorb +def click_component_from_menu(instance_id, expected_css): + new_instance = world.browser.find_by_id(instance_id) + assert_equal(1, len(new_instance)) + # TODO: why is this sleep necessary? + time.sleep(float(1)) + new_instance[0].click() + assert_equal(1, len(world.css_find(expected_css))) + +@world.absorb +def edit_component_and_select_settings(): + world.css_click('a.edit-button') + world.css_click('#settings-mode') + +@world.absorb +def verify_setting_entry(setting, display_name, value, explicitly_set): + assert_equal(display_name, setting.find_by_css('.setting-label')[0].value) + assert_equal(value, setting.find_by_css('.setting-input')[0].value) + settingClearButton = setting.find_by_css('.setting-clear')[0] + assert_equal(explicitly_set, settingClearButton.has_class('active')) + assert_equal(not explicitly_set, settingClearButton.has_class('inactive')) + +@world.absorb +def verify_all_setting_entries(expected_entries): + settings = world.browser.find_by_css('.wrapper-comp-setting') + assert_equal(len(expected_entries), len(settings)) + for (counter, setting) in enumerate(settings): + world.verify_setting_entry(setting, expected_entries[counter][0], + expected_entries[counter][1], expected_entries[counter][2]) + +@world.absorb +def save_component_and_reopen(step): + world.css_click("a.save-button") + # We have a known issue that modifications are still shown within the edit window after cancel (though) + # they are not persisted. Refresh the browser to make sure the changes WERE persisted after Save. + reload_the_page(step) + edit_component_and_select_settings() + +@world.absorb +def cancel_component(step): + world.css_click("a.cancel-button") + # We have a known issue that modifications are still shown within the edit window after cancel (though) + # they are not persisted. Refresh the browser to make sure the changes were not persisted. + reload_the_page(step) + +@world.absorb +def revert_setting_entry(label): + get_setting_entry(label).find_by_css('.setting-clear')[0].click() + +@world.absorb +def get_setting_entry(label): + settings = world.browser.find_by_css('.wrapper-comp-setting') + for setting in settings: + if setting.find_by_css('.setting-label')[0].value == label: + return setting + return None + + diff --git a/cms/djangoapps/contentstore/features/discussion-editor.feature b/cms/djangoapps/contentstore/features/discussion-editor.feature new file mode 100644 index 0000000000..24683c3297 --- /dev/null +++ b/cms/djangoapps/contentstore/features/discussion-editor.feature @@ -0,0 +1,13 @@ +Feature: Discussion Component Editor + As a course author, I want to be able to create discussion components. + + Scenario: User can view metadata + Given I have created a Discussion Tag + And I edit and select Settings + Then I see three alphabetized settings and their expected values + + Scenario: User can modify display name + Given I have created a Discussion Tag + And I edit and select Settings + Then I can modify the display name + And my display name change is persisted on save diff --git a/cms/djangoapps/contentstore/features/discussion-editor.py b/cms/djangoapps/contentstore/features/discussion-editor.py new file mode 100644 index 0000000000..4e661a89e1 --- /dev/null +++ b/cms/djangoapps/contentstore/features/discussion-editor.py @@ -0,0 +1,18 @@ +#pylint: disable=C0111 +#pylint: disable=W0621 + +from lettuce import world, step + +@step('I have created a Discussion Tag$') +def i_created_blank_common_problem(step): + world.create_component_instance(step, '.large-discussion-icon', 'i4x://edx/templates/discussion/Discussion_Tag', + '.xmodule_DiscussionModule') + +@step('I see three alphabetized settings and their expected values$') +def i_see_only_the_display_name(step): + world.verify_all_setting_entries( + [ + ['Category', "Week 1", True], + ['Display Name', "Discussion Tag", True], + ['Subcategory', "Topic-Level Student-Visible Label", True] + ]) diff --git a/cms/djangoapps/contentstore/features/html-editor.feature b/cms/djangoapps/contentstore/features/html-editor.feature new file mode 100644 index 0000000000..6cd455d681 --- /dev/null +++ b/cms/djangoapps/contentstore/features/html-editor.feature @@ -0,0 +1,13 @@ +Feature: HTML Editor + As a course author, I want to be able to create HTML blocks. + + Scenario: User can view metadata + Given I have created a Blank HTML Page + And I edit and select Settings + Then I see only the HTML display name setting + + Scenario: User can modify display name + Given I have created a Blank HTML Page + And I edit and select Settings + Then I can modify the display name + And my display name change is persisted on save diff --git a/cms/djangoapps/contentstore/features/html-editor.py b/cms/djangoapps/contentstore/features/html-editor.py new file mode 100644 index 0000000000..564d4d986c --- /dev/null +++ b/cms/djangoapps/contentstore/features/html-editor.py @@ -0,0 +1,13 @@ +#pylint: disable=C0111 +#pylint: disable=W0621 + +from lettuce import world, step + +@step('I have created a Blank HTML Page$') +def i_created_blank_common_problem(step): + world.create_component_instance(step, '.large-html-icon', 'i4x://edx/templates/html/Blank_HTML_Page', + '.xmodule_HtmlModule') + +@step('I see only the HTML display name setting$') +def i_see_only_the_html_display_name(step): + world.verify_all_setting_entries([['Display Name', "Blank HTML Page", True]]) diff --git a/cms/djangoapps/contentstore/features/problem-editor.feature b/cms/djangoapps/contentstore/features/problem-editor.feature index 6028fa4b02..07438353d0 100644 --- a/cms/djangoapps/contentstore/features/problem-editor.feature +++ b/cms/djangoapps/contentstore/features/problem-editor.feature @@ -1,5 +1,65 @@ Feature: Problem Editor - As a course author, I want to be able to create problems. + As a course author, I want to be able to create problems and edit their settings. Scenario: User can view metadata - Given I have created a Blank Common Problem \ No newline at end of file + Given I have created a Blank Common Problem + And I edit and select Settings + Then I see five alphabetized settings and their expected values + And Edit High Level Source is not visible + + Scenario: User can modify String values + Given I have created a Blank Common Problem + And I edit and select Settings + Then I can modify the display name + And my display name change is persisted on save + + Scenario: User can revert display name to unset + Given I have created a Blank Common Problem + And I edit and select Settings + Then I can revert the display name to unset + And my display name is unset on save + + Scenario: User can select values in a Select + Given I have created a Blank Common Problem + And I edit and select Settings + Then I can select Per Student for Randomization + And my change to randomization is persisted + And I can revert to the default value for randomization + + Scenario: User can modify float input values + Given I have created a Blank Common Problem + And I edit and select Settings + Then I can set the weight to 3.5 + And my change to weight is persisted + And I can revert to the default value of unset for weight + + Scenario: User cannot type letters in float number field + Given I have created a Blank Common Problem + And I edit and select Settings + Then if I set the weight to abc, it remains unset + + Scenario: User cannot type decimal values integer number field + Given I have created a Blank Common Problem + And I edit and select Settings + Then if I set the max attempts to 2.34, the max attempts are persisted as 234 + + Scenario: User cannot type out of range values in an integer number field + Given I have created a Blank Common Problem + And I edit and select Settings + Then if I set the max attempts to -3, the max attempts are persisted as 1 + + + Scenario: Settings changes are not saved on Cancel + Given I have created a Blank Common Problem + And I edit and select Settings + Then I can set the weight to 3.5 + And I can modify the display name + Then If I press Cancel my changes are not persisted + + Scenario: Edit High Level source is available for LaTeX problem + Given I have created a LaTeX Problem + And I edit and select Settings + Then Edit High Level Source is visible + +# Future tests to add-- +# Check that code is shown in editor (type code, Save, check that code appears when re-edited). diff --git a/cms/djangoapps/contentstore/features/problem-editor.py b/cms/djangoapps/contentstore/features/problem-editor.py index ef3f73197d..ade1f07b2d 100644 --- a/cms/djangoapps/contentstore/features/problem-editor.py +++ b/cms/djangoapps/contentstore/features/problem-editor.py @@ -2,16 +2,146 @@ #pylint: disable=W0621 from lettuce import world, step -from nose.tools import assert_true, assert_equal, assert_in -from terrain.steps import reload_the_page +from nose.tools import assert_equal +DISPLAY_NAME = "Display Name" +MAXIMUM_ATTEMPTS="Maximum Attempts" +PROBLEM_WEIGHT="Problem Weight" +RANDOMIZATION='Randomization' +SHOW_ANSWER="Show Answer" ############### ACTIONS #################### @step('I have created a Blank Common Problem$') def i_created_blank_common_problem(step): - step.given('I have opened a new course section in Studio') - step.given('I have added a new subsection') - step.given('I expand the first section') - world.css_click('a.new-unit-item') - world.css_click('.large-problem-icon') - world.css_click('#i4x://edx/templates/problem/Blank_Common_Problem') + world.create_component_instance(step, '.large-problem-icon', 'i4x://edx/templates/problem/Blank_Common_Problem', + '.xmodule_CapaModule') + +@step('I edit and select Settings$') +def i_edit_and_select_settings(step): + world.edit_component_and_select_settings() + +@step('I see five alphabetized settings and their expected values$') +def i_see_five_settings_with_values(step): + world.verify_all_setting_entries( + [ + [DISPLAY_NAME, "Blank Common Problem", True], + [MAXIMUM_ATTEMPTS, "", False], + [PROBLEM_WEIGHT, "", False], + [RANDOMIZATION, "Never", True], + [SHOW_ANSWER, "Finished", True] + ]) + +@step('I can modify the display name') +def i_can_modify_the_display_name(step): + world.get_setting_entry(DISPLAY_NAME).find_by_css('.setting-input')[0].fill('modified') + verify_modified_display_name() + +@step('my display name change is persisted on save') +def my_display_name_change_is_persisted_on_save(step): + world.save_component_and_reopen(step) + verify_modified_display_name() + +@step('I can revert the display name to unset') +def can_revert_display_name_to_unset(step): + world.revert_setting_entry(DISPLAY_NAME) + verify_unset_display_name() + +@step('my display name is unset on save') +def my_display_name_is_persisted_on_save(step): + world.save_component_and_reopen(step) + verify_unset_display_name() + +@step('I can select Per Student for Randomization') +def i_can_select_per_student_for_randomization(step): + world.browser.select(RANDOMIZATION, "Per Student") + verify_modified_randomization() + +@step('my change to randomization is persisted') +def my_change_to_randomization_is_persisted(step): + world.save_component_and_reopen(step) + verify_modified_randomization() + +@step('I can revert to the default value for randomization') +def i_can_revert_to_default_for_randomization(step): + world.revert_setting_entry(RANDOMIZATION) + world.save_component_and_reopen(step) + world.verify_setting_entry(world.get_setting_entry(RANDOMIZATION), RANDOMIZATION, "Always", False) + +@step('I can set the weight to 3.5') +def i_can_set_weight_to_3_5(step): + world.get_setting_entry(PROBLEM_WEIGHT).find_by_css('.setting-input')[0].fill('3.5') + verify_modified_weight() + +@step('my change to weight is persisted') +def my_change_to_randomization_is_persisted(step): + world.save_component_and_reopen(step) + verify_modified_weight() + +@step('I can revert to the default value of unset for weight') +def i_can_revert_to_default_for_randomization(step): + world.revert_setting_entry(PROBLEM_WEIGHT) + world.save_component_and_reopen(step) + world.verify_setting_entry(world.get_setting_entry(PROBLEM_WEIGHT), PROBLEM_WEIGHT, "", False) + +@step('if I set the weight to abc, it remains unset') +def set_the_weight_to_abc(step): + world.get_setting_entry(PROBLEM_WEIGHT).find_by_css('.setting-input')[0].fill('abc') + # We show the clear button immediately on type, hence the "True" here. + world.verify_setting_entry(world.get_setting_entry(PROBLEM_WEIGHT), PROBLEM_WEIGHT, "", True) + world.save_component_and_reopen(step) + # But no change was actually ever sent to the model, so on reopen, explicitly_set is False + world.verify_setting_entry(world.get_setting_entry(PROBLEM_WEIGHT), PROBLEM_WEIGHT, "", False) + +@step('if I set the max attempts to 2.34, the max attempts are persisted as 234') +def set_the_weight_to_abc(step): + world.get_setting_entry(MAXIMUM_ATTEMPTS).find_by_css('.setting-input')[0].fill('2.34') + world.verify_setting_entry(world.get_setting_entry(MAXIMUM_ATTEMPTS), MAXIMUM_ATTEMPTS, "234", True) + world.save_component_and_reopen(step) + world.verify_setting_entry(world.get_setting_entry(MAXIMUM_ATTEMPTS), MAXIMUM_ATTEMPTS, "234", True) + +@step('I set the max attempts to -3, the max attempts are persisted as 1') +def set_max_attempts_to_neg_3(step): + world.get_setting_entry(MAXIMUM_ATTEMPTS).find_by_css('.setting-input')[0].fill('-3') + world.verify_setting_entry(world.get_setting_entry(MAXIMUM_ATTEMPTS), MAXIMUM_ATTEMPTS, "-3", True) + world.save_component_and_reopen(step) + world.verify_setting_entry(world.get_setting_entry(MAXIMUM_ATTEMPTS), MAXIMUM_ATTEMPTS, "1", True) + +@step('Edit High Level Source is not visible') +def edit_high_level_source_not_visible(step): + verify_high_level_source(step, False) + +@step('Edit High Level Source is visible') +def edit_high_level_source_visible(step): + verify_high_level_source(step, True) + +@step('If I press Cancel my changes are not persisted') +def cancel_does_not_save_changes(step): + world.cancel_component(step) + step.given("I edit and select Settings") + step.given("I see five alphabetized settings and their expected values") + +@step('I have created a LaTeX Problem') +def create_latex_problem(step): + world.click_new_component_button(step, '.large-problem-icon') + # Go to advanced tab (waiting for the tab to be visible) + world.css_find('#ui-id-2') + world.css_click('#ui-id-2') + world.click_component_from_menu("i4x://edx/templates/problem/Problem_Written_in_LaTeX", '.xmodule_CapaModule') + +def verify_high_level_source(step, visible): + assert_equal(visible, world.is_css_present('.launch-latex-compiler')) + world.cancel_component(step) + assert_equal(visible, world.is_css_present('.upload-button')) + +def verify_modified_weight(): + world.verify_setting_entry(world.get_setting_entry(PROBLEM_WEIGHT), PROBLEM_WEIGHT, "3.5", True) + +def verify_modified_randomization(): + world.verify_setting_entry(world.get_setting_entry(RANDOMIZATION), RANDOMIZATION, "Per Student", True) + +def verify_modified_display_name(): + world.verify_setting_entry(world.get_setting_entry(DISPLAY_NAME), DISPLAY_NAME, 'modified', True) + +def verify_unset_display_name(): + world.verify_setting_entry(world.get_setting_entry(DISPLAY_NAME), DISPLAY_NAME, '', False) + diff --git a/cms/djangoapps/contentstore/features/video-editor.feature b/cms/djangoapps/contentstore/features/video-editor.feature new file mode 100644 index 0000000000..4c2a460042 --- /dev/null +++ b/cms/djangoapps/contentstore/features/video-editor.feature @@ -0,0 +1,13 @@ +Feature: Video Component Editor + As a course author, I want to be able to create video components. + + Scenario: User can view metadata + Given I have created a Video component + And I edit and select Settings + Then I see only the Video display name setting + + Scenario: User can modify display name + Given I have created a Video component + And I edit and select Settings + Then I can modify the display name + And my display name change is persisted on save diff --git a/cms/djangoapps/contentstore/features/video-editor.py b/cms/djangoapps/contentstore/features/video-editor.py new file mode 100644 index 0000000000..2bd2733e59 --- /dev/null +++ b/cms/djangoapps/contentstore/features/video-editor.py @@ -0,0 +1,13 @@ +#pylint: disable=C0111 +#pylint: disable=W0621 + +from lettuce import world, step + +@step('I have created a Video component$') +def i_created_a_video_component(step): + world.create_component_instance(step, '.large-video-icon', 'i4x://edx/templates/video/default', + '.xmodule_VideoModule') + +@step('I see only the video display name setting$') +def i_see_only_the_video_display_name(step): + world.verify_all_setting_entries([['Display Name', "default", True]]) diff --git a/cms/static/js/views/metadata_editor_view.js b/cms/static/js/views/metadata_editor_view.js index fa2df079a9..e1b380ef2a 100644 --- a/cms/static/js/views/metadata_editor_view.js +++ b/cms/static/js/views/metadata_editor_view.js @@ -235,6 +235,7 @@ CMS.Views.Metadata.Number = CMS.Views.Metadata.AbstractEditor.extend({ value = this.min; } this.setValueInEditor(value); + this.updateModel(); } });