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
69ee413d
Commit
69ee413d
authored
Jan 17, 2017
by
Matjaz Gregoric
Committed by
GitHub
Jan 17, 2017
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #112 from open-craft/mtyaka/a11y-interaction-issues
a11y Interaction Improvements
parents
c773d9fa
85262c32
Hide whitespace changes
Inline
Side-by-side
Showing
15 changed files
with
420 additions
and
196 deletions
+420
-196
.travis.yml
+1
-1
Changelog.md
+10
-0
drag_and_drop_v2/public/js/drag_and_drop.js
+155
-85
drag_and_drop_v2/templates/html/drag_and_drop.html
+4
-3
drag_and_drop_v2/templates/html/drag_and_drop_edit.html
+23
-21
drag_and_drop_v2/translations/en/LC_MESSAGES/text.po
+25
-0
drag_and_drop_v2/translations/eo/LC_MESSAGES/text.po
+25
-0
setup.py
+1
-1
tests/integration/test_base.py
+41
-19
tests/integration/test_events.py
+1
-1
tests/integration/test_interaction.py
+112
-43
tests/integration/test_interaction_assessment.py
+18
-14
tests/integration/test_render.py
+1
-5
tests/integration/test_title_and_question.py
+2
-2
tests/unit/test_basics.py
+1
-1
No files found.
.travis.yml
View file @
69ee413d
...
...
@@ -12,7 +12,7 @@ install:
-
"
pip
install
selenium==2.53.0"
-
"
pip
uninstall
-y
xblock-drag-and-drop-v2"
-
"
python
setup.py
sdist"
-
"
pip
install
dist/xblock-drag-and-drop-v2-2.0.1
3
.tar.gz"
-
"
pip
install
dist/xblock-drag-and-drop-v2-2.0.1
4
.tar.gz"
script
:
-
pep8 drag_and_drop_v2 tests --max-line-length=120
-
pylint drag_and_drop_v2
...
...
Changelog.md
View file @
69ee413d
Version 2.0.14 (2017-01-17)
---------------------------
*
Various accessibility improvements (PRs #110, #111, #112)
Version 2.0.13 (2017-01-02)
---------------------------
*
i18n improvements (PR #113)
Version 2.0.12 (2016-11-08)
---------------------------
...
...
drag_and_drop_v2/public/js/drag_and_drop.js
View file @
69ee413d
...
...
@@ -8,7 +8,8 @@ function DragAndDropTemplates(configuration) {
}
return
(
h
(
"div.spinner-wrapper"
,
{
key
:
item
.
value
+
'-spinner'
},
[
h
(
"i.fa.fa-spin.fa-spinner"
)
h
(
"span.fa.fa-spin.fa-spinner"
,
{
attributes
:
{
'aria-hidden'
:
true
}}),
h
(
"span.sr"
,
gettext
(
'Submitting'
))
])
);
};
...
...
@@ -53,7 +54,6 @@ function DragAndDropTemplates(configuration) {
var
itemTemplate
=
function
(
item
,
ctx
)
{
// Define properties
var
descriptionClassName
=
"sr description"
;
var
className
=
(
item
.
class_name
)
?
item
.
class_name
:
""
;
var
zone
=
getZone
(
item
.
zone
,
ctx
)
||
{};
if
(
item
.
has_image
)
{
...
...
@@ -119,8 +119,8 @@ function DragAndDropTemplates(configuration) {
var
item_description_id
=
configuration
.
url_name
+
'-item-'
+
item
.
value
+
'-description'
;
item_content
.
properties
.
attributes
=
{
'aria-describedby'
:
item_description_id
};
item_description
=
h
(
'div'
,
{
key
:
item
.
value
+
'-description'
,
id
:
item_description_id
,
className
:
descriptionClassName
},
'div
.sr.description
'
,
{
key
:
item
_description_id
,
id
:
item_description_id
},
description_content
);
}
...
...
@@ -185,7 +185,7 @@ function DragAndDropTemplates(configuration) {
var
item_wrapper
=
'div.item-wrapper.item-align.item-align-'
+
zone
.
align
;
var
is_item_in_zone
=
function
(
i
)
{
return
i
.
is_placed
&&
(
i
.
zone
===
zone
.
uid
);
};
var
items_in_zone
=
$
.
grep
(
ctx
.
items
,
is_item_in_zone
);
var
zone_description_id
=
configuration
.
url_name
+
'-zone-'
+
zone
.
uid
+
'-description'
;
var
zone_description_id
=
zone
.
prefixed_
uid
+
'-description'
;
if
(
items_in_zone
.
length
==
0
)
{
var
zone_description
=
h
(
'div'
,
...
...
@@ -221,8 +221,15 @@ function DragAndDropTemplates(configuration) {
}
},
[
h
(
'p'
,
{
className
:
className
},
zone
.
title
),
h
(
'p'
,
{
className
:
'zone-description sr'
},
zone
.
description
||
gettext
(
"droppable"
)),
h
(
'p'
,
{
className
:
className
},
[
zone
.
title
,
h
(
'span.sr'
,
gettext
(
', dropzone'
))
]
),
h
(
'p'
,
{
className
:
'zone-description sr'
},
zone
.
description
||
gettext
(
'droppable'
)),
h
(
item_wrapper
,
renderCollection
(
itemTemplate
,
items_in_zone
,
ctx
)),
zone_description
]
...
...
@@ -242,10 +249,10 @@ function DragAndDropTemplates(configuration) {
});
return
(
h
(
'
section.feedback'
,
{
},
[
h
(
'
div.feedback'
,
{
attributes
:
{
'role'
:
'group'
,
'aria-label'
:
gettext
(
'Feedback'
)}
},
[
h
(
"div.feedback-content"
,
{
attributes
:
{
'aria-live'
:
'polite'
}
},
{},
[
h
(
'h3.title1'
,
{
style
:
{
display
:
feedback_display
}
},
gettext
(
'Feedback'
)),
h
(
'div.messages'
,
{
style
:
{
display
:
feedback_display
}
},
feedback_messages
),
...
...
@@ -286,27 +293,42 @@ function DragAndDropTemplates(configuration) {
};
var
submitAnswerTemplate
=
function
(
ctx
)
{
var
attemptsUsedId
=
"attempts-used-"
+
configuration
.
url_name
;
var
attemptsUsedDisplay
=
(
ctx
.
max_attempts
&&
ctx
.
max_attempts
>
0
)
?
'inline'
:
'none'
;
var
submitButtonProperties
=
{
disabled
:
ctx
.
disable_submit_button
||
ctx
.
submit_spinner
,
attributes
:
{}
};
var
attemptsUsedInfo
=
null
;
if
(
ctx
.
max_attempts
&&
ctx
.
max_attempts
>
0
)
{
var
attemptsUsedId
=
"attempts-used-"
+
configuration
.
url_name
;
submitButtonProperties
.
attributes
[
"aria-describedby"
]
=
attemptsUsedId
;
var
attemptsUsedTemplate
=
gettext
(
"You have used {used} of {total} attempts."
);
var
attemptsUsedText
=
attemptsUsedTemplate
.
replace
(
"{used}"
,
ctx
.
attempts
).
replace
(
"{total}"
,
ctx
.
max_attempts
);
attemptsUsedInfo
=
h
(
"span.attempts-used"
,
{
id
:
attemptsUsedId
},
attemptsUsedText
);
}
var
submitSpinner
=
null
;
if
(
ctx
.
submit_spinner
)
{
submitSpinner
=
h
(
'span'
,
[
h
(
'span.fa.fa-spin.fa-spinner'
,
{
attributes
:
{
'aria-hidden'
:
true
}}),
h
(
'span.sr'
,
gettext
(
'Submitting'
))
]);
}
return
(
h
(
"section.action-toolbar-item.submit-answer"
,
{},
[
h
(
"button.btn-brand.submit-answer-button"
,
{
disabled
:
ctx
.
disable_submit_button
||
ctx
.
submit_spinner
,
attributes
:
{
"aria-describedby"
:
attemptsUsedId
}},
[
(
ctx
.
submit_spinner
?
h
(
"span.fa.fa-spin.fa-spinner"
)
:
null
),
gettext
(
"Submit"
)
]
),
h
(
"span.attempts-used#"
+
attemptsUsedId
,
{
style
:
{
display
:
attemptsUsedDisplay
}},
gettext
(
"You have used {used} of {total} attempts."
)
.
replace
(
"{used}"
,
ctx
.
attempts
).
replace
(
"{total}"
,
ctx
.
max_attempts
)
)
])
h
(
"div.action-toolbar-item.submit-answer"
,
{},
[
h
(
"button.btn-brand.submit-answer-button"
,
submitButtonProperties
,
[
submitSpinner
,
' '
,
// whitespace between spinner icon and text
gettext
(
"Submit"
)
]
),
attemptsUsedInfo
])
);
};
...
...
@@ -351,7 +373,7 @@ function DragAndDropTemplates(configuration) {
go_to_beginning_button_class
+=
' sr'
;
}
return
(
h
(
"
section
.action-toolbar-item.sidebar-buttons"
,
{},
[
h
(
"
div
.action-toolbar-item.sidebar-buttons"
,
{},
[
sidebarButtonTemplate
(
go_to_beginning_button_class
,
"fa-arrow-up"
,
...
...
@@ -397,13 +419,7 @@ function DragAndDropTemplates(configuration) {
return
h
(
popupSelector
,
{
style
:
{
display
:
have_messages
?
'block'
:
'none'
},
attributes
:
{
"tabindex"
:
"-1"
,
'aria-live'
:
'polite'
,
'aria-atomic'
:
'true'
,
'aria-relevant'
:
'additions'
,
}
style
:
{
display
:
have_messages
?
'block'
:
'none'
}
},
[
h
(
...
...
@@ -491,18 +507,24 @@ function DragAndDropTemplates(configuration) {
return
h
(
'div.problem-progress'
,
{
id
:
configuration
.
url_name
+
'-problem-progress'
,
attributes
:
{
'role'
:
'status'
,
'aria-live'
:
'polite
'
}
attributes
:
{
role
:
'status
'
}
},
progress_text
);
};
var
mainTemplate
=
function
(
ctx
)
{
var
main_element_properties
=
{
attributes
:
{
role
:
'group'
}};
var
problemProgress
=
progressTemplate
(
ctx
);
var
problemTitle
=
null
;
if
(
ctx
.
show_title
)
{
var
problem_title_id
=
configuration
.
url_name
+
'-problem-title'
;
problemTitle
=
h
(
'h3.problem-title'
,
{
id
:
problem_title_id
,
innerHTML
:
ctx
.
title_html
,
attributes
:
{
'aria-describedby'
:
problemProgress
.
properties
.
id
}
});
main_element_properties
.
attributes
[
'arial-labelledby'
]
=
problem_title_id
;
}
else
{
main_element_properties
.
attributes
[
'aria-label'
]
=
gettext
(
'Drag and Drop Problem'
);
}
var
problemHeader
=
ctx
.
show_problem_header
?
h
(
'h4.title1'
,
gettext
(
'Problem'
))
:
null
;
// Render only items in the bank here, including placeholders. Placed
...
...
@@ -523,36 +545,37 @@ function DragAndDropTemplates(configuration) {
item_bank_properties
.
attributes
[
'role'
]
=
'button'
;
}
return
(
h
(
'
section.themed-xblock.xblock--drag-and-drop'
,
[
h
(
'
div.themed-xblock.xblock--drag-and-drop'
,
main_element_properties
,
[
problemTitle
,
problemProgress
,
h
(
'div'
,
[
forwardKeyboardHelpButtonTemplate
(
ctx
)]),
h
(
'
section
.problem'
,
[
h
(
'
div
.problem'
,
[
problemHeader
,
h
(
'p'
,
{
innerHTML
:
ctx
.
problem_html
}),
]),
h
(
'
section
.drag-container'
,
{},
[
h
(
'
div
.drag-container'
,
{},
[
h
(
'div.item-bank'
,
item_bank_properties
,
[
renderCollection
(
itemTemplate
,
items_in_bank
,
ctx
),
renderCollection
(
itemPlaceholderTemplate
,
items_placed
,
ctx
)
]),
h
(
'div.target'
,
{},
[
itemFeedbackPopupTemplate
(
ctx
),
h
(
'div.target-img-wrapper'
,
[
h
(
'img.target-img'
,
{
src
:
ctx
.
target_img_src
,
alt
:
ctx
.
target_img_description
}),
]
),
h
(
'div.target'
,
{
attributes
:
{
'role'
:
'group'
,
'arial-label'
:
gettext
(
'Drop Targets'
)}},
[
itemFeedbackPopupTemplate
(
ctx
),
h
(
'div.target-img-wrapper'
,
[
h
(
'img.target-img'
,
{
src
:
ctx
.
target_img_src
,
alt
:
ctx
.
target_img_description
}),
]),
renderCollection
(
zoneTemplate
,
ctx
.
zones
,
ctx
)
]),
]),
h
(
"
section.actions-toolbar"
,
{
},
[
h
(
"
div.actions-toolbar"
,
{
attributes
:
{
'role'
:
'group'
,
'aria-label'
:
gettext
(
'Actions'
)}
},
[
sidebarTemplate
(
ctx
),
(
ctx
.
show_submit_answer
?
submitAnswerTemplate
(
ctx
)
:
null
),
]),
keyboardHelpPopupTemplate
(
ctx
),
feedbackTemplate
(
ctx
),
h
(
'div.sr.reader-feedback-area'
,
{
attributes
:
{
'aria-live'
:
'polite'
,
'aria-atomic'
:
true
},
innerHTML
:
ctx
.
screen_reader_messages
}),
])
);
};
...
...
@@ -664,6 +687,9 @@ function DragAndDropBlock(runtime, element, configuration) {
// to watch for load events on any child element, since load events do not bubble.
element
.
addEventListener
(
'load'
,
webkitFix
,
true
);
// Remove the spinner and create a blank slate for virtualDom to take over.
$root
.
empty
();
applyState
();
initDroppable
();
...
...
@@ -904,7 +930,7 @@ function DragAndDropBlock(runtime, element, configuration) {
return
feedback_msgs_list
.
map
(
function
(
message
)
{
return
message
.
message
;
}).
join
(
'
\
n'
);
};
var
updateDOM
=
function
(
state
)
{
var
updateDOM
=
function
()
{
var
new_vdom
=
render
(
state
);
var
patches
=
virtualDom
.
diff
(
__vdom
,
new_vdom
);
root
=
virtualDom
.
patch
(
root
,
patches
);
...
...
@@ -912,6 +938,51 @@ function DragAndDropBlock(runtime, element, configuration) {
__vdom
=
new_vdom
;
};
var
sr_clear_timeout
=
null
;
var
setScreenReaderMessages
=
function
()
{
clearTimeout
(
sr_clear_timeout
);
var
pluckMessages
=
function
(
feedback_items
)
{
return
feedback_items
.
map
(
function
(
item
)
{
return
item
.
message
;
});
};
var
messages
=
[];
// In standard mode, it makes more sense to read the per-item feedback before overall feedback.
if
(
state
.
feedback
&&
configuration
.
mode
===
DragAndDropBlock
.
STANDARD_MODE
)
{
messages
=
messages
.
concat
(
pluckMessages
(
state
.
feedback
));
}
if
(
state
.
overall_feedback
)
{
messages
=
messages
.
concat
(
pluckMessages
(
state
.
overall_feedback
));
}
// In assessment mode overall feedback comes first then multiple per-item feedbacks.
if
(
state
.
feedback
&&
configuration
.
mode
===
DragAndDropBlock
.
ASSESSMENT_MODE
)
{
if
(
state
.
feedback
.
length
>
0
)
{
if
(
!
state
.
last_action_correct
)
{
messages
.
push
(
gettext
(
"Some of your answers were not correct."
));
}
messages
=
messages
.
concat
(
gettext
(
"Hints:"
),
pluckMessages
(
state
.
feedback
)
);
}
}
var
paragraphs
=
messages
.
map
(
function
(
msg
)
{
return
'<p>'
+
msg
+
'</p>'
;
});
state
.
screen_reader_messages
=
paragraphs
.
join
(
''
);
// Remove the text on next redraw. This will make screen readers read the message again,
// next time the user performs an action, even if next feedback message did not change from
// last attempt (for example: if user drops the same item on two wrong zones one after another,
// the negative feedback should be read out twice, not only on first drop).
sr_clear_timeout
=
setTimeout
(
function
()
{
state
.
screen_reader_messages
=
''
;
},
0
);
};
var
publishEvent
=
function
(
data
)
{
$
.
ajax
({
type
:
'POST'
,
...
...
@@ -981,16 +1052,18 @@ function DragAndDropBlock(runtime, element, configuration) {
return
false
;
};
var
placeItem
=
function
(
$zone
,
$item
)
{
var
item_id
;
if
(
$item
!==
undefined
)
{
item_id
=
$item
.
data
(
'value'
);
}
else
{
item_id
=
$selectedItem
.
data
(
'value'
);
}
var
placeGrabbedItem
=
function
(
$zone
)
{
var
zone
=
String
(
$zone
.
data
(
'uid'
));
var
zone_align
=
$zone
.
data
(
'zone_align'
);
var
items
=
configuration
.
items
;
var
item_id
;
for
(
var
i
=
0
;
i
<
items
.
length
;
i
++
)
{
if
(
items
[
i
].
grabbed
)
{
item_id
=
items
[
i
].
id
;
break
;
}
}
var
items_in_zone_count
=
countItemsInZone
(
zone
,
[
item_id
.
toString
()]);
if
(
configuration
.
max_items_per_zone
&&
configuration
.
max_items_per_zone
<=
items_in_zone_count
)
{
...
...
@@ -1036,18 +1109,17 @@ function DragAndDropBlock(runtime, element, configuration) {
}
else
if
(
isCancelKey
(
evt
))
{
evt
.
preventDefault
();
state
.
keyboard_placement_mode
=
false
;
release
Item
(
$selectedItem
);
release
GrabbedItems
(
);
}
else
if
(
isActionKey
(
evt
))
{
evt
.
preventDefault
();
evt
.
stopPropagation
();
state
.
keyboard_placement_mode
=
false
;
releaseItem
(
$selectedItem
);
if
(
$zone
.
is
(
'.item-bank'
))
{
delete
state
.
items
[
$selectedItem
.
data
(
'value'
)];
applyState
();
}
else
{
placeItem
(
$zone
);
place
Grabbed
Item
(
$zone
);
}
releaseGrabbedItems
();
}
}
else
if
(
isTabKey
(
evt
)
&&
!
evt
.
shiftKey
)
{
// If the user just dropped an item to this zone, next TAB keypress
...
...
@@ -1074,8 +1146,7 @@ function DragAndDropBlock(runtime, element, configuration) {
tolerance
:
'pointer'
,
drop
:
function
(
evt
,
ui
)
{
var
$zone
=
$
(
this
);
var
$item
=
ui
.
helper
;
placeItem
(
$zone
,
$item
);
placeGrabbedItem
(
$zone
);
}
});
...
...
@@ -1087,7 +1158,7 @@ function DragAndDropBlock(runtime, element, configuration) {
drop
:
function
(
evt
,
ui
)
{
var
$item
=
ui
.
helper
;
var
item_id
=
$item
.
data
(
'value'
);
release
Item
(
$item
);
release
GrabbedItems
(
);
delete
state
.
items
[
item_id
];
applyState
();
}
...
...
@@ -1140,7 +1211,7 @@ function DragAndDropBlock(runtime, element, configuration) {
stop
:
function
(
evt
,
ui
)
{
// Revert to original position.
$item
.
css
(
$item
.
data
(
'initial-position'
));
release
Item
(
$
(
this
)
);
release
GrabbedItems
(
);
}
});
}
catch
(
e
)
{
...
...
@@ -1152,31 +1223,27 @@ function DragAndDropBlock(runtime, element, configuration) {
var
grabItem
=
function
(
$item
,
interaction_type
)
{
var
item_id
=
$item
.
data
(
'value'
);
setGrabbedState
(
item_id
,
true
,
interaction_type
);
configuration
.
items
.
forEach
(
function
(
item
)
{
if
(
item
.
id
===
item_id
)
{
item
.
grabbed
=
true
;
item
.
grabbed_with
=
interaction_type
;
}
else
{
item
.
grabbed
=
false
;
delete
item
.
grabbed_with
;
}
});
closePopup
(
false
);
// applyState(true) skips destroying and initializing draggable
applyState
(
true
);
};
var
releaseItem
=
function
(
$item
)
{
var
item_id
=
$item
.
data
(
'value'
);
setGrabbedState
(
item_id
,
false
);
// applyState(true) skips destroying and initializing draggable
applyState
(
true
);
};
var
setGrabbedState
=
function
(
item_id
,
grabbed
,
interaction_type
)
{
var
releaseGrabbedItems
=
function
()
{
configuration
.
items
.
forEach
(
function
(
item
)
{
if
(
item
.
id
===
item_id
)
{
if
(
grabbed
)
{
item
.
grabbed
=
true
;
item
.
grabbed_with
=
interaction_type
;
}
else
{
item
.
grabbed
=
false
;
delete
item
.
grabbed_with
;
}
}
item
.
grabbed
=
false
;
delete
item
.
grabbed_with
;
});
// applyState(true) skips destroying and initializing draggable
applyState
(
true
);
};
var
destroyDraggable
=
function
()
{
...
...
@@ -1220,6 +1287,7 @@ function DragAndDropBlock(runtime, element, configuration) {
state
.
finished
=
true
;
state
.
overall_feedback
=
data
.
overall_feedback
;
}
setScreenReaderMessages
();
}
applyState
();
if
(
state
.
feedback
&&
state
.
feedback
.
length
>
0
)
{
...
...
@@ -1324,6 +1392,7 @@ function DragAndDropBlock(runtime, element, configuration) {
}
else
{
state
.
finished
=
true
;
}
setScreenReaderMessages
();
}).
always
(
function
()
{
state
.
submit_spinner
=
false
;
applyState
();
...
...
@@ -1451,7 +1520,8 @@ function DragAndDropBlock(runtime, element, configuration) {
showing_answer
:
state
.
showing_answer
,
show_answer_spinner
:
state
.
show_answer_spinner
,
disable_go_to_beginning_button
:
!
canGoToBeginning
(),
show_go_to_beginning_button
:
state
.
go_to_beginning_button_visible
show_go_to_beginning_button
:
state
.
go_to_beginning_button_visible
,
screen_reader_messages
:
(
state
.
screen_reader_messages
||
''
)
};
return
renderView
(
context
);
...
...
drag_and_drop_v2/templates/html/drag_and_drop.html
View file @
69ee413d
{% load i18n %}
<section
class=
"themed-xblock xblock--drag-and-drop"
>
<i
class=
"fa fa-spin fa-spinner initial-load-spinner"
></i>
{% trans "Loading drag and drop problem." %}
</section>
<div
class=
"themed-xblock xblock--drag-and-drop"
>
<span
class=
"fa fa-spin fa-spinner initial-load-spinner"
aria-hidden=
"true"
></span>
{% trans "Loading drag and drop problem." %}
</div>
drag_and_drop_v2/templates/html/drag_and_drop_edit.html
View file @
69ee413d
...
...
@@ -4,10 +4,12 @@
<div
class=
"xblock--drag-and-drop--editor editor-with-buttons"
>
{{ js_templates|safe }}
<section
class=
"drag-builder"
>
<div
class=
"tab feedback-tab"
>
<section
class=
"tab-content"
>
<div
class=
"drag-builder"
>
<section
class=
"tab feedback-tab"
>
<header
class=
"tab-header"
>
<h3>
{% trans "Basic Settings" %}
</h3>
</header>
<div
class=
"tab-content"
>
<form
class=
"feedback-form"
>
<label
class=
"h4"
>
<span>
{% trans fields.display_name.display_name %}
</span>
...
...
@@ -80,14 +82,14 @@
<textarea
class=
"final-feedback"
>
{{ self.data.feedback.finish }}
</textarea>
</label>
</form>
</
section
>
</
div
>
</
div
>
</
section
>
<
div
class=
"tab zones-tab hidden"
>
<
section
class=
"tab zones-tab hidden"
>
<header
class=
"tab-header"
>
<h3>
{% trans "Zones" %}
</h3>
</header>
<
section
class=
"tab-content"
>
<
div
class=
"tab-content"
>
<form
class=
"target-image-form"
>
<label
class=
"h4"
for=
"background-url-{{id_suffix}}"
>
<span>
{% trans "Background URL" %}
</span>
...
...
@@ -114,8 +116,8 @@
{% endblocktrans %}
</div>
</form>
</
section
>
<
section
class=
"tab-content"
>
</
div
>
<
div
class=
"tab-content"
>
<form
class=
"display-labels-form"
>
<h4>
{% trans "Zone labels" %}
</h4>
<label
class=
"checkbox-label"
>
...
...
@@ -130,8 +132,8 @@
<span>
{% trans "Display zone borders on the image" %}
</span>
</label>
</form>
</
section
>
<
section
class=
"tab-content"
>
</
div
>
<
div
class=
"tab-content"
>
<h4>
{% trans "Zone definitions" %}
</h4>
<div
class=
"zone-editor"
>
<div
class=
"controls"
>
...
...
@@ -147,14 +149,14 @@
</div>
</div>
</div>
</
section
>
</
div
>
</
div
>
</
section
>
<
div
class=
"tab items-tab hidden"
>
<
section
class=
"tab items-tab hidden"
>
<header
class=
"tab-header"
>
<h3>
{% trans "Items" %}
</h3>
</header>
<
section
class=
"tab-content"
>
<
div
class=
"tab-content"
>
<form
class=
"item-styles-form"
>
<label
class=
"h4"
>
<span>
{% trans fields.item_background_color.display_name %}
</span>
...
...
@@ -184,18 +186,18 @@
{% trans fields.max_items_per_zone.help %}
</div>
</form>
</
section
>
<
section
class=
"tab-content"
>
</
div
>
<
div
class=
"tab-content"
>
<h4>
{% trans "Item definitions" %}
</h4>
<form
class=
"items-form"
></form>
<button
class=
"btn add-item add-element"
>
<span
class=
"icon add"
aria-hidden=
"true"
></span>
{% trans "Add an item" %}
</button>
</
section
>
</
div
>
</
div
>
</
section
>
</
section
>
</
div
>
<div
class=
"xblock-actions"
>
<ul
class=
"action-buttons"
>
...
...
drag_and_drop_v2/translations/en/LC_MESSAGES/text.po
View file @
69ee413d
...
...
@@ -595,6 +595,31 @@ msgid_plural "{possible} points possible (ungraded)"
msgstr[0] ""
msgstr[1] ""
#: drag_and_drop_v2/public/js/drag_and_drop.js:374
#: drag_and_drop_v2/public/js/drag_and_drop.js:839
msgid "Hints:"
msgstr ""
#: drag_and_drop_v2/public/js/drag_and_drop.js:224
msgid ", dropzone"
msgstr ""
#: drag_and_drop_v2/public/js/drag_and_drop.js:479
msgid "Drag and Drop Problem"
msgstr ""
#: drag_and_drop_v2/public/js/drag_and_drop.js:509
msgid "Drop Targets"
msgstr ""
#: drag_and_drop_v2/public/js/drag_and_drop.js:517
msgid "Actions"
msgstr ""
#: drag_and_drop_v2/public/js/drag_and_drop.js:12
msgid "Submitting"
msgstr ""
#: utils.py:44
msgid "Your highest score is {score}"
msgstr ""
...
...
drag_and_drop_v2/translations/eo/LC_MESSAGES/text.po
View file @
69ee413d
...
...
@@ -694,6 +694,31 @@ msgstr[1] "{possible} pöïnts pössïßlé (üngrädéd) Ⱡ'σяєм ιρѕυ
msgid "Close"
msgstr "Çlösé Ⱡ'σяєм ιρѕ#"
#: drag_and_drop_v2/public/js/drag_and_drop.js:374
#: drag_and_drop_v2/public/js/drag_and_drop.js:839
msgid "Hints:"
msgstr "Hïnts: Ⱡ'σяєм ιρѕυ#"
#: drag_and_drop_v2/public/js/drag_and_drop.js:224
msgid ", dropzone"
msgstr ", dröpzöné Ⱡ'σяєм ιρѕυм ∂σłσ#"
#: drag_and_drop_v2/public/js/drag_and_drop.js:479
msgid "Drag and Drop Problem"
msgstr "Dräg änd Dröp Prößlém Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт αмєт, #"
#: drag_and_drop_v2/public/js/drag_and_drop.js:509
msgid "Drop Targets"
msgstr "Dröp Tärgéts Ⱡ'σяєм ιρѕυм ∂σłσя ѕ#"
#: drag_and_drop_v2/public/js/drag_and_drop.js:517
msgid "Actions"
msgstr "Àçtïöns Ⱡ'σяєм ιρѕυм #"
#: drag_and_drop_v2/public/js/drag_and_drop.js:12
msgid "Submitting"
msgstr "Süßmïttïng Ⱡ'σяєм ιρѕυм ∂σłσ#"
#: utils.py:44
msgid "Your highest score is {score}"
msgstr "Ýöür hïghést sçöré ïs {score} Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт αмєт, ¢σηѕ#"
...
...
setup.py
View file @
69ee413d
...
...
@@ -23,7 +23,7 @@ def package_data(pkg, root_list):
setup
(
name
=
'xblock-drag-and-drop-v2'
,
version
=
'2.0.1
3
'
,
version
=
'2.0.1
4
'
,
description
=
'XBlock - Drag-and-Drop v2'
,
packages
=
[
'drag_and_drop_v2'
],
install_requires
=
[
...
...
tests/integration/test_base.py
View file @
69ee413d
...
...
@@ -48,7 +48,7 @@ ItemDefinition = namedtuple( # pylint: disable=invalid-name
class
BaseIntegrationTest
(
SeleniumBaseTest
):
default_css_selector
=
'
section
.themed-xblock.xblock--drag-and-drop'
default_css_selector
=
'.themed-xblock.xblock--drag-and-drop'
module_name
=
__name__
_additional_escapes
=
{
...
...
@@ -239,22 +239,32 @@ class DefaultDataTestMixin(object):
class
InteractionTestBase
(
object
):
POPUP_ERROR_CLASS
=
"popup-incorrect"
@classmethod
def
_get_items_with_zone
(
cls
,
items_map
):
def
setUp
(
self
):
super
(
InteractionTestBase
,
self
)
.
setUp
()
scenario_xml
=
self
.
_get_scenario_xml
()
self
.
_add_scenario
(
self
.
PAGE_ID
,
self
.
PAGE_TITLE
,
scenario_xml
)
self
.
_page
=
self
.
go_to_page
(
self
.
PAGE_TITLE
)
# Resize window so that the entire drag container is visible.
# Selenium has issues when dragging to an area that is off screen.
self
.
browser
.
set_window_size
(
1024
,
1024
)
@staticmethod
def
_get_items_with_zone
(
items_map
):
return
{
item_key
:
definition
for
item_key
,
definition
in
items_map
.
items
()
if
definition
.
zone_ids
!=
[]
}
@
class
method
def
_get_items_without_zone
(
cls
,
items_map
):
@
static
method
def
_get_items_without_zone
(
items_map
):
return
{
item_key
:
definition
for
item_key
,
definition
in
items_map
.
items
()
if
definition
.
zone_ids
==
[]
}
@
class
method
def
_get_items_by_zone
(
cls
,
items_map
):
@
static
method
def
_get_items_by_zone
(
items_map
):
zone_ids
=
set
([
definition
.
zone_ids
[
0
]
for
_
,
definition
in
items_map
.
items
()
if
definition
.
zone_ids
])
return
{
zone_id
:
{
item_key
:
definition
for
item_key
,
definition
in
items_map
.
items
()
...
...
@@ -262,15 +272,17 @@ class InteractionTestBase(object):
for
zone_id
in
zone_ids
}
def
setUp
(
self
):
super
(
InteractionTestBase
,
self
)
.
setUp
()
scenario_xml
=
self
.
_get_scenario_xml
()
self
.
_add_scenario
(
self
.
PAGE_ID
,
self
.
PAGE_TITLE
,
scenario_xml
)
self
.
_page
=
self
.
go_to_page
(
self
.
PAGE_TITLE
)
# Resize window so that the entire drag container is visible.
# Selenium has issues when dragging to an area that is off screen.
self
.
browser
.
set_window_size
(
1024
,
800
)
@staticmethod
def
_get_incorrect_zone_for_item
(
item
,
zones
):
"""Returns the first zone that is not correct for this item."""
zone_id
=
None
zone_title
=
None
for
z_id
,
z_title
in
zones
:
if
z_id
not
in
item
.
zone_ids
:
zone_id
=
z_id
zone_title
=
z_title
break
return
[
zone_id
,
zone_title
]
def
_get_item_by_value
(
self
,
item_value
):
return
self
.
_page
.
find_elements_by_xpath
(
".//div[@data-value='{item_id}']"
.
format
(
item_id
=
item_value
))[
0
]
...
...
@@ -312,7 +324,7 @@ class InteractionTestBase(object):
both the HTML attribute and the DOM property are set to false.
We work around that selenium bug by using JavaScript to get the correct value of 'draggable'.
"""
script
=
"return $('
div
.option[data-value={}]').prop('draggable')"
.
format
(
item_value
)
script
=
"return $('.option[data-value={}]').prop('draggable')"
.
format
(
item_value
)
return
self
.
browser
.
execute_script
(
script
)
def
assertDraggable
(
self
,
item_value
):
...
...
@@ -370,7 +382,7 @@ class InteractionTestBase(object):
item
.
send_keys
(
""
)
item
.
send_keys
(
action_key
)
# Focus is on first *zone* now
self
.
assert_
grabbed_item
(
item
)
self
.
assert_
item_grabbed
(
item
)
# Get desired zone and figure out how many times we have to press Tab to focus the zone.
if
zone_id
is
None
:
# moving back to the bank
zone
=
self
.
_get_item_bank
()
...
...
@@ -387,9 +399,12 @@ class InteractionTestBase(object):
ActionChains
(
self
.
browser
)
.
send_keys
(
Keys
.
TAB
)
.
perform
()
zone
.
send_keys
(
action_key
)
def
assert_
grabbed_item
(
self
,
item
):
def
assert_
item_grabbed
(
self
,
item
):
self
.
assertEqual
(
item
.
get_attribute
(
'aria-grabbed'
),
'true'
)
def
assert_item_not_grabbed
(
self
,
item
):
self
.
assertEqual
(
item
.
get_attribute
(
'aria-grabbed'
),
'false'
)
def
assert_placed_item
(
self
,
item_value
,
zone_title
,
assessment_mode
=
False
):
item
=
self
.
_get_placed_item_by_value
(
item_value
)
self
.
wait_until_visible
(
item
)
...
...
@@ -474,3 +489,10 @@ class InteractionTestBase(object):
def
assert_button_enabled
(
self
,
submit_button
,
enabled
=
True
):
self
.
assertEqual
(
submit_button
.
is_enabled
(),
enabled
)
def
assert_reader_feedback_messages
(
self
,
expected_message_lines
):
expected_paragraphs
=
[
'<p>{}</p>'
.
format
(
l
)
for
l
in
expected_message_lines
]
expected_html
=
''
.
join
(
expected_paragraphs
)
feedback_area
=
self
.
_page
.
find_element_by_css_selector
(
'.reader-feedback-area'
)
actual_html
=
feedback_area
.
get_attribute
(
'innerHTML'
)
self
.
assertEqual
(
actual_html
,
expected_html
)
tests/integration/test_events.py
View file @
69ee413d
...
...
@@ -76,7 +76,7 @@ class EventsFiredTest(DefaultDataTestMixin, ParameterizedTestsMixin, BaseEventsT
@data
(
*
enumerate
(
scenarios
))
# pylint: disable=star-args
@unpack
def
test_event
(
self
,
index
,
event
):
self
.
parameterized_item_positive_feedback_on_good_move
(
self
.
items_map
)
self
.
parameterized_item_positive_feedback_on_good_move
_standard
(
self
.
items_map
)
dummy
,
name
,
published_data
=
self
.
publish
.
call_args_list
[
index
][
0
]
self
.
assertEqual
(
name
,
event
[
'name'
])
self
.
assertEqual
(
published_data
,
event
[
'data'
])
...
...
tests/integration/test_interaction.py
View file @
69ee413d
...
...
@@ -50,35 +50,92 @@ class ParameterizedTestsMixin(object):
ActionChains
(
self
.
browser
)
.
send_keys
(
Keys
.
TAB
)
.
perform
()
self
.
assertFocused
(
go_to_beginning_button
)
def
parameterized_item_positive_feedback_on_good_move
(
self
,
items_map
,
scroll_down
=
100
,
action_key
=
None
,
assessment_mode
=
Fals
e
def
parameterized_item_positive_feedback_on_good_move
_standard
(
self
,
items_map
,
scroll_down
=
100
,
action_key
=
None
,
feedback
=
Non
e
):
if
feedback
is
None
:
feedback
=
self
.
feedback
popup
=
self
.
_get_popup
()
feedback_popup_content
=
self
.
_get_popup_content
()
# Scroll drop zones into view to make sure Selenium can successfully drop items
self
.
scroll_down
(
pixels
=
scroll_down
)
for
definition
in
self
.
_get_items_with_zone
(
items_map
)
.
values
():
items_with_zones
=
self
.
_get_items_with_zone
(
items_map
)
.
values
()
for
i
,
definition
in
enumerate
(
items_with_zones
):
self
.
place_item
(
definition
.
item_id
,
definition
.
zone_ids
[
0
],
action_key
)
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_mod
e
)
self
.
assert_placed_item
(
definition
.
item_id
,
definition
.
zone_title
,
assessment_mode
=
Fals
e
)
feedback_popup_html
=
feedback_popup_content
.
get_attribute
(
'innerHTML'
)
if
assessment_mode
:
self
.
assertEqual
(
feedback_popup_html
,
''
)
self
.
assertFalse
(
popup
.
is_displayed
())
if
action_key
:
# Next TAB keypress should move focus to "Go to Beginning button"
self
.
_test_next_tab_goes_to_go_to_beginning_button
()
self
.
assertEqual
(
feedback_popup_html
,
"<p>{}</p>"
.
format
(
definition
.
feedback_positive
))
self
.
assert_popup_correct
(
popup
)
self
.
assertTrue
(
popup
.
is_displayed
())
expected_sr_texts
=
[
definition
.
feedback_positive
]
if
i
==
len
(
items_with_zones
)
-
1
:
# We just dropped the last item, so the problem is done and we should see the final feedback.
overall_feedback
=
feedback
[
'final'
]
else
:
self
.
assertEqual
(
feedback_popup_html
,
"<p>{}</p>"
.
format
(
definition
.
feedback_positive
))
self
.
assert_popup_correct
(
popup
)
overall_feedback
=
feedback
[
'intro'
]
expected_sr_texts
.
append
(
overall_feedback
)
self
.
assert_reader_feedback_messages
(
expected_sr_texts
)
self
.
_test_popup_focus_and_close
(
popup
,
action_key
)
def
parameterized_item_positive_feedback_on_good_move_assessment
(
self
,
items_map
,
scroll_down
=
100
,
action_key
=
None
,
feedback
=
None
):
if
feedback
is
None
:
feedback
=
self
.
feedback
popup
=
self
.
_get_popup
()
feedback_popup_content
=
self
.
_get_popup_content
()
# Scroll drop zones into view to make sure Selenium can successfully drop items
self
.
scroll_down
(
pixels
=
scroll_down
)
items_with_zones
=
self
.
_get_items_with_zone
(
items_map
)
.
values
()
for
definition
in
items_with_zones
:
self
.
place_item
(
definition
.
item_id
,
definition
.
zone_ids
[
0
],
action_key
)
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
=
True
)
feedback_popup_html
=
feedback_popup_content
.
get_attribute
(
'innerHTML'
)
self
.
assertEqual
(
feedback_popup_html
,
''
)
self
.
assertFalse
(
popup
.
is_displayed
())
self
.
assert_reader_feedback_messages
([])
if
action_key
:
# Next TAB keypress should move focus to "Go to Beginning button"
self
.
_test_next_tab_goes_to_go_to_beginning_button
()
def
parameterized_item_negative_feedback_on_bad_move_standard
(
self
,
items_map
,
all_zones
,
scroll_down
=
100
,
action_key
=
None
,
feedback
=
None
):
if
feedback
is
None
:
feedback
=
self
.
feedback
popup
=
self
.
_get_popup
()
feedback_popup_content
=
self
.
_get_popup_content
()
# Scroll drop zones into view to make sure Selenium can successfully drop items
self
.
scroll_down
(
pixels
=
scroll_down
)
for
definition
in
items_map
.
values
():
zone_id
,
_
=
self
.
_get_incorrect_zone_for_item
(
definition
,
all_zones
)
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
)
self
.
wait_until_html_in
(
definition
.
feedback_negative
,
feedback_popup_content
)
self
.
assert_popup_incorrect
(
popup
)
self
.
assertTrue
(
popup
.
is_displayed
())
self
.
assert_reverted_item
(
definition
.
item_id
)
expected_sr_texts
=
[
definition
.
feedback_negative
,
feedback
[
'intro'
]]
self
.
assert_reader_feedback_messages
(
expected_sr_texts
)
self
.
_test_popup_focus_and_close
(
popup
,
action_key
)
def
parameterized_item_negative_feedback_on_bad_move
(
self
,
items_map
,
all_zones
,
scroll_down
=
100
,
action_key
=
None
,
assessment_mode
=
Fals
e
def
parameterized_item_negative_feedback_on_bad_move
_assessment
(
self
,
items_map
,
all_zones
,
scroll_down
=
100
,
action_key
=
None
,
feedback
=
Non
e
):
if
feedback
is
None
:
feedback
=
self
.
feedback
popup
=
self
.
_get_popup
()
feedback_popup_content
=
self
.
_get_popup_content
()
...
...
@@ -86,30 +143,17 @@ class ParameterizedTestsMixin(object):
self
.
scroll_down
(
pixels
=
scroll_down
)
for
definition
in
items_map
.
values
():
# Get first zone that is not correct for this item.
zone_id
=
None
zone_title
=
None
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
zone_id
,
zone_title
=
self
.
_get_incorrect_zone_for_item
(
definition
,
all_zones
)
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
)
if
action_key
:
self
.
_test_next_tab_goes_to_go_to_beginning_button
()
else
:
self
.
wait_until_html_in
(
definition
.
feedback_negative
,
feedback_popup_content
)
self
.
assert_popup_incorrect
(
popup
)
self
.
assertTrue
(
popup
.
is_displayed
())
self
.
assert_reverted_item
(
definition
.
item_id
)
self
.
_test_popup_focus_and_close
(
popup
,
action_key
)
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
)
self
.
assert_reader_feedback_messages
([])
if
action_key
:
self
.
_test_next_tab_goes_to_go_to_beginning_button
()
def
parameterized_move_items_between_zones
(
self
,
items_map
,
all_zones
,
scroll_down
=
100
,
action_key
=
None
):
# Scroll drop zones into view to make sure Selenium can successfully drop items
...
...
@@ -247,11 +291,13 @@ class StandardInteractionTest(DefaultDataTestMixin, InteractionTestBase, Paramet
"""
@data
(
*
ITEM_DRAG_KEYBOARD_KEYS
)
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
)
self
.
parameterized_item_positive_feedback_on_good_move
_standard
(
self
.
items_map
,
action_key
=
action_key
)
@data
(
*
ITEM_DRAG_KEYBOARD_KEYS
)
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
)
self
.
parameterized_item_negative_feedback_on_bad_move_standard
(
self
.
items_map
,
self
.
all_zones
,
action_key
=
action_key
)
@data
(
*
ITEM_DRAG_KEYBOARD_KEYS
)
def
test_cannot_move_items_between_zones
(
self
,
action_key
):
...
...
@@ -332,6 +378,23 @@ class StandardInteractionTest(DefaultDataTestMixin, InteractionTestBase, Paramet
# After placing all items, we get the full score.
self
.
assertEqual
(
progress
.
text
,
'1/1 point (ungraded)'
)
@data
(
*
ITEM_DRAG_KEYBOARD_KEYS
)
def
test_cannot_select_multiple_items
(
self
,
action_key
):
if
action_key
:
all_item_ids
=
self
.
items_map
.
keys
()
# Go through all items and select them all using the keyboard action key.
for
item_id
in
all_item_ids
:
item
=
self
.
_get_item_by_value
(
item_id
)
item
.
send_keys
(
''
)
item
.
send_keys
(
action_key
)
# Item should be grabbed.
self
.
assert_item_grabbed
(
item
)
# Other items should NOT be grabbed.
for
other_item_id
in
all_item_ids
:
if
other_item_id
!=
item_id
:
other_item
=
self
.
_get_item_by_value
(
other_item_id
)
self
.
assert_item_not_grabbed
(
other_item
)
class
MultipleValidOptionsInteractionTest
(
DefaultDataTestMixin
,
InteractionTestBase
,
BaseIntegrationTest
):
...
...
@@ -477,16 +540,22 @@ class MultipleBlocksDataInteraction(ParameterizedTestsMixin, InteractionTestBase
def
test_item_positive_feedback_on_good_move
(
self
):
self
.
_switch_to_block
(
0
)
self
.
parameterized_item_positive_feedback_on_good_move
(
self
.
item_maps
[
'block1'
])
self
.
parameterized_item_positive_feedback_on_good_move_standard
(
self
.
item_maps
[
'block1'
],
feedback
=
self
.
feedback
[
'block1'
]
)
self
.
_switch_to_block
(
1
)
self
.
parameterized_item_positive_feedback_on_good_move
(
self
.
item_maps
[
'block2'
],
scroll_down
=
1000
)
self
.
parameterized_item_positive_feedback_on_good_move_standard
(
self
.
item_maps
[
'block2'
],
feedback
=
self
.
feedback
[
'block2'
],
scroll_down
=
1000
)
def
test_item_negative_feedback_on_bad_move
(
self
):
self
.
_switch_to_block
(
0
)
self
.
parameterized_item_negative_feedback_on_bad_move
(
self
.
item_maps
[
'block1'
],
self
.
all_zones
[
'block1'
])
self
.
parameterized_item_negative_feedback_on_bad_move_standard
(
self
.
item_maps
[
'block1'
],
self
.
all_zones
[
'block1'
],
feedback
=
self
.
feedback
[
'block1'
]
)
self
.
_switch_to_block
(
1
)
self
.
parameterized_item_negative_feedback_on_bad_move
(
self
.
item_maps
[
'block2'
],
self
.
all_zones
[
'block2'
],
scroll_down
=
1000
self
.
parameterized_item_negative_feedback_on_bad_move
_standard
(
self
.
item_maps
[
'block2'
],
self
.
all_zones
[
'block2'
],
feedback
=
self
.
feedback
[
'block2'
],
scroll_down
=
1000
)
def
test_final_feedback_and_reset
(
self
):
...
...
tests/integration/test_interaction_assessment.py
View file @
69ee413d
...
...
@@ -81,14 +81,12 @@ class AssessmentInteractionTest(
"""
@data
(
*
ITEM_DRAG_KEYBOARD_KEYS
)
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
)
self
.
parameterized_item_positive_feedback_on_good_move_assessment
(
self
.
items_map
,
action_key
=
action_key
)
@data
(
*
ITEM_DRAG_KEYBOARD_KEYS
)
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
self
.
parameterized_item_negative_feedback_on_bad_move
_assessment
(
self
.
items_map
,
self
.
all_zones
,
action_key
=
action_key
)
@data
(
*
ITEM_DRAG_KEYBOARD_KEYS
)
...
...
@@ -250,6 +248,18 @@ class AssessmentInteractionTest(
"""
Test updating overall feedback after submitting solution in assessment mode
"""
def
check_feedback
(
overall_feedback_lines
,
per_item_feedback_lines
=
None
):
# Check that the feedback is correctly displayed in the overall feedback area.
expected_overall_feedback
=
"
\n
"
.
join
([
"FEEDBACK"
]
+
overall_feedback_lines
)
self
.
assertEqual
(
self
.
_get_feedback
()
.
text
,
expected_overall_feedback
)
# Check that the SR.readText function was passed correct feedback messages.
sr_feedback_lines
=
overall_feedback_lines
if
per_item_feedback_lines
:
sr_feedback_lines
+=
[
"Some of your answers were not correct."
,
"Hints:"
]
sr_feedback_lines
+=
per_item_feedback_lines
self
.
assert_reader_feedback_messages
(
sr_feedback_lines
)
# used keyboard mode to avoid bug/feature with selenium "selecting" everything instead of dragging an element
self
.
place_item
(
0
,
TOP_ZONE_ID
,
Keys
.
RETURN
)
...
...
@@ -261,29 +271,25 @@ class AssessmentInteractionTest(
expected_grade
=
2.0
/
5.0
feedback_lines
=
[
"FEEDBACK"
,
FeedbackMessages
.
correctly_placed
(
1
),
FeedbackMessages
.
not_placed
(
3
),
START_FEEDBACK
,
FeedbackMessages
.
GRADE_FEEDBACK_TPL
.
format
(
score
=
expected_grade
)
]
expected_feedback
=
"
\n
"
.
join
(
feedback_lines
)
self
.
assertEqual
(
self
.
_get_feedback
()
.
text
,
expected_feedback
)
check_feedback
(
feedback_lines
)
# Place the item into incorrect zone. The score does not change.
self
.
place_item
(
1
,
BOTTOM_ZONE_ID
,
Keys
.
RETURN
)
self
.
click_submit
()
feedback_lines
=
[
"FEEDBACK"
,
FeedbackMessages
.
correctly_placed
(
1
),
FeedbackMessages
.
misplaced_returned
(
1
),
FeedbackMessages
.
not_placed
(
2
),
START_FEEDBACK
,
FeedbackMessages
.
GRADE_FEEDBACK_TPL
.
format
(
score
=
expected_grade
)
]
expected_feedback
=
"
\n
"
.
join
(
feedback_lines
)
self
.
assertEqual
(
self
.
_get_feedback
()
.
text
,
expected_feedback
)
check_feedback
(
feedback_lines
,
[
"No, this item does not belong here. Try again."
])
# reach final attempt
for
_
in
xrange
(
self
.
MAX_ATTEMPTS
-
3
):
...
...
@@ -299,13 +305,11 @@ class AssessmentInteractionTest(
expected_grade
=
1.0
feedback_lines
=
[
"FEEDBACK"
,
FeedbackMessages
.
correctly_placed
(
4
),
FINISH_FEEDBACK
,
FeedbackMessages
.
FINAL_ATTEMPT_TPL
.
format
(
score
=
expected_grade
)
]
expected_feedback
=
"
\n
"
.
join
(
feedback_lines
)
self
.
assertEqual
(
self
.
_get_feedback
()
.
text
,
expected_feedback
)
check_feedback
(
feedback_lines
)
def
test_per_item_feedback_multiple_misplaced
(
self
):
self
.
place_item
(
0
,
MIDDLE_ZONE_ID
,
Keys
.
RETURN
)
...
...
tests/integration/test_render.py
View file @
69ee413d
...
...
@@ -194,7 +194,7 @@ class TestDragAndDropRender(BaseIntegrationTest):
'#-Zone_{}'
.
format
(
zone_number
),
**
zone_box_percentages
)
zone_name
=
zone
.
find_element_by_css_selector
(
'p.zone-name'
)
self
.
assertEqual
(
zone_name
.
text
,
'Zone {}'
.
format
(
zone_number
))
self
.
assertEqual
(
zone_name
.
text
,
'Zone {}
\n
, dropzone
'
.
format
(
zone_number
))
zone_description
=
zone
.
find_element_by_css_selector
(
'p.zone-description'
)
self
.
assertEqual
(
zone_description
.
text
,
'This describes zone {}'
.
format
(
zone_number
))
# Zone description should only be visible to screen readers:
...
...
@@ -204,12 +204,10 @@ class TestDragAndDropRender(BaseIntegrationTest):
self
.
load_scenario
()
popup
=
self
.
_get_popup
()
popup_wrapper
=
self
.
_get_popup_wrapper
()
popup_content
=
self
.
_get_popup_content
()
self
.
assertFalse
(
popup
.
is_displayed
())
self
.
assertIn
(
'popup'
,
popup
.
get_attribute
(
'class'
))
self
.
assertEqual
(
popup_content
.
text
,
""
)
self
.
assertEqual
(
popup_wrapper
.
get_attribute
(
'aria-live'
),
'polite'
)
@data
(
None
,
Keys
.
RETURN
)
def
test_go_to_beginning_button
(
self
,
action_key
):
...
...
@@ -253,9 +251,7 @@ class TestDragAndDropRender(BaseIntegrationTest):
def
test_feedback
(
self
):
self
.
load_scenario
()
feedback
=
self
.
_get_feedback
()
feedback_message
=
self
.
_get_feedback_message
()
self
.
assertEqual
(
feedback
.
get_attribute
(
'aria-live'
),
'polite'
)
self
.
assertEqual
(
feedback_message
.
text
,
START_FEEDBACK
)
def
test_background_image
(
self
):
...
...
tests/integration/test_title_and_question.py
View file @
69ee413d
...
...
@@ -26,10 +26,10 @@ class TestDragAndDropTitleAndProblem(BaseIntegrationTest):
self
.
addCleanup
(
scenarios
.
remove_scenario
,
const_page_id
)
page
=
self
.
go_to_page
(
const_page_name
)
is_problem_header_visible
=
len
(
page
.
find_elements_by_css_selector
(
'
section
.problem > h4'
))
>
0
is_problem_header_visible
=
len
(
page
.
find_elements_by_css_selector
(
'.problem > h4'
))
>
0
self
.
assertEqual
(
is_problem_header_visible
,
show_problem_header
)
problem
=
page
.
find_element_by_css_selector
(
'
section
.problem > p'
)
problem
=
page
.
find_element_by_css_selector
(
'.problem > p'
)
self
.
assertEqual
(
self
.
get_element_html
(
problem
),
problem_text
)
@unpack
...
...
tests/unit/test_basics.py
View file @
69ee413d
...
...
@@ -45,7 +45,7 @@ class BasicTests(TestCaseMixin, unittest.TestCase):
def
test_template_contents
(
self
):
context
=
{}
student_fragment
=
self
.
block
.
runtime
.
render
(
self
.
block
,
'student_view'
,
context
)
self
.
assertIn
(
'<
section
class="themed-xblock xblock--drag-and-drop">'
,
student_fragment
.
content
)
self
.
assertIn
(
'<
div
class="themed-xblock xblock--drag-and-drop">'
,
student_fragment
.
content
)
self
.
assertIn
(
'Loading drag and drop problem.'
,
student_fragment
.
content
)
def
test_get_configuration
(
self
):
...
...
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