diff --git a/drag_and_drop_v2/drag_and_drop_v2.py b/drag_and_drop_v2/drag_and_drop_v2.py
index a2bcf5ccb..4c88ed1a7 100644
--- a/drag_and_drop_v2/drag_and_drop_v2.py
+++ b/drag_and_drop_v2/drag_and_drop_v2.py
@@ -127,6 +127,12 @@ class DragAndDropBlock(XBlock, XBlockWithSettingsMixin, ThemableXBlockMixin):
default={},
)
+ num_attempts = Integer(
+ help=_("Number of attempts learner used"),
+ scope=Scope.user_state,
+ default=0
+ )
+
completed = Boolean(
help=_("Indicates whether a learner has completed the problem at least once"),
scope=Scope.user_state,
@@ -191,6 +197,7 @@ def items_without_answers():
return {
"mode": self.mode,
+ "max_attempts": self.max_attempts,
"zones": self._get_zones(),
# SDK doesn't supply url_name.
"url_name": getattr(self, 'url_name', ''),
@@ -433,6 +440,7 @@ def _get_user_state(self):
return {
'items': item_state,
'finished': is_finished,
+ 'num_attempts': self.num_attempts,
'overall_feedback': self.data['feedback']['finish' if is_finished else 'start'],
}
diff --git a/drag_and_drop_v2/public/css/drag_and_drop.css b/drag_and_drop_v2/public/css/drag_and_drop.css
index ed7217adb..06de5039b 100644
--- a/drag_and_drop_v2/public/css/drag_and_drop.css
+++ b/drag_and_drop_v2/public/css/drag_and_drop.css
@@ -326,13 +326,174 @@
outline: 2px solid white;
}
-/*** KEYBOARD HELP ***/
+/*** edX pattern library components ***/
+
+/* reset stock edx-platform button styles */
+.xblock--drag-and-drop button,
+.xblock--drag-and-drop button:hover,
+.xblock--drag-and-drop button.is-hovered,
+.xblock--drag-and-drop button:focus,
+.xblock--drag-and-drop button.is-focused,
+.xblock--drag-and-drop button:active,
+.xblock--drag-and-drop button.is-active {
+ box-shadow: none;
+ text-shadow: none;
+ background-image: none;
+}
+
+.xblock--drag-and-drop .btn-default,
+.xblock--drag-and-drop .btn-brand {
+ display: inline-block;
+ font-weight: normal;
+ background: #0079bc;
+ color: #fcfcfc;
+ -webkit-transition: color 0.125s ease-in-out 0s, border-color 0.125s ease-in-out 0s, background 0.125s ease-in-out 0s, box-shadow 0.125s ease-in-out 0s;
+ -moz-transition: color 0.125s ease-in-out 0s, border-color 0.125s ease-in-out 0s, background 0.125s ease-in-out 0s, box-shadow 0.125s ease-in-out 0s;
+ transition: color 0.125s ease-in-out 0s, border-color 0.125s ease-in-out 0s, background 0.125s ease-in-out 0s, box-shadow 0.125s ease-in-out 0s;
+ border-radius: 3px;
+ border: 1px solid #0079bc;
+ padding: 0.625em 1.25em;
+ font-size: 1em;
+}
+
+.xblock--drag-and-drop .btn-default {
+ border-color: transparent;
+ background: transparent;
+ color: #0074b4
+}
+
+.xblock--drag-and-drop .btn-brand {
+ border-color: #0075b4;
+ background: white;
+ color: #0075b4;
+}
+
+.xblock--drag-and-drop .btn-small {
+ padding: 0.625em 0.625em;
+ font-size: 0.875em;
+}
+
+.xblock--drag-and-drop .btn-default:hover,
+.xblock--drag-and-drop .btn-default.is-hovered,
+.xblock--drag-and-drop .btn-default:focus,
+.xblock--drag-and-drop .btn-default.is-focused {
+ background-color: transparent;
+ border: 1px solid #0074b4;
+ color: #0074b4
+}
+
+.xblock--drag-and-drop .btn-default:active,
+.xblock--drag-and-drop .btn-default.is-pressed,
+.xblock--drag-and-drop .btn-default.is-active {
+ border-color: #0074b4;
+ color: #0074b4
+}
+
+.xblock--drag-and-drop .btn-default:disabled,
+.xblock--drag-and-drop .btn-default.is-disabled {
+ color: #6b6969
+}
+
+.xblock--drag-and-drop .btn-brand {
+ border-color: #0074b4;
+ background: #0074b4;
+ color: #fcfcfc
+}
+
+.xblock--drag-and-drop .btn-brand:hover,
+.xblock--drag-and-drop .btn-brand.is-hovered,
+.xblock--drag-and-drop .btn-brand:focus,
+.xblock--drag-and-drop .btn-brand.is-focused {
+ border-color: #008bd8;
+ background-color: #008bd8;
+ color: #fcfcfc
+}
+
+.xblock--drag-and-drop .btn-brand:active,
+.xblock--drag-and-drop .btn-brand.is-pressed,
+.xblock--drag-and-drop .btn-brand.is-active {
+ border-color: #0074b4;
+ background: #0074b4
+}
+
+.xblock--drag-and-drop .btn-brand:disabled,
+.xblock--drag-and-drop .btn-brand.is-disabled {
+ border-color: #d2d0d0;
+ background: #f2f3f3;
+ color: #676666
+}
+
+
+/*** ACTIONS TOOLBAR ***/
+
+.xblock--drag-and-drop .actions-toolbar {
+ min-height: 3.75em;
+ width: auto;
+ position: relative;
+}
+
+.xblock--drag-and-drop .actions-toolbar .action-toolbar-item {
+ display: inline-block;
+ margin: 10px 0;
+}
+
+.xblock--drag-and-drop .attempts-used {
+ margin-left: 0.675em;
+ font-size: 0.875em;
+ color: #666;
+}
+
+.xblock--drag-and-drop .actions-toolbar .action-toolbar-item.sidebar-buttons {
+ text-align: left;
+ display: block;
+}
+
+@media (min-width: 768px) {
+ .xblock--drag-and-drop .actions-toolbar .action-toolbar-item.sidebar-buttons {
+ float: right;
+ margin: 0;
+ padding-right: -5px;
+ }
+}
+
+.xblock--drag-and-drop .sidebar-buttons .sidebar-button-wrapper {
+ border-right: 1px solid #ddd;
+ border-collapse: collapse;
+ padding: 0 5px;
+ display: inline-block;
+ height: 100%;
+}
+
+.xblock--drag-and-drop .sidebar-buttons .sidebar-button-wrapper:first-child {
+ padding-left: 0;
+}
+
+.xblock--drag-and-drop .sidebar-buttons .sidebar-button-wrapper:last-child {
+ border-right: none;
+ padding-right: 0;
+}
+
+.xblock--drag-and-drop .sidebar-buttons .btn-brand {
+ display: inline-block;
+ padding: 3px 5px;
+}
.xblock--drag-and-drop .keyboard-help {
margin-top: 3px;
margin-bottom: 6px;
}
+.xblock--drag-and-drop .btn-icon {
+ display: block;
+}
+
+.xblock--drag-and-drop .reset-button {
+ margin-top: 3px;
+}
+
+/*** ACTIONS TOOLBAR END ***/
+
+/*** KEYBOARD HELP ***/
.xblock--drag-and-drop .keyboard-help-dialog {
position: fixed;
left: 50%;
@@ -382,26 +543,7 @@
margin-left: 2%;
}
-.xblock--drag-and-drop .link-button {
- padding: 0;
- margin: 0;
- cursor: pointer;
- color: #2d74b3;
- font-weight: normal;
- font-size: 12pt;
- background: none;
- box-shadow: none;
- border: none;
-}
-
-.xblock--drag-and-drop .reset-button {
- float: right;
- margin-top: 3px;
-}
-
-.xblock--drag-and-drop .link-button:focus {
- outline: 2px solid #2d74b3;
-}
+/*** KEYBOARD HELP END ***/
/* Make sure screen-reader content is hidden in the workbench: */
.xblock--drag-and-drop .sr {
diff --git a/drag_and_drop_v2/public/js/drag_and_drop.js b/drag_and_drop_v2/public/js/drag_and_drop.js
index 18f48cf7b..10d85bf67 100644
--- a/drag_and_drop_v2/public/js/drag_and_drop.js
+++ b/drag_and_drop_v2/public/js/drag_and_drop.js
@@ -228,51 +228,87 @@ function DragAndDropTemplates(configuration) {
var feedbackTemplate = function(ctx) {
var feedback_display = ctx.feedback_html ? 'block' : 'none';
- var reset_button_display = ctx.display_reset_button ? 'block' : 'none';
var properties = { attributes: { 'aria-live': 'polite' } };
return (
h('section.feedback', properties, [
- h(
- 'button.reset-button.unbutton.link-button',
- { style: { display: reset_button_display }, attributes: { tabindex: 0 }, 'aria-live': 'off'},
- gettext('Reset problem')
- ),
h('h3.title1', { style: { display: feedback_display } }, gettext('Feedback')),
h('p.message', { style: { display: feedback_display }, innerHTML: ctx.feedback_html })
])
);
};
- var keyboardHelpTemplate = function(ctx) {
- var dialog_attributes = { role: 'dialog', 'aria-labelledby': 'modal-window-title' };
- var dialog_style = {};
+ var keyboardHelpPopupTemplate = function(ctx) {
+ var labelledby_id = 'modal-window-title-'+configuration.url_name;
return (
- h('section.keyboard-help', [
- h('button.keyboard-help-button.unbutton.link-button', { attributes: { tabindex: 0 } }, gettext('Keyboard Help')),
- h('div.keyboard-help-dialog', [
- h('div.modal-window-overlay'),
- h('div.modal-window', { attributes: dialog_attributes, style: dialog_style }, [
- h('div.modal-header', [
- h('h2.modal-window-title', gettext('Keyboard Help'))
- ]),
- h('div.modal-content', [
- h('p', gettext('You can complete this problem using only your keyboard.')),
- h('ul', [
- h('li', gettext('Use "Tab" and "Shift-Tab" to navigate between items and zones.')),
- h('li', gettext('Press "Enter", "Space", "Ctrl-m", or "⌘-m" on an item to select it for dropping, then navigate to the zone you want to drop it on.')),
- h('li', gettext('Press "Enter", "Space", "Ctrl-m", or "⌘-m" to drop the item on the current zone.')),
- h('li', gettext('Press "Esc" if you want to cancel the drop operation (for example, to select a different item).')),
- ])
- ]),
- h('div.modal-actions', [
- h('button.modal-dismiss-button', gettext("OK"))
+ h('div.keyboard-help-dialog', [
+ h('div.modal-window-overlay'),
+ h('div.modal-window', {attributes: {role: 'dialog', 'aria-labelledby': labelledby_id}}, [
+ h('div.modal-header', [
+ h('h2.modal-window-title#'+labelledby_id, gettext('Keyboard Help'))
+ ]),
+ h('div.modal-content', [
+ h('p', gettext('You can complete this problem using only your keyboard.')),
+ h('ul', [
+ h('li', gettext('Use "Tab" and "Shift-Tab" to navigate between items and zones.')),
+ h('li', gettext('Press "Enter", "Space", "Ctrl-m", or "⌘-m" on an item to select it for dropping, then navigate to the zone you want to drop it on.')),
+ h('li', gettext('Press "Enter", "Space", "Ctrl-m", or "⌘-m" to drop the item on the current zone.')),
+ h('li', gettext('Press "Esc" if you want to cancel the drop operation (for example, to select a different item).')),
])
+ ]),
+ h('div.modal-actions', [
+ h('button.modal-dismiss-button', gettext("OK"))
])
])
])
);
};
+ var submitAnswerTemplate = function(ctx) {
+ var attemptsUsedId = "attempts-used-"+configuration.url_name;
+ var attemptsUsedDisplay = (ctx.max_attempts && ctx.max_attempts > 0) ? 'inline': 'none';
+ var button_enabled = ctx.items.some(function(item) {return item.is_placed;}) &&
+ (ctx.max_attempts === null || ctx.max_attempts > ctx.num_attempts);
+
+ return (
+ h("section.action-toolbar-item.submit-answer", {}, [
+ h(
+ "button.btn-brand.submit-answer-button",
+ {disabled: !button_enabled, attributes: {"aria-describedby": attemptsUsedId}},
+ gettext("Submit")
+ ),
+ h(
+ "span.attempts-used#"+attemptsUsedId, {style: {display: attemptsUsedDisplay}},
+ gettext("You have used {used} of {total} attempts.")
+ .replace("{used}", ctx.num_attempts).replace("{total}", ctx.max_attempts)
+ )
+ ])
+ );
+ };
+
+ var sidebarButtonTemplate = function(buttonClass, iconClass, buttonText, disabled) {
+ return (
+ h('span.sidebar-button-wrapper', {}, [
+ h(
+ 'button.unbutton.btn-default.btn-small.'+buttonClass,
+ {disabled: disabled || false, attributes: {tabindex: 0}},
+ [
+ h("span.btn-icon.fa."+iconClass, {attributes: {"aria-hidden": true}}, []),
+ buttonText
+ ]
+ )
+ ])
+ );
+ };
+
+ var sidebarTemplate = function(ctx) {
+ return(
+ h("section.action-toolbar-item.sidebar-buttons", {}, [
+ sidebarButtonTemplate("keyboard-help-button", "fa-question", gettext('Keyboard Help')),
+ sidebarButtonTemplate("reset-button", "fa-refresh", gettext('Reset'), ctx.disable_reset_button),
+ ])
+ )
+ };
+
var mainTemplate = function(ctx) {
var problemTitle = ctx.show_title ? h('h2.problem-title', {innerHTML: ctx.title_html}) : null;
var problemHeader = ctx.show_problem_header ? h('h3.title1', gettext('Problem')) : null;
@@ -336,7 +372,11 @@ function DragAndDropTemplates(configuration) {
renderCollection(zoneTemplate, ctx.zones, ctx)
]),
]),
- keyboardHelpTemplate(ctx),
+ h("section.actions-toolbar", {}, [
+ sidebarTemplate(ctx),
+ (ctx.show_submit_answer ? submitAnswerTemplate(ctx) : null),
+ ]),
+ keyboardHelpPopupTemplate(ctx),
feedbackTemplate(ctx),
])
);
@@ -655,6 +695,10 @@ function DragAndDropBlock(runtime, element, configuration) {
zones[idx].focus();
};
+ var focusFirstDraggable = function() {
+ $root.find('.item-bank .option').first().focus();
+ };
+
var placeItem = function($zone, $item) {
var item_id;
var $anchor;
@@ -918,13 +962,11 @@ function DragAndDropBlock(runtime, element, configuration) {
type: 'POST',
url: runtime.handlerUrl(element, 'reset'),
data: '{}',
+ }).done(function(data) {
+ state = data;
+ applyState();
+ focusFirstDraggable();
});
- state = {
- 'items': [],
- 'finished': false,
- 'overall_feedback': configuration.initial_feedback,
- };
- applyState();
};
var render = function() {
@@ -984,8 +1026,12 @@ function DragAndDropBlock(runtime, element, configuration) {
bg_image_width: bgImgNaturalWidth, // Not stored in configuration since it's unknown on the server side
title_html: configuration.title,
show_title: configuration.show_title,
+ mode: configuration.mode,
+ max_attempts: configuration.max_attempts,
+ num_attempts: state.num_attempts,
problem_html: configuration.problem_text,
show_problem_header: configuration.show_problem_header,
+ show_submit_answer: configuration.mode == DragAndDropBlock.ASSESSMENT_MODE,
target_img_src: configuration.target_img_expanded_url,
target_img_description: configuration.target_img_description,
display_zone_labels: configuration.display_zone_labels,
@@ -997,7 +1043,7 @@ function DragAndDropBlock(runtime, element, configuration) {
item_bank_focusable: item_bank_focusable,
popup_html: state.feedback || '',
feedback_html: $.trim(state.overall_feedback),
- display_reset_button: Object.keys(state.items).length > 0,
+ disable_reset_button: Object.keys(state.items).length == 0,
};
return renderView(context);
@@ -1035,8 +1081,8 @@ function DragAndDropBlock(runtime, element, configuration) {
if (configuration.items[i].id === +item_id) {
var size = configuration.items[i].size;
// size is an object like '{width: "50px", height: "auto"}'
- if (parseInt(size.width ) > 0) { width = parseInt(size.width); }
- if (parseInt(size.height) > 0) { height = parseInt(size.height); }
+ if (parseInt(size.width ) > 0) {width = parseInt(size.width);}
+ if (parseInt(size.height) > 0) {height = parseInt(size.height);}
break;
}
}
diff --git a/tests/integration/test_base.py b/tests/integration/test_base.py
index a0be8d4c5..ab71d1e8c 100644
--- a/tests/integration/test_base.py
+++ b/tests/integration/test_base.py
@@ -74,14 +74,20 @@ def _get_keyboard_help(self):
return self._page.find_element_by_css_selector(".keyboard-help")
def _get_keyboard_help_button(self):
- return self._page.find_element_by_css_selector(".keyboard-help .keyboard-help-button")
+ return self._page.find_element_by_css_selector(".keyboard-help-button")
def _get_keyboard_help_dialog(self):
- return self._page.find_element_by_css_selector(".keyboard-help .keyboard-help-dialog")
+ return self._page.find_element_by_css_selector(".keyboard-help-dialog")
def _get_reset_button(self):
return self._page.find_element_by_css_selector('.reset-button')
+ def _get_submit_button(self):
+ return self._page.find_element_by_css_selector('.submit-answer-button')
+
+ def _get_attempts_info(self):
+ return self._page.find_element_by_css_selector('.attempts-used')
+
def _get_feedback(self):
return self._page.find_element_by_css_selector(".feedback")
diff --git a/tests/integration/test_interaction.py b/tests/integration/test_interaction.py
index 6860a3640..d9de6d5cc 100644
--- a/tests/integration/test_interaction.py
+++ b/tests/integration/test_interaction.py
@@ -473,8 +473,12 @@ class DefaultAssessmentDataTestMixin(DefaultDataTestMixin):
"""
Provides a test scenario with default options in assessment mode.
"""
+ MAX_ATTEMPTS = 5
+
def _get_scenario_xml(self): # pylint: disable=no-self-use
- return ""
+ return """
+
+ """.format(max_attempts=self.MAX_ATTEMPTS)
@ddt
@@ -542,6 +546,22 @@ def test_final_feedback_and_reset(self, action_key):
def test_keyboard_help(self, use_keyboard):
self.interact_with_keyboard_help(use_keyboard=use_keyboard)
+ def test_submit_button_shown(self):
+ first_item_definition = self._get_items_with_zone(self.items_map).values()[0]
+
+ submit_button = self._get_submit_button()
+ self.assertTrue(submit_button.is_displayed())
+ self.assertEqual(submit_button.get_attribute('disabled'), 'true') # no items are placed
+
+ attempts_info = self._get_attempts_info()
+ expected_text = "You have used {num} of {max} attempts.".format(num=0, max=self.MAX_ATTEMPTS)
+ self.assertEqual(attempts_info.text, expected_text)
+ self.assertEqual(attempts_info.is_displayed(), self.MAX_ATTEMPTS > 0)
+
+ self.place_item(first_item_definition.item_id, first_item_definition.zone_ids[0], None)
+
+ self.assertEqual(submit_button.get_attribute('disabled'), None)
+
class MultipleValidOptionsInteractionTest(DefaultDataTestMixin, InteractionTestBase, BaseIntegrationTest):
diff --git a/tests/integration/test_render.py b/tests/integration/test_render.py
index b45e26791..e8537a3fa 100644
--- a/tests/integration/test_render.py
+++ b/tests/integration/test_render.py
@@ -213,7 +213,6 @@ def test_popup(self):
def test_keyboard_help(self):
self.load_scenario()
- self._get_keyboard_help()
keyboard_help_button = self._get_keyboard_help_button()
keyboard_help_dialog = self._get_keyboard_help_dialog()
dialog_modal_overlay = keyboard_help_dialog.find_element_by_css_selector('.modal-window-overlay')
@@ -223,7 +222,7 @@ def test_keyboard_help(self):
self.assertFalse(dialog_modal_overlay.is_displayed())
self.assertFalse(dialog_modal.is_displayed())
self.assertEqual(dialog_modal.get_attribute('role'), 'dialog')
- self.assertEqual(dialog_modal.get_attribute('aria-labelledby'), 'modal-window-title')
+ self.assertEqual(dialog_modal.get_attribute('aria-labelledby'), 'modal-window-title-')
def test_feedback(self):
self.load_scenario()
diff --git a/tests/unit/data/assessment/config_out.json b/tests/unit/data/assessment/config_out.json
new file mode 100644
index 000000000..1b31e8f25
--- /dev/null
+++ b/tests/unit/data/assessment/config_out.json
@@ -0,0 +1,62 @@
+{
+ "title": "DnDv2 XBlock with plain text instructions",
+ "mode": "assessment",
+ "max_attempts": 10,
+ "show_title": true,
+ "problem_text": "Can you solve this drag-and-drop problem?",
+ "show_problem_header": true,
+ "target_img_expanded_url": "http://placehold.it/800x600",
+ "target_img_description": "This describes the target image",
+ "item_background_color": null,
+ "item_text_color": null,
+ "initial_feedback": "This is the initial feedback.",
+ "display_zone_borders": false,
+ "display_zone_labels": false,
+ "url_name": "test",
+
+ "zones": [
+ {
+ "title": "Zone 1",
+ "y": 123,
+ "x": 234,
+ "width": 345,
+ "height": 456,
+ "uid": "zone-1"
+ },
+ {
+ "title": "Zone 2",
+ "y": 20,
+ "x": 10,
+ "width": 30,
+ "height": 40,
+ "uid": "zone-2"
+ }
+ ],
+
+ "items": [
+ {
+ "displayName": "1",
+ "imageURL": "",
+ "expandedImageURL": "",
+ "id": 0
+ },
+ {
+ "displayName": "2",
+ "imageURL": "",
+ "expandedImageURL": "",
+ "id": 1
+ },
+ {
+ "displayName": "X",
+ "imageURL": "/static/test_url_expansion",
+ "expandedImageURL": "/course/test-course/assets/test_url_expansion",
+ "id": 2
+ },
+ {
+ "displayName": "",
+ "imageURL": "http://placehold.it/200x100",
+ "expandedImageURL": "http://placehold.it/200x100",
+ "id": 3
+ }
+ ]
+}
diff --git a/tests/unit/data/assessment/data.json b/tests/unit/data/assessment/data.json
new file mode 100644
index 000000000..0a0de5373
--- /dev/null
+++ b/tests/unit/data/assessment/data.json
@@ -0,0 +1,75 @@
+{
+ "zones": [
+ {
+ "title": "Zone 1",
+ "y": 123,
+ "x": 234,
+ "width": 345,
+ "height": 456,
+ "uid": "zone-1"
+ },
+ {
+ "title": "Zone 2",
+ "y": 20,
+ "x": 10,
+ "width": 30,
+ "height": 40,
+ "uid": "zone-2"
+ }
+ ],
+
+
+ "items": [
+ {
+ "displayName": "1",
+ "feedback": {
+ "incorrect": "No 1",
+ "correct": "Yes 1"
+ },
+ "zone": "zone-1",
+ "imageURL": "",
+ "id": 0
+ },
+ {
+ "displayName": "2",
+ "feedback": {
+ "incorrect": "No 2",
+ "correct": "Yes 2"
+ },
+ "zone": "zone-2",
+ "imageURL": "",
+ "id": 1
+ },
+ {
+ "displayName": "X",
+ "feedback": {
+ "incorrect": "",
+ "correct": ""
+ },
+ "zone": "none",
+ "imageURL": "/static/test_url_expansion",
+ "id": 2
+ },
+ {
+ "displayName": "",
+ "feedback": {
+ "incorrect": "",
+ "correct": ""
+ },
+ "zone": "none",
+ "imageURL": "http://placehold.it/200x100",
+ "id": 3
+ }
+ ],
+
+
+ "feedback": {
+ "start": "This is the initial feedback.",
+ "finish": "This is the final feedback."
+ },
+
+
+ "targetImg": "http://placehold.it/800x600",
+ "targetImgDescription": "This describes the target image",
+ "displayLabels": false
+}
diff --git a/tests/unit/data/assessment/settings.json b/tests/unit/data/assessment/settings.json
new file mode 100644
index 000000000..59b9de1b4
--- /dev/null
+++ b/tests/unit/data/assessment/settings.json
@@ -0,0 +1,12 @@
+{
+ "display_name": "DnDv2 XBlock with plain text instructions",
+ "mode": "assessment",
+ "max_attempts": 10,
+ "show_title": true,
+ "question_text": "Can you solve this drag-and-drop problem?",
+ "show_question_header": true,
+ "weight": 1,
+ "item_background_color": "",
+ "item_text_color": "",
+ "url_name": "test"
+}
diff --git a/tests/unit/data/html/config_out.json b/tests/unit/data/html/config_out.json
index 80f40880c..7e809ea43 100644
--- a/tests/unit/data/html/config_out.json
+++ b/tests/unit/data/html/config_out.json
@@ -1,6 +1,7 @@
{
"title": "DnDv2 XBlock with HTML instructions",
"mode": "standard",
+ "max_attempts": 0,
"show_title": false,
"problem_text": "Solve this drag-and-drop problem.",
"show_problem_header": false,
diff --git a/tests/unit/data/html/settings.json b/tests/unit/data/html/settings.json
index 1db59684d..dc26eb779 100644
--- a/tests/unit/data/html/settings.json
+++ b/tests/unit/data/html/settings.json
@@ -1,5 +1,6 @@
{
"display_name": "DnDv2 XBlock with HTML instructions",
+ "max_attempts": 0,
"show_title": false,
"question_text": "Solve this drag-and-drop problem.",
"show_question_header": false,
diff --git a/tests/unit/data/old/config_out.json b/tests/unit/data/old/config_out.json
index d2ef3efd1..f4c0dd888 100644
--- a/tests/unit/data/old/config_out.json
+++ b/tests/unit/data/old/config_out.json
@@ -1,6 +1,7 @@
{
"title": "Drag and Drop",
"mode": "standard",
+ "max_attempts": null,
"show_title": true,
"problem_text": "",
"show_problem_header": true,
diff --git a/tests/unit/data/plain/config_out.json b/tests/unit/data/plain/config_out.json
index 6f60f442b..fd116d481 100644
--- a/tests/unit/data/plain/config_out.json
+++ b/tests/unit/data/plain/config_out.json
@@ -1,6 +1,7 @@
{
"title": "DnDv2 XBlock with plain text instructions",
"mode": "standard",
+ "max_attempts": 0,
"show_title": true,
"problem_text": "Can you solve this drag-and-drop problem?",
"show_problem_header": true,
diff --git a/tests/unit/data/plain/settings.json b/tests/unit/data/plain/settings.json
index fb3a97ad7..27d7c22e5 100644
--- a/tests/unit/data/plain/settings.json
+++ b/tests/unit/data/plain/settings.json
@@ -1,5 +1,6 @@
{
"display_name": "DnDv2 XBlock with plain text instructions",
+ "max_attempts": 0,
"show_title": true,
"question_text": "Can you solve this drag-and-drop problem?",
"show_question_header": true,
diff --git a/tests/unit/test_advanced.py b/tests/unit/test_advanced.py
index 4449fa345..3be85e7ea 100644
--- a/tests/unit/test_advanced.py
+++ b/tests/unit/test_advanced.py
@@ -5,8 +5,6 @@
from xblockutils.resources import ResourceLoader
-from drag_and_drop_v2.drag_and_drop_v2 import DragAndDropBlock
-
from ..utils import make_block, TestCaseMixin
@@ -57,8 +55,13 @@ def initial_feedback(cls):
return cls.expected_configuration()["initial_feedback"]
def test_get_configuration(self):
- self.assertEqual(self.expected_configuration(), self.block.get_configuration())
+ self.assertEqual(self.block.get_configuration(), self.expected_configuration())
+
+class StandardModeFixture(BaseDragAndDropAjaxFixture):
+ """
+ Common tests for drag and drop in standard mode
+ """
def test_do_attempt_wrong_with_feedback(self):
item_id, zone_id = 0, self.ZONE_2
data = {"val": item_id, "zone": zone_id, "x_percent": "33%", "y_percent": "11%"}
@@ -92,14 +95,6 @@ def test_do_attempt_correct(self):
"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):
published_grades = []
@@ -131,6 +126,7 @@ def test_do_attempt_final(self):
"0": {"x_percent": "33%", "y_percent": "11%", "correct": True, "zone": self.ZONE_1}
},
"finished": False,
+ "num_attempts": 0,
'overall_feedback': self.initial_feedback(),
}
self.assertEqual(expected_state, self.call_handler('get_user_state', method="GET"))
@@ -154,12 +150,25 @@ def test_do_attempt_final(self):
}
},
"finished": True,
+ "num_attempts": 0,
'overall_feedback': self.FINAL_FEEDBACK,
}
self.assertEqual(expected_state, self.call_handler('get_user_state', method="GET"))
-class TestDragAndDropHtmlData(BaseDragAndDropAjaxFixture, unittest.TestCase):
+class AssessmentModeFixture(BaseDragAndDropAjaxFixture):
+ """
+ Common tests for drag and drop in assessment mode
+ """
+ def test_do_attempt_in_assessment_mode(self):
+ 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, {})
+
+
+class TestDragAndDropHtmlData(StandardModeFixture, unittest.TestCase):
FOLDER = "html"
ZONE_1 = "Zone 1"
@@ -174,7 +183,7 @@ class TestDragAndDropHtmlData(BaseDragAndDropAjaxFixture, unittest.TestCase):
FINAL_FEEDBACK = "Final feedback!"
-class TestDragAndDropPlainData(BaseDragAndDropAjaxFixture, unittest.TestCase):
+class TestDragAndDropPlainData(StandardModeFixture, unittest.TestCase):
FOLDER = "plain"
ZONE_1 = "zone-1"
@@ -198,3 +207,18 @@ class TestOldDataFormat(TestDragAndDropPlainData):
ZONE_1 = "Zone 1"
ZONE_2 = "Zone 2"
+
+
+class TestDragAndDropAssessmentData(AssessmentModeFixture, unittest.TestCase):
+ FOLDER = "assessment"
+
+ ZONE_1 = "zone-1"
+ ZONE_2 = "zone-2"
+
+ FEEDBACK = {
+ 0: {"correct": "Yes 1", "incorrect": "No 1"},
+ 1: {"correct": "Yes 2", "incorrect": "No 2"},
+ 2: {"correct": "", "incorrect": ""}
+ }
+
+ FINAL_FEEDBACK = "This is the final feedback."
diff --git a/tests/unit/test_basics.py b/tests/unit/test_basics.py
index 4191a1bac..b7c570980 100644
--- a/tests/unit/test_basics.py
+++ b/tests/unit/test_basics.py
@@ -31,6 +31,7 @@ def test_get_configuration(self):
items = config.pop("items")
self.assertEqual(config, {
"mode": DragAndDropBlock.STANDARD_MODE,
+ "max_attempts": None,
"display_zone_borders": False,
"display_zone_labels": False,
"title": "Drag and Drop",
@@ -68,6 +69,7 @@ def assert_user_state_empty():
self.assertEqual(self.call_handler("get_user_state"), {
'items': {},
'finished': False,
+ "num_attempts": 0,
'overall_feedback': START_FEEDBACK,
})
assert_user_state_empty()
@@ -98,6 +100,7 @@ def assert_user_state_empty():
'3': {'x_percent': '67%', 'y_percent': '80%', 'correct': True, "zone": MIDDLE_ZONE_ID},
},
'finished': True,
+ "num_attempts": 0,
'overall_feedback': FINISH_FEEDBACK,
})