Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
X
xblock-drag-and-drop-v2
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
OpenEdx
xblock-drag-and-drop-v2
Commits
30173c47
Commit
30173c47
authored
Jul 31, 2016
by
Matjaz Gregoric
Committed by
GitHub
Jul 31, 2016
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #82 from open-craft/mtyaka/assessment-item-placement
Keep items in dropped position in assessment mode.
parents
cd689891
97c5c4d8
Show whitespace changes
Inline
Side-by-side
Showing
10 changed files
with
194 additions
and
59 deletions
+194
-59
drag_and_drop_v2/drag_and_drop_v2.py
+8
-1
drag_and_drop_v2/public/js/drag_and_drop.js
+24
-8
drag_and_drop_v2/translations/en/LC_MESSAGES/text.po
+4
-0
drag_and_drop_v2/translations/eo/LC_MESSAGES/text.po
+4
-0
tests/integration/test_interaction.py
+140
-50
tests/unit/data/html/config_out.json
+1
-0
tests/unit/data/old/config_out.json
+1
-0
tests/unit/data/plain/config_out.json
+1
-0
tests/unit/test_advanced.py
+10
-0
tests/unit/test_basics.py
+1
-0
No files found.
drag_and_drop_v2/drag_and_drop_v2.py
View file @
30173c47
...
@@ -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
=
{}
...
...
drag_and_drop_v2/public/js/drag_and_drop.js
View file @
30173c47
function
Drag
NDropTemplates
(
url_name
)
{
function
Drag
AndDropTemplates
(
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
);
};
};
/**
/**
...
...
drag_and_drop_v2/translations/en/LC_MESSAGES/text.po
View file @
30173c47
...
@@ -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 ""
...
...
drag_and_drop_v2/translations/eo/LC_MESSAGES/text.po
View file @
30173c47
...
@@ -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: Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт αмєт, #"
...
...
tests/integration/test_interaction.py
View file @
30173c47
...
@@ -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
,
BaseIntegra
tionTest
):
class
CustomHtmlDataInteractionTest
(
StandardInterac
tionTest
):
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
=
{
...
...
tests/unit/data/html/config_out.json
View file @
30173c47
{
{
"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
,
...
...
tests/unit/data/old/config_out.json
View file @
30173c47
{
{
"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
,
...
...
tests/unit/data/plain/config_out.json
View file @
30173c47
{
{
"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
,
...
...
tests/unit/test_advanced.py
View file @
30173c47
...
@@ -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
=
[]
...
...
tests/unit/test_basics.py
View file @
30173c47
...
@@ -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"
,
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment