Commit 30173c47 by Matjaz Gregoric Committed by GitHub

Merge pull request #82 from open-craft/mtyaka/assessment-item-placement

Keep items in dropped position in assessment mode.
parents cd689891 97c5c4d8
...@@ -190,6 +190,7 @@ class DragAndDropBlock(XBlock, XBlockWithSettingsMixin, ThemableXBlockMixin): ...@@ -190,6 +190,7 @@ class DragAndDropBlock(XBlock, XBlockWithSettingsMixin, ThemableXBlockMixin):
return items return items
return { return {
"mode": self.mode,
"zones": self._get_zones(), "zones": self._get_zones(),
# SDK doesn't supply url_name. # SDK doesn't supply url_name.
"url_name": getattr(self, 'url_name', ''), "url_name": getattr(self, 'url_name', ''),
...@@ -337,13 +338,19 @@ class DragAndDropBlock(XBlock, XBlockWithSettingsMixin, ThemableXBlockMixin): ...@@ -337,13 +338,19 @@ class DragAndDropBlock(XBlock, XBlockWithSettingsMixin, ThemableXBlockMixin):
'is_correct': is_correct, 'is_correct': is_correct,
}) })
return { if self.mode == self.ASSESSMENT_MODE:
# In assessment mode we don't send any feedback on drop.
result = {}
else:
result = {
'correct': is_correct, 'correct': is_correct,
'finished': self._is_finished(), 'finished': self._is_finished(),
'overall_feedback': overall_feedback, 'overall_feedback': overall_feedback,
'feedback': feedback 'feedback': feedback
} }
return result
@XBlock.json_handler @XBlock.json_handler
def reset(self, data, suffix=''): def reset(self, data, suffix=''):
self.item_state = {} self.item_state = {}
......
function DragNDropTemplates(url_name) { function DragAndDropTemplates(configuration) {
"use strict"; "use strict";
var h = virtualDom.h; var h = virtualDom.h;
// Set up a mock for gettext if it isn't available in the client runtime: // Set up a mock for gettext if it isn't available in the client runtime:
...@@ -107,13 +107,22 @@ function DragNDropTemplates(url_name) { ...@@ -107,13 +107,22 @@ function DragNDropTemplates(url_name) {
var item_content = h('div', { innerHTML: item_content_html, className: "item-content" }); var item_content = h('div', { innerHTML: item_content_html, className: "item-content" });
if (item.is_placed) { if (item.is_placed) {
// Insert information about zone in which this item has been placed // Insert information about zone in which this item has been placed
var item_description_id = url_name + '-item-' + item.value + '-description'; var item_description_id = configuration.url_name + '-item-' + item.value + '-description';
item_content.properties.attributes = { 'aria-describedby': item_description_id }; item_content.properties.attributes = { 'aria-describedby': item_description_id };
var zone_title = (zone.title || "Unknown Zone"); // This "Unknown" text should never be seen, so does not need i18n var zone_title = (zone.title || "Unknown Zone"); // This "Unknown" text should never be seen, so does not need i18n
var description_content;
if (configuration.mode === DragAndDropBlock.ASSESSMENT_MODE) {
// In assessment mode placed items will "stick" even when not in correct zone.
description_content = gettext('Placed in: {zone_title}').replace('{zone_title}', zone_title);
} else {
// In standard mode item is immediately returned back to the bank if dropped on a wrong zone,
// so all placed items are always in the correct zone.
description_content = gettext('Correctly placed in: {zone_title}').replace('{zone_title}', zone_title);
}
var item_description = h( var item_description = h(
'div', 'div',
{ id: item_description_id, className: 'sr' }, { id: item_description_id, className: 'sr' },
gettext('Correctly placed in: ') + zone_title description_content
); );
children.splice(1, 0, item_description); children.splice(1, 0, item_description);
} }
...@@ -283,14 +292,17 @@ function DragNDropTemplates(url_name) { ...@@ -283,14 +292,17 @@ function DragNDropTemplates(url_name) {
); );
}; };
DragAndDropBlock.renderView = mainTemplate; return mainTemplate;
} }
function DragAndDropBlock(runtime, element, configuration) { function DragAndDropBlock(runtime, element, configuration) {
"use strict"; "use strict";
DragNDropTemplates(configuration.url_name); DragAndDropBlock.STANDARD_MODE = 'standard';
DragAndDropBlock.ASSESSMENT_MODE = 'assessment';
var renderView = DragAndDropTemplates(configuration);
// Set up a mock for gettext if it isn't available in the client runtime: // Set up a mock for gettext if it isn't available in the client runtime:
if (!window.gettext) { window.gettext = function gettext_stub(string) { return string; }; } if (!window.gettext) { window.gettext = function gettext_stub(string) { return string; }; }
...@@ -747,10 +759,13 @@ function DragAndDropBlock(runtime, element, configuration) { ...@@ -747,10 +759,13 @@ function DragAndDropBlock(runtime, element, configuration) {
$.post(url, JSON.stringify(data), 'json') $.post(url, JSON.stringify(data), 'json')
.done(function(data){ .done(function(data){
state.items[item_id].submitting_location = false;
// In standard mode we immediately return item to the bank if dropped on wrong zone.
// In assessment mode we leave it in the chosen zone until explicit answer submission.
if (configuration.mode === DragAndDropBlock.STANDARD_MODE) {
state.last_action_correct = data.correct; state.last_action_correct = data.correct;
if (data.correct) { if (data.correct) {
state.items[item_id].correct = true; state.items[item_id].correct = true;
state.items[item_id].submitting_location = false;
} else { } else {
delete state.items[item_id]; delete state.items[item_id];
} }
...@@ -759,6 +774,7 @@ function DragAndDropBlock(runtime, element, configuration) { ...@@ -759,6 +774,7 @@ function DragAndDropBlock(runtime, element, configuration) {
state.finished = true; state.finished = true;
state.overall_feedback = data.overall_feedback; state.overall_feedback = data.overall_feedback;
} }
}
applyState(); applyState();
}) })
.fail(function (data) { .fail(function (data) {
...@@ -866,7 +882,7 @@ function DragAndDropBlock(runtime, element, configuration) { ...@@ -866,7 +882,7 @@ function DragAndDropBlock(runtime, element, configuration) {
display_reset_button: Object.keys(state.items).length > 0, display_reset_button: Object.keys(state.items).length > 0,
}; };
return DragAndDropBlock.renderView(context); return renderView(context);
}; };
/** /**
......
...@@ -409,6 +409,10 @@ msgid "ok" ...@@ -409,6 +409,10 @@ msgid "ok"
msgstr "" msgstr ""
#: public/js/drag_and_drop.js #: public/js/drag_and_drop.js
msgid "Placed in: "
msgstr ""
#: public/js/drag_and_drop.js
msgid "Correctly placed in: " msgid "Correctly placed in: "
msgstr "" msgstr ""
......
...@@ -494,6 +494,10 @@ msgid "ok" ...@@ -494,6 +494,10 @@ msgid "ok"
msgstr "ök Ⱡ'σя#" msgstr "ök Ⱡ'σя#"
#: public/js/drag_and_drop.js #: public/js/drag_and_drop.js
msgid "Placed in: "
msgstr "Pläçéd ïn: Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт αмєт, #"
#: public/js/drag_and_drop.js
msgid "Correctly placed in: " msgid "Correctly placed in: "
msgstr "Çörréçtlý pläçéd ïn: Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт αмєт, #" msgstr "Çörréçtlý pläçéd ïn: Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт αмєт, #"
......
...@@ -6,6 +6,7 @@ from mock import Mock, patch ...@@ -6,6 +6,7 @@ from mock import Mock, patch
from selenium.common.exceptions import NoSuchElementException from selenium.common.exceptions import NoSuchElementException
from selenium.webdriver import ActionChains from selenium.webdriver import ActionChains
from selenium.webdriver.common.keys import Keys from selenium.webdriver.common.keys import Keys
from selenium.webdriver.support.ui import WebDriverWait
from workbench.runtime import WorkbenchRuntime from workbench.runtime import WorkbenchRuntime
from xblockutils.resources import ResourceLoader from xblockutils.resources import ResourceLoader
...@@ -89,6 +90,19 @@ class InteractionTestBase(object): ...@@ -89,6 +90,19 @@ class InteractionTestBase(object):
'return $("div[data-uid=\'{zone_id}\']").prevAll(".zone").length'.format(zone_id=zone_id) 'return $("div[data-uid=\'{zone_id}\']").prevAll(".zone").length'.format(zone_id=zone_id)
) )
@staticmethod
def wait_until_ondrop_xhr_finished(elem):
"""
Waits until the XHR request triggered by dropping the item finishes loading.
"""
wait = WebDriverWait(elem, 2)
# While the XHR is in progress, a spinner icon is shown inside the item.
# When the spinner disappears, we can assume that the XHR request has finished.
wait.until(
lambda e: 'fa-spinner' not in e.get_attribute('innerHTML'),
u"Spinner should not be in {}".format(elem.get_attribute('innerHTML'))
)
def place_item(self, item_value, zone_id, action_key=None): def place_item(self, item_value, zone_id, action_key=None):
if action_key is None: if action_key is None:
self.drag_item_to_zone(item_value, zone_id) self.drag_item_to_zone(item_value, zone_id)
...@@ -117,7 +131,7 @@ class InteractionTestBase(object): ...@@ -117,7 +131,7 @@ class InteractionTestBase(object):
def assert_grabbed_item(self, item): def assert_grabbed_item(self, item):
self.assertEqual(item.get_attribute('aria-grabbed'), 'true') self.assertEqual(item.get_attribute('aria-grabbed'), 'true')
def assert_placed_item(self, item_value, zone_title): def assert_placed_item(self, item_value, zone_title, assessment_mode=False):
item = self._get_placed_item_by_value(item_value) item = self._get_placed_item_by_value(item_value)
self.wait_until_visible(item) self.wait_until_visible(item)
item_content = item.find_element_by_css_selector('.item-content') item_content = item.find_element_by_css_selector('.item-content')
...@@ -131,6 +145,9 @@ class InteractionTestBase(object): ...@@ -131,6 +145,9 @@ class InteractionTestBase(object):
self.assertEqual(item.get_attribute('data-drag-disabled'), 'true') self.assertEqual(item.get_attribute('data-drag-disabled'), 'true')
self.assertEqual(item_content.get_attribute('aria-describedby'), item_description_id) self.assertEqual(item_content.get_attribute('aria-describedby'), item_description_id)
self.assertEqual(item_description.get_attribute('id'), item_description_id) self.assertEqual(item_description.get_attribute('id'), item_description_id)
if assessment_mode:
self.assertEqual(item_description.text, 'Placed in: {}'.format(zone_title))
else:
self.assertEqual(item_description.text, 'Correctly placed in: {}'.format(zone_title)) self.assertEqual(item_description.text, 'Correctly placed in: {}'.format(zone_title))
def assert_reverted_item(self, item_value): def assert_reverted_item(self, item_value):
...@@ -152,16 +169,28 @@ class InteractionTestBase(object): ...@@ -152,16 +169,28 @@ class InteractionTestBase(object):
else: else:
self.fail('Reverted item should not have .sr description.') self.fail('Reverted item should not have .sr description.')
def assert_decoy_items(self, items_map): def place_decoy_items(self, items_map, action_key):
decoy_items = self._get_items_without_zone(items_map)
# Place decoy items into first available zone.
zone_id, zone_title = self.all_zones[0]
for definition in decoy_items.values():
self.place_item(definition.item_id, zone_id, action_key)
self.assert_placed_item(definition.item_id, zone_title, assessment_mode=True)
def assert_decoy_items(self, items_map, assessment_mode=False):
decoy_items = self._get_items_without_zone(items_map) decoy_items = self._get_items_without_zone(items_map)
for item_key in decoy_items: for item_key in decoy_items:
item = self._get_item_by_value(item_key) item = self._get_item_by_value(item_key)
self.assertEqual(item.get_attribute('class'), 'option fade')
self.assertEqual(item.get_attribute('aria-grabbed'), 'false') self.assertEqual(item.get_attribute('aria-grabbed'), 'false')
self.assertEqual(item.get_attribute('data-drag-disabled'), 'true') self.assertEqual(item.get_attribute('data-drag-disabled'), 'true')
if assessment_mode:
self.assertEqual(item.get_attribute('class'), 'option')
else:
self.assertEqual(item.get_attribute('class'), 'option fade')
def parameterized_item_positive_feedback_on_good_move(self, items_map, scroll_down=100, action_key=None): def parameterized_item_positive_feedback_on_good_move(
self, items_map, scroll_down=100, action_key=None, assessment_mode=False
):
popup = self._get_popup() popup = self._get_popup()
feedback_popup_content = self._get_popup_content() feedback_popup_content = self._get_popup_content()
...@@ -170,11 +199,20 @@ class InteractionTestBase(object): ...@@ -170,11 +199,20 @@ class InteractionTestBase(object):
for definition in self._get_items_with_zone(items_map).values(): for definition in self._get_items_with_zone(items_map).values():
self.place_item(definition.item_id, definition.zone_ids[0], action_key) self.place_item(definition.item_id, definition.zone_ids[0], action_key)
self.wait_until_html_in(definition.feedback_positive, feedback_popup_content) self.wait_until_ondrop_xhr_finished(self._get_item_by_value(definition.item_id))
self.assert_placed_item(definition.item_id, definition.zone_title, assessment_mode=assessment_mode)
feedback_popup_html = feedback_popup_content.get_attribute('innerHTML')
if assessment_mode:
self.assertEqual(feedback_popup_html, '')
self.assertFalse(popup.is_displayed())
else:
self.assertEqual(feedback_popup_html, definition.feedback_positive)
self.assertEqual(popup.get_attribute('class'), 'popup') self.assertEqual(popup.get_attribute('class'), 'popup')
self.assert_placed_item(definition.item_id, definition.zone_title) self.assertTrue(popup.is_displayed())
def parameterized_item_negative_feedback_on_bad_move(self, items_map, all_zones, scroll_down=100, action_key=None): def parameterized_item_negative_feedback_on_bad_move(
self, items_map, all_zones, scroll_down=100, action_key=None, assessment_mode=False
):
popup = self._get_popup() popup = self._get_popup()
feedback_popup_content = self._get_popup_content() feedback_popup_content = self._get_popup_content()
...@@ -182,15 +220,31 @@ class InteractionTestBase(object): ...@@ -182,15 +220,31 @@ class InteractionTestBase(object):
self.scroll_down(pixels=scroll_down) self.scroll_down(pixels=scroll_down)
for definition in items_map.values(): for definition in items_map.values():
for zone in all_zones: # Get first zone that is not correct for this item.
if zone in definition.zone_ids: zone_id = None
continue zone_title = None
self.place_item(definition.item_id, zone, action_key) for z_id, z_title in all_zones:
if z_id not in definition.zone_ids:
zone_id = z_id
zone_title = z_title
break
if zone_id is not None: # Some items may be placed in any zone, ignore those.
self.place_item(definition.item_id, zone_id, action_key)
if assessment_mode:
self.wait_until_ondrop_xhr_finished(self._get_item_by_value(definition.item_id))
feedback_popup_html = feedback_popup_content.get_attribute('innerHTML')
self.assertEqual(feedback_popup_html, '')
self.assertFalse(popup.is_displayed())
self.assert_placed_item(definition.item_id, zone_title, assessment_mode=True)
else:
self.wait_until_html_in(definition.feedback_negative, feedback_popup_content) self.wait_until_html_in(definition.feedback_negative, feedback_popup_content)
self.assertEqual(popup.get_attribute('class'), 'popup popup-incorrect') self.assertEqual(popup.get_attribute('class'), 'popup popup-incorrect')
self.assertTrue(popup.is_displayed())
self.assert_reverted_item(definition.item_id) self.assert_reverted_item(definition.item_id)
def parameterized_final_feedback_and_reset(self, items_map, feedback, scroll_down=100, action_key=None): def parameterized_final_feedback_and_reset(
self, items_map, feedback, scroll_down=100, action_key=None, assessment_mode=False
):
feedback_message = self._get_feedback_message() feedback_message = self._get_feedback_message()
self.assertEqual(self.get_element_html(feedback_message), feedback['intro']) # precondition check self.assertEqual(self.get_element_html(feedback_message), feedback['intro']) # precondition check
...@@ -206,12 +260,17 @@ class InteractionTestBase(object): ...@@ -206,12 +260,17 @@ class InteractionTestBase(object):
for item_key, definition in items.items(): for item_key, definition in items.items():
self.place_item(definition.item_id, definition.zone_ids[0], action_key) self.place_item(definition.item_id, definition.zone_ids[0], action_key)
self.assert_placed_item(definition.item_id, definition.zone_title) self.assert_placed_item(definition.item_id, definition.zone_title, assessment_mode=assessment_mode)
if assessment_mode:
# In assessment mode we also place decoy items onto the board,
# to make sure they are correctly reverted back to the bank on problem reset.
self.place_decoy_items(items_map, action_key)
else:
self.wait_until_html_in(feedback['final'], self._get_feedback_message()) self.wait_until_html_in(feedback['final'], self._get_feedback_message())
# Check decoy items # Check decoy items
self.assert_decoy_items(items_map) self.assert_decoy_items(items_map, assessment_mode=assessment_mode)
# Scroll "Reset problem" button into view to make sure Selenium can successfully click it # Scroll "Reset problem" button into view to make sure Selenium can successfully click it
self.scroll_down(pixels=scroll_down+150) self.scroll_down(pixels=scroll_down+150)
...@@ -298,7 +357,11 @@ class DefaultDataTestMixin(object): ...@@ -298,7 +357,11 @@ class DefaultDataTestMixin(object):
4: ItemDefinition(4, [], None, "", ITEM_NO_ZONE_FEEDBACK), 4: ItemDefinition(4, [], None, "", ITEM_NO_ZONE_FEEDBACK),
} }
all_zones = [TOP_ZONE_ID, MIDDLE_ZONE_ID, BOTTOM_ZONE_ID] all_zones = [
(TOP_ZONE_ID, TOP_ZONE_TITLE),
(MIDDLE_ZONE_ID, MIDDLE_ZONE_TITLE),
(BOTTOM_ZONE_ID, BOTTOM_ZONE_TITLE)
]
feedback = { feedback = {
"intro": START_FEEDBACK, "intro": START_FEEDBACK,
...@@ -309,21 +372,66 @@ class DefaultDataTestMixin(object): ...@@ -309,21 +372,66 @@ class DefaultDataTestMixin(object):
return "<vertical_demo><drag-and-drop-v2/></vertical_demo>" return "<vertical_demo><drag-and-drop-v2/></vertical_demo>"
class BasicInteractionTest(DefaultDataTestMixin, InteractionTestBase): class DefaultAssessmentDataTestMixin(DefaultDataTestMixin):
""" """
Testing interactions with Drag and Drop XBlock against default data. If default data changes this will break. Provides a test scenario with default options in assessment mode.
""" """
def test_item_positive_feedback_on_good_move(self): def _get_scenario_xml(self): # pylint: disable=no-self-use
self.parameterized_item_positive_feedback_on_good_move(self.items_map) return "<vertical_demo><drag-and-drop-v2 mode='assessment'/></vertical_demo>"
def test_item_negative_feedback_on_bad_move(self):
self.parameterized_item_negative_feedback_on_bad_move(self.items_map, self.all_zones)
def test_final_feedback_and_reset(self): @ddt
self.parameterized_final_feedback_and_reset(self.items_map, self.feedback) class StandardInteractionTest(DefaultDataTestMixin, InteractionTestBase, BaseIntegrationTest):
"""
Testing interactions with Drag and Drop XBlock against default data.
All interactions are tested using mouse (action_key=None) and four different keyboard action keys.
If default data changes this will break.
"""
@data(None, Keys.RETURN, Keys.SPACE, Keys.CONTROL+'m', Keys.COMMAND+'m')
def test_item_positive_feedback_on_good_move(self, action_key):
self.parameterized_item_positive_feedback_on_good_move(self.items_map, action_key=action_key)
def test_keyboard_help(self): @data(None, Keys.RETURN, Keys.SPACE, Keys.CONTROL+'m', Keys.COMMAND+'m')
self.interact_with_keyboard_help() def test_item_negative_feedback_on_bad_move(self, action_key):
self.parameterized_item_negative_feedback_on_bad_move(self.items_map, self.all_zones, action_key=action_key)
@data(None, Keys.RETURN, Keys.SPACE, Keys.CONTROL+'m', Keys.COMMAND+'m')
def test_final_feedback_and_reset(self, action_key):
self.parameterized_final_feedback_and_reset(self.items_map, self.feedback, action_key=action_key)
@data(False, True)
def test_keyboard_help(self, use_keyboard):
self.interact_with_keyboard_help(use_keyboard=use_keyboard)
@ddt
class AssessmentInteractionTest(DefaultAssessmentDataTestMixin, InteractionTestBase, BaseIntegrationTest):
"""
Testing interactions with Drag and Drop XBlock against default data in assessment mode.
All interactions are tested using mouse (action_key=None) and four different keyboard action keys.
If default data changes this will break.
"""
@data(None, Keys.RETURN, Keys.SPACE, Keys.CONTROL+'m', Keys.COMMAND+'m')
def test_item_no_feedback_on_good_move(self, action_key):
self.parameterized_item_positive_feedback_on_good_move(
self.items_map, action_key=action_key, assessment_mode=True
)
@data(None, Keys.RETURN, Keys.SPACE, Keys.CONTROL+'m', Keys.COMMAND+'m')
def test_item_no_feedback_on_bad_move(self, action_key):
self.parameterized_item_negative_feedback_on_bad_move(
self.items_map, self.all_zones, action_key=action_key, assessment_mode=True
)
@data(None, Keys.RETURN, Keys.SPACE, Keys.CONTROL+'m', Keys.COMMAND+'m')
def test_final_feedback_and_reset(self, action_key):
self.parameterized_final_feedback_and_reset(
self.items_map, self.feedback, action_key=action_key, assessment_mode=True
)
@data(False, True)
def test_keyboard_help(self, use_keyboard):
self.interact_with_keyboard_help(use_keyboard=use_keyboard)
class MultipleValidOptionsInteractionTest(DefaultDataTestMixin, InteractionTestBase, BaseIntegrationTest): class MultipleValidOptionsInteractionTest(DefaultDataTestMixin, InteractionTestBase, BaseIntegrationTest):
...@@ -417,32 +525,14 @@ class EventsFiredTest(DefaultDataTestMixin, InteractionTestBase, BaseIntegration ...@@ -417,32 +525,14 @@ class EventsFiredTest(DefaultDataTestMixin, InteractionTestBase, BaseIntegration
) )
@ddt class CustomDataInteractionTest(StandardInteractionTest):
class KeyboardInteractionTest(BasicInteractionTest, BaseIntegrationTest):
@data(Keys.RETURN, Keys.SPACE, Keys.CONTROL+'m', Keys.COMMAND+'m')
def test_item_positive_feedback_on_good_move_with_keyboard(self, action_key):
self.parameterized_item_positive_feedback_on_good_move(self.items_map, action_key=action_key)
@data(Keys.RETURN, Keys.SPACE, Keys.CONTROL+'m', Keys.COMMAND+'m')
def test_item_negative_feedback_on_bad_move_with_keyboard(self, action_key):
self.parameterized_item_negative_feedback_on_bad_move(self.items_map, self.all_zones, action_key=action_key)
@data(Keys.RETURN, Keys.SPACE, Keys.CONTROL+'m', Keys.COMMAND+'m')
def test_final_feedback_and_reset_with_keyboard(self, action_key):
self.parameterized_final_feedback_and_reset(self.items_map, self.feedback, action_key=action_key)
def test_keyboard_help(self):
self.interact_with_keyboard_help(use_keyboard=True)
class CustomDataInteractionTest(BasicInteractionTest, BaseIntegrationTest):
items_map = { items_map = {
0: ItemDefinition(0, ['zone-1'], "Zone 1", "Yes 1", "No 1"), 0: ItemDefinition(0, ['zone-1'], "Zone 1", "Yes 1", "No 1"),
1: ItemDefinition(1, ['zone-2'], "Zone 2", "Yes 2", "No 2"), 1: ItemDefinition(1, ['zone-2'], "Zone 2", "Yes 2", "No 2"),
2: ItemDefinition(2, [], None, "", "No Zone for this") 2: ItemDefinition(2, [], None, "", "No Zone for this")
} }
all_zones = ['zone-1', 'zone-2'] all_zones = [('zone-1', 'Zone 1'), ('zone-2', 'Zone 2')]
feedback = { feedback = {
"intro": "Some Intro Feed", "intro": "Some Intro Feed",
...@@ -453,14 +543,14 @@ class CustomDataInteractionTest(BasicInteractionTest, BaseIntegrationTest): ...@@ -453,14 +543,14 @@ class CustomDataInteractionTest(BasicInteractionTest, BaseIntegrationTest):
return self._get_custom_scenario_xml("data/test_data.json") return self._get_custom_scenario_xml("data/test_data.json")
class CustomHtmlDataInteractionTest(BasicInteractionTest, BaseIntegrationTest): class CustomHtmlDataInteractionTest(StandardInteractionTest):
items_map = { items_map = {
0: ItemDefinition(0, ['zone-1'], 'Zone <i>1</i>', "Yes <b>1</b>", "No <b>1</b>"), 0: ItemDefinition(0, ['zone-1'], 'Zone <i>1</i>', "Yes <b>1</b>", "No <b>1</b>"),
1: ItemDefinition(1, ['zone-2'], 'Zone <b>2</b>', "Yes <i>2</i>", "No <i>2</i>"), 1: ItemDefinition(1, ['zone-2'], 'Zone <b>2</b>', "Yes <i>2</i>", "No <i>2</i>"),
2: ItemDefinition(2, [], None, "", "No Zone for <i>X</i>") 2: ItemDefinition(2, [], None, "", "No Zone for <i>X</i>")
} }
all_zones = ['zone-1', 'zone-2'] all_zones = [('zone-1', 'Zone 1'), ('zone-2', 'Zone 2')]
feedback = { feedback = {
"intro": "Intro <i>Feed</i>", "intro": "Intro <i>Feed</i>",
...@@ -492,8 +582,8 @@ class MultipleBlocksDataInteraction(InteractionTestBase, BaseIntegrationTest): ...@@ -492,8 +582,8 @@ class MultipleBlocksDataInteraction(InteractionTestBase, BaseIntegrationTest):
} }
all_zones = { all_zones = {
'block1': ['zone-1', 'zone-2'], 'block1': [('zone-1', 'Zone 1'), ('zone-2', 'Zone 2')],
'block2': ['zone-51', 'zone-52'] 'block2': [('zone-51', 'Zone 51'), ('zone-52', 'Zone 52')]
} }
feedback = { feedback = {
......
{ {
"title": "DnDv2 XBlock with HTML instructions", "title": "DnDv2 XBlock with HTML instructions",
"mode": "standard",
"show_title": false, "show_title": false,
"problem_text": "Solve this <strong>drag-and-drop</strong> problem.", "problem_text": "Solve this <strong>drag-and-drop</strong> problem.",
"show_problem_header": false, "show_problem_header": false,
......
{ {
"title": "Drag and Drop", "title": "Drag and Drop",
"mode": "standard",
"show_title": true, "show_title": true,
"problem_text": "", "problem_text": "",
"show_problem_header": true, "show_problem_header": true,
......
{ {
"title": "DnDv2 XBlock with plain text instructions", "title": "DnDv2 XBlock with plain text instructions",
"mode": "standard",
"show_title": true, "show_title": true,
"problem_text": "Can you solve this drag-and-drop problem?", "problem_text": "Can you solve this drag-and-drop problem?",
"show_problem_header": true, "show_problem_header": true,
......
...@@ -5,6 +5,8 @@ import unittest ...@@ -5,6 +5,8 @@ import unittest
from xblockutils.resources import ResourceLoader from xblockutils.resources import ResourceLoader
from drag_and_drop_v2.drag_and_drop_v2 import DragAndDropBlock
from ..utils import make_block, TestCaseMixin from ..utils import make_block, TestCaseMixin
...@@ -90,6 +92,14 @@ class BaseDragAndDropAjaxFixture(TestCaseMixin): ...@@ -90,6 +92,14 @@ class BaseDragAndDropAjaxFixture(TestCaseMixin):
"feedback": self.FEEDBACK[item_id]["correct"] "feedback": self.FEEDBACK[item_id]["correct"]
}) })
def test_do_attempt_in_assessment_mode(self):
self.block.mode = DragAndDropBlock.ASSESSMENT_MODE
item_id, zone_id = 0, self.ZONE_1
data = {"val": item_id, "zone": zone_id, "x_percent": "33%", "y_percent": "11%"}
res = self.call_handler('do_attempt', data)
# In assessment mode, the do_attempt doesn't return any data.
self.assertEqual(res, {})
def test_grading(self): def test_grading(self):
published_grades = [] published_grades = []
......
...@@ -30,6 +30,7 @@ class BasicTests(TestCaseMixin, unittest.TestCase): ...@@ -30,6 +30,7 @@ class BasicTests(TestCaseMixin, unittest.TestCase):
zones = config.pop("zones") zones = config.pop("zones")
items = config.pop("items") items = config.pop("items")
self.assertEqual(config, { self.assertEqual(config, {
"mode": DragAndDropBlock.STANDARD_MODE,
"display_zone_borders": False, "display_zone_borders": False,
"display_zone_labels": False, "display_zone_labels": False,
"title": "Drag and Drop", "title": "Drag and Drop",
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment