Commit 630a8d7f by Jesse Shapiro Committed by GitHub

Merge pull request #86 from open-craft/ekolpakov/assessment-submit-ux

[SOL-1944] Submit Answer UI
parents 2ad8a2cb dd94251a
...@@ -127,6 +127,12 @@ class DragAndDropBlock(XBlock, XBlockWithSettingsMixin, ThemableXBlockMixin): ...@@ -127,6 +127,12 @@ class DragAndDropBlock(XBlock, XBlockWithSettingsMixin, ThemableXBlockMixin):
default={}, default={},
) )
num_attempts = Integer(
help=_("Number of attempts learner used"),
scope=Scope.user_state,
default=0
)
completed = Boolean( completed = Boolean(
help=_("Indicates whether a learner has completed the problem at least once"), help=_("Indicates whether a learner has completed the problem at least once"),
scope=Scope.user_state, scope=Scope.user_state,
...@@ -191,6 +197,7 @@ class DragAndDropBlock(XBlock, XBlockWithSettingsMixin, ThemableXBlockMixin): ...@@ -191,6 +197,7 @@ class DragAndDropBlock(XBlock, XBlockWithSettingsMixin, ThemableXBlockMixin):
return { return {
"mode": self.mode, "mode": self.mode,
"max_attempts": self.max_attempts,
"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', ''),
...@@ -433,6 +440,7 @@ class DragAndDropBlock(XBlock, XBlockWithSettingsMixin, ThemableXBlockMixin): ...@@ -433,6 +440,7 @@ class DragAndDropBlock(XBlock, XBlockWithSettingsMixin, ThemableXBlockMixin):
return { return {
'items': item_state, 'items': item_state,
'finished': is_finished, 'finished': is_finished,
'num_attempts': self.num_attempts,
'overall_feedback': self.data['feedback']['finish' if is_finished else 'start'], 'overall_feedback': self.data['feedback']['finish' if is_finished else 'start'],
} }
......
...@@ -326,13 +326,174 @@ ...@@ -326,13 +326,174 @@
outline: 2px solid white; 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 { .xblock--drag-and-drop .keyboard-help {
margin-top: 3px; margin-top: 3px;
margin-bottom: 6px; 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 { .xblock--drag-and-drop .keyboard-help-dialog {
position: fixed; position: fixed;
left: 50%; left: 50%;
...@@ -382,26 +543,7 @@ ...@@ -382,26 +543,7 @@
margin-left: 2%; margin-left: 2%;
} }
.xblock--drag-and-drop .link-button { /*** KEYBOARD HELP END ***/
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;
}
/* Make sure screen-reader content is hidden in the workbench: */ /* Make sure screen-reader content is hidden in the workbench: */
.xblock--drag-and-drop .sr { .xblock--drag-and-drop .sr {
......
...@@ -228,51 +228,87 @@ function DragAndDropTemplates(configuration) { ...@@ -228,51 +228,87 @@ function DragAndDropTemplates(configuration) {
var feedbackTemplate = function(ctx) { var feedbackTemplate = function(ctx) {
var feedback_display = ctx.feedback_html ? 'block' : 'none'; var feedback_display = ctx.feedback_html ? 'block' : 'none';
var reset_button_display = ctx.display_reset_button ? 'block' : 'none';
var properties = { attributes: { 'aria-live': 'polite' } }; var properties = { attributes: { 'aria-live': 'polite' } };
return ( return (
h('section.feedback', properties, [ 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('h3.title1', { style: { display: feedback_display } }, gettext('Feedback')),
h('p.message', { style: { display: feedback_display }, innerHTML: ctx.feedback_html }) h('p.message', { style: { display: feedback_display }, innerHTML: ctx.feedback_html })
]) ])
); );
}; };
var keyboardHelpTemplate = function(ctx) { var keyboardHelpPopupTemplate = function(ctx) {
var dialog_attributes = { role: 'dialog', 'aria-labelledby': 'modal-window-title' }; var labelledby_id = 'modal-window-title-'+configuration.url_name;
var dialog_style = {};
return ( return (
h('section.keyboard-help', [ h('div.keyboard-help-dialog', [
h('button.keyboard-help-button.unbutton.link-button', { attributes: { tabindex: 0 } }, gettext('Keyboard Help')), h('div.modal-window-overlay'),
h('div.keyboard-help-dialog', [ h('div.modal-window', {attributes: {role: 'dialog', 'aria-labelledby': labelledby_id}}, [
h('div.modal-window-overlay'), h('div.modal-header', [
h('div.modal-window', { attributes: dialog_attributes, style: dialog_style }, [ h('h2.modal-window-title#'+labelledby_id, gettext('Keyboard Help'))
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('div.modal-content', [ h('ul', [
h('p', gettext('You can complete this problem using only your keyboard.')), h('li', gettext('Use "Tab" and "Shift-Tab" to navigate between items and zones.')),
h('ul', [ 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('Use "Tab" and "Shift-Tab" to navigate between items and zones.')), h('li', gettext('Press "Enter", "Space", "Ctrl-m", or "⌘-m" to drop the item on the current zone.')),
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 "Esc" if you want to cancel the drop operation (for example, to select a different item).')),
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.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 mainTemplate = function(ctx) {
var problemTitle = ctx.show_title ? h('h2.problem-title', {innerHTML: ctx.title_html}) : null; 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; var problemHeader = ctx.show_problem_header ? h('h3.title1', gettext('Problem')) : null;
...@@ -336,7 +372,11 @@ function DragAndDropTemplates(configuration) { ...@@ -336,7 +372,11 @@ function DragAndDropTemplates(configuration) {
renderCollection(zoneTemplate, ctx.zones, ctx) renderCollection(zoneTemplate, ctx.zones, ctx)
]), ]),
]), ]),
keyboardHelpTemplate(ctx), h("section.actions-toolbar", {}, [
sidebarTemplate(ctx),
(ctx.show_submit_answer ? submitAnswerTemplate(ctx) : null),
]),
keyboardHelpPopupTemplate(ctx),
feedbackTemplate(ctx), feedbackTemplate(ctx),
]) ])
); );
...@@ -655,6 +695,10 @@ function DragAndDropBlock(runtime, element, configuration) { ...@@ -655,6 +695,10 @@ function DragAndDropBlock(runtime, element, configuration) {
zones[idx].focus(); zones[idx].focus();
}; };
var focusFirstDraggable = function() {
$root.find('.item-bank .option').first().focus();
};
var placeItem = function($zone, $item) { var placeItem = function($zone, $item) {
var item_id; var item_id;
var $anchor; var $anchor;
...@@ -918,13 +962,11 @@ function DragAndDropBlock(runtime, element, configuration) { ...@@ -918,13 +962,11 @@ function DragAndDropBlock(runtime, element, configuration) {
type: 'POST', type: 'POST',
url: runtime.handlerUrl(element, 'reset'), url: runtime.handlerUrl(element, 'reset'),
data: '{}', data: '{}',
}).done(function(data) {
state = data;
applyState();
focusFirstDraggable();
}); });
state = {
'items': [],
'finished': false,
'overall_feedback': configuration.initial_feedback,
};
applyState();
}; };
var render = function() { var render = function() {
...@@ -984,8 +1026,12 @@ function DragAndDropBlock(runtime, element, configuration) { ...@@ -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 bg_image_width: bgImgNaturalWidth, // Not stored in configuration since it's unknown on the server side
title_html: configuration.title, title_html: configuration.title,
show_title: configuration.show_title, show_title: configuration.show_title,
mode: configuration.mode,
max_attempts: configuration.max_attempts,
num_attempts: state.num_attempts,
problem_html: configuration.problem_text, problem_html: configuration.problem_text,
show_problem_header: configuration.show_problem_header, 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_src: configuration.target_img_expanded_url,
target_img_description: configuration.target_img_description, target_img_description: configuration.target_img_description,
display_zone_labels: configuration.display_zone_labels, display_zone_labels: configuration.display_zone_labels,
...@@ -997,7 +1043,7 @@ function DragAndDropBlock(runtime, element, configuration) { ...@@ -997,7 +1043,7 @@ function DragAndDropBlock(runtime, element, configuration) {
item_bank_focusable: item_bank_focusable, item_bank_focusable: item_bank_focusable,
popup_html: state.feedback || '', popup_html: state.feedback || '',
feedback_html: $.trim(state.overall_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); return renderView(context);
...@@ -1035,8 +1081,8 @@ function DragAndDropBlock(runtime, element, configuration) { ...@@ -1035,8 +1081,8 @@ function DragAndDropBlock(runtime, element, configuration) {
if (configuration.items[i].id === +item_id) { if (configuration.items[i].id === +item_id) {
var size = configuration.items[i].size; var size = configuration.items[i].size;
// size is an object like '{width: "50px", height: "auto"}' // size is an object like '{width: "50px", height: "auto"}'
if (parseInt(size.width ) > 0) { width = parseInt(size.width); } if (parseInt(size.width ) > 0) {width = parseInt(size.width);}
if (parseInt(size.height) > 0) { height = parseInt(size.height); } if (parseInt(size.height) > 0) {height = parseInt(size.height);}
break; break;
} }
} }
......
...@@ -74,14 +74,20 @@ class BaseIntegrationTest(SeleniumBaseTest): ...@@ -74,14 +74,20 @@ class BaseIntegrationTest(SeleniumBaseTest):
return self._page.find_element_by_css_selector(".keyboard-help") return self._page.find_element_by_css_selector(".keyboard-help")
def _get_keyboard_help_button(self): 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): 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): def _get_reset_button(self):
return self._page.find_element_by_css_selector('.reset-button') 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): def _get_feedback(self):
return self._page.find_element_by_css_selector(".feedback") return self._page.find_element_by_css_selector(".feedback")
......
...@@ -473,8 +473,12 @@ class DefaultAssessmentDataTestMixin(DefaultDataTestMixin): ...@@ -473,8 +473,12 @@ class DefaultAssessmentDataTestMixin(DefaultDataTestMixin):
""" """
Provides a test scenario with default options in assessment mode. Provides a test scenario with default options in assessment mode.
""" """
MAX_ATTEMPTS = 5
def _get_scenario_xml(self): # pylint: disable=no-self-use def _get_scenario_xml(self): # pylint: disable=no-self-use
return "<vertical_demo><drag-and-drop-v2 mode='assessment'/></vertical_demo>" return """
<vertical_demo><drag-and-drop-v2 mode='assessment' max_attempts='{max_attempts}'/></vertical_demo>
""".format(max_attempts=self.MAX_ATTEMPTS)
@ddt @ddt
...@@ -542,6 +546,22 @@ class AssessmentInteractionTest(DefaultAssessmentDataTestMixin, InteractionTestB ...@@ -542,6 +546,22 @@ class AssessmentInteractionTest(DefaultAssessmentDataTestMixin, InteractionTestB
def test_keyboard_help(self, use_keyboard): def test_keyboard_help(self, use_keyboard):
self.interact_with_keyboard_help(use_keyboard=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): class MultipleValidOptionsInteractionTest(DefaultDataTestMixin, InteractionTestBase, BaseIntegrationTest):
......
...@@ -213,7 +213,6 @@ class TestDragAndDropRender(BaseIntegrationTest): ...@@ -213,7 +213,6 @@ class TestDragAndDropRender(BaseIntegrationTest):
def test_keyboard_help(self): def test_keyboard_help(self):
self.load_scenario() self.load_scenario()
self._get_keyboard_help()
keyboard_help_button = self._get_keyboard_help_button() keyboard_help_button = self._get_keyboard_help_button()
keyboard_help_dialog = self._get_keyboard_help_dialog() keyboard_help_dialog = self._get_keyboard_help_dialog()
dialog_modal_overlay = keyboard_help_dialog.find_element_by_css_selector('.modal-window-overlay') dialog_modal_overlay = keyboard_help_dialog.find_element_by_css_selector('.modal-window-overlay')
...@@ -223,7 +222,7 @@ class TestDragAndDropRender(BaseIntegrationTest): ...@@ -223,7 +222,7 @@ class TestDragAndDropRender(BaseIntegrationTest):
self.assertFalse(dialog_modal_overlay.is_displayed()) self.assertFalse(dialog_modal_overlay.is_displayed())
self.assertFalse(dialog_modal.is_displayed()) self.assertFalse(dialog_modal.is_displayed())
self.assertEqual(dialog_modal.get_attribute('role'), 'dialog') 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): def test_feedback(self):
self.load_scenario() self.load_scenario()
......
{
"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
}
]
}
{
"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
}
{
"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"
}
{ {
"title": "DnDv2 XBlock with HTML instructions", "title": "DnDv2 XBlock with HTML instructions",
"mode": "standard", "mode": "standard",
"max_attempts": 0,
"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,
......
{ {
"display_name": "DnDv2 XBlock with HTML instructions", "display_name": "DnDv2 XBlock with HTML instructions",
"max_attempts": 0,
"show_title": false, "show_title": false,
"question_text": "Solve this <strong>drag-and-drop</strong> problem.", "question_text": "Solve this <strong>drag-and-drop</strong> problem.",
"show_question_header": false, "show_question_header": false,
......
{ {
"title": "Drag and Drop", "title": "Drag and Drop",
"mode": "standard", "mode": "standard",
"max_attempts": null,
"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", "mode": "standard",
"max_attempts": 0,
"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,
......
{ {
"display_name": "DnDv2 XBlock with plain text instructions", "display_name": "DnDv2 XBlock with plain text instructions",
"max_attempts": 0,
"show_title": true, "show_title": true,
"question_text": "Can you solve this drag-and-drop problem?", "question_text": "Can you solve this drag-and-drop problem?",
"show_question_header": true, "show_question_header": true,
......
...@@ -5,8 +5,6 @@ import unittest ...@@ -5,8 +5,6 @@ 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
...@@ -57,8 +55,13 @@ class BaseDragAndDropAjaxFixture(TestCaseMixin): ...@@ -57,8 +55,13 @@ class BaseDragAndDropAjaxFixture(TestCaseMixin):
return cls.expected_configuration()["initial_feedback"] return cls.expected_configuration()["initial_feedback"]
def test_get_configuration(self): 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): def test_do_attempt_wrong_with_feedback(self):
item_id, zone_id = 0, self.ZONE_2 item_id, zone_id = 0, self.ZONE_2
data = {"val": item_id, "zone": zone_id, "x_percent": "33%", "y_percent": "11%"} data = {"val": item_id, "zone": zone_id, "x_percent": "33%", "y_percent": "11%"}
...@@ -92,14 +95,6 @@ class BaseDragAndDropAjaxFixture(TestCaseMixin): ...@@ -92,14 +95,6 @@ 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 = []
...@@ -131,6 +126,7 @@ class BaseDragAndDropAjaxFixture(TestCaseMixin): ...@@ -131,6 +126,7 @@ class BaseDragAndDropAjaxFixture(TestCaseMixin):
"0": {"x_percent": "33%", "y_percent": "11%", "correct": True, "zone": self.ZONE_1} "0": {"x_percent": "33%", "y_percent": "11%", "correct": True, "zone": self.ZONE_1}
}, },
"finished": False, "finished": False,
"num_attempts": 0,
'overall_feedback': self.initial_feedback(), 'overall_feedback': self.initial_feedback(),
} }
self.assertEqual(expected_state, self.call_handler('get_user_state', method="GET")) self.assertEqual(expected_state, self.call_handler('get_user_state', method="GET"))
...@@ -154,12 +150,25 @@ class BaseDragAndDropAjaxFixture(TestCaseMixin): ...@@ -154,12 +150,25 @@ class BaseDragAndDropAjaxFixture(TestCaseMixin):
} }
}, },
"finished": True, "finished": True,
"num_attempts": 0,
'overall_feedback': self.FINAL_FEEDBACK, 'overall_feedback': self.FINAL_FEEDBACK,
} }
self.assertEqual(expected_state, self.call_handler('get_user_state', method="GET")) 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" FOLDER = "html"
ZONE_1 = "Zone <i>1</i>" ZONE_1 = "Zone <i>1</i>"
...@@ -174,7 +183,7 @@ class TestDragAndDropHtmlData(BaseDragAndDropAjaxFixture, unittest.TestCase): ...@@ -174,7 +183,7 @@ class TestDragAndDropHtmlData(BaseDragAndDropAjaxFixture, unittest.TestCase):
FINAL_FEEDBACK = "Final <strong>feedback</strong>!" FINAL_FEEDBACK = "Final <strong>feedback</strong>!"
class TestDragAndDropPlainData(BaseDragAndDropAjaxFixture, unittest.TestCase): class TestDragAndDropPlainData(StandardModeFixture, unittest.TestCase):
FOLDER = "plain" FOLDER = "plain"
ZONE_1 = "zone-1" ZONE_1 = "zone-1"
...@@ -198,3 +207,18 @@ class TestOldDataFormat(TestDragAndDropPlainData): ...@@ -198,3 +207,18 @@ class TestOldDataFormat(TestDragAndDropPlainData):
ZONE_1 = "Zone 1" ZONE_1 = "Zone 1"
ZONE_2 = "Zone 2" 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."
...@@ -31,6 +31,7 @@ class BasicTests(TestCaseMixin, unittest.TestCase): ...@@ -31,6 +31,7 @@ class BasicTests(TestCaseMixin, unittest.TestCase):
items = config.pop("items") items = config.pop("items")
self.assertEqual(config, { self.assertEqual(config, {
"mode": DragAndDropBlock.STANDARD_MODE, "mode": DragAndDropBlock.STANDARD_MODE,
"max_attempts": None,
"display_zone_borders": False, "display_zone_borders": False,
"display_zone_labels": False, "display_zone_labels": False,
"title": "Drag and Drop", "title": "Drag and Drop",
...@@ -68,6 +69,7 @@ class BasicTests(TestCaseMixin, unittest.TestCase): ...@@ -68,6 +69,7 @@ class BasicTests(TestCaseMixin, unittest.TestCase):
self.assertEqual(self.call_handler("get_user_state"), { self.assertEqual(self.call_handler("get_user_state"), {
'items': {}, 'items': {},
'finished': False, 'finished': False,
"num_attempts": 0,
'overall_feedback': START_FEEDBACK, 'overall_feedback': START_FEEDBACK,
}) })
assert_user_state_empty() assert_user_state_empty()
...@@ -98,6 +100,7 @@ class BasicTests(TestCaseMixin, unittest.TestCase): ...@@ -98,6 +100,7 @@ class BasicTests(TestCaseMixin, unittest.TestCase):
'3': {'x_percent': '67%', 'y_percent': '80%', 'correct': True, "zone": MIDDLE_ZONE_ID}, '3': {'x_percent': '67%', 'y_percent': '80%', 'correct': True, "zone": MIDDLE_ZONE_ID},
}, },
'finished': True, 'finished': True,
"num_attempts": 0,
'overall_feedback': FINISH_FEEDBACK, 'overall_feedback': FINISH_FEEDBACK,
}) })
......
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