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:
...
@@ -12,7 +12,7 @@ install:
-
"
pip
install
selenium==2.53.0"
-
"
pip
install
selenium==2.53.0"
-
"
pip
uninstall
-y
xblock-drag-and-drop-v2"
-
"
pip
uninstall
-y
xblock-drag-and-drop-v2"
-
"
python
setup.py
sdist"
-
"
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
:
script
:
-
pep8 drag_and_drop_v2 tests --max-line-length=120
-
pep8 drag_and_drop_v2 tests --max-line-length=120
-
pylint drag_and_drop_v2
-
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)
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) {
...
@@ -8,7 +8,8 @@ function DragAndDropTemplates(configuration) {
}
}
return
(
return
(
h
(
"div.spinner-wrapper"
,
{
key
:
item
.
value
+
'-spinner'
},
[
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) {
...
@@ -53,7 +54,6 @@ function DragAndDropTemplates(configuration) {
var
itemTemplate
=
function
(
item
,
ctx
)
{
var
itemTemplate
=
function
(
item
,
ctx
)
{
// Define properties
// Define properties
var
descriptionClassName
=
"sr description"
;
var
className
=
(
item
.
class_name
)
?
item
.
class_name
:
""
;
var
className
=
(
item
.
class_name
)
?
item
.
class_name
:
""
;
var
zone
=
getZone
(
item
.
zone
,
ctx
)
||
{};
var
zone
=
getZone
(
item
.
zone
,
ctx
)
||
{};
if
(
item
.
has_image
)
{
if
(
item
.
has_image
)
{
...
@@ -119,8 +119,8 @@ function DragAndDropTemplates(configuration) {
...
@@ -119,8 +119,8 @@ function DragAndDropTemplates(configuration) {
var
item_description_id
=
configuration
.
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
};
item_description
=
h
(
item_description
=
h
(
'div'
,
'div
.sr.description
'
,
{
key
:
item
.
value
+
'-description'
,
id
:
item_description_id
,
className
:
descriptionClassName
},
{
key
:
item
_description_id
,
id
:
item_description_id
},
description_content
description_content
);
);
}
}
...
@@ -185,7 +185,7 @@ function DragAndDropTemplates(configuration) {
...
@@ -185,7 +185,7 @@ function DragAndDropTemplates(configuration) {
var
item_wrapper
=
'div.item-wrapper.item-align.item-align-'
+
zone
.
align
;
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
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
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
)
{
if
(
items_in_zone
.
length
==
0
)
{
var
zone_description
=
h
(
var
zone_description
=
h
(
'div'
,
'div'
,
...
@@ -221,8 +221,15 @@ function DragAndDropTemplates(configuration) {
...
@@ -221,8 +221,15 @@ function DragAndDropTemplates(configuration) {
}
}
},
},
[
[
h
(
'p'
,
{
className
:
className
},
zone
.
title
),
h
(
h
(
'p'
,
{
className
:
'zone-description sr'
},
zone
.
description
||
gettext
(
"droppable"
)),
'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
)),
h
(
item_wrapper
,
renderCollection
(
itemTemplate
,
items_in_zone
,
ctx
)),
zone_description
zone_description
]
]
...
@@ -242,10 +249,10 @@ function DragAndDropTemplates(configuration) {
...
@@ -242,10 +249,10 @@ function DragAndDropTemplates(configuration) {
});
});
return
(
return
(
h
(
'
section.feedback'
,
{
},
[
h
(
'
div.feedback'
,
{
attributes
:
{
'role'
:
'group'
,
'aria-label'
:
gettext
(
'Feedback'
)}
},
[
h
(
h
(
"div.feedback-content"
,
"div.feedback-content"
,
{
attributes
:
{
'aria-live'
:
'polite'
}
},
{},
[
[
h
(
'h3.title1'
,
{
style
:
{
display
:
feedback_display
}
},
gettext
(
'Feedback'
)),
h
(
'h3.title1'
,
{
style
:
{
display
:
feedback_display
}
},
gettext
(
'Feedback'
)),
h
(
'div.messages'
,
{
style
:
{
display
:
feedback_display
}
},
feedback_messages
),
h
(
'div.messages'
,
{
style
:
{
display
:
feedback_display
}
},
feedback_messages
),
...
@@ -286,27 +293,42 @@ function DragAndDropTemplates(configuration) {
...
@@ -286,27 +293,42 @@ function DragAndDropTemplates(configuration) {
};
};
var
submitAnswerTemplate
=
function
(
ctx
)
{
var
submitAnswerTemplate
=
function
(
ctx
)
{
var
attemptsUsedId
=
"attempts-used-"
+
configuration
.
url_name
;
var
submitButtonProperties
=
{
var
attemptsUsedDisplay
=
(
ctx
.
max_attempts
&&
ctx
.
max_attempts
>
0
)
?
'inline'
:
'none'
;
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
(
return
(
h
(
"section.action-toolbar-item.submit-answer"
,
{},
[
h
(
"div.action-toolbar-item.submit-answer"
,
{},
[
h
(
h
(
"button.btn-brand.submit-answer-button"
,
"button.btn-brand.submit-answer-button"
,
{
submitButtonProperties
,
disabled
:
ctx
.
disable_submit_button
||
ctx
.
submit_spinner
,
[
attributes
:
{
"aria-describedby"
:
attemptsUsedId
}},
submitSpinner
,
[
' '
,
// whitespace between spinner icon and text
(
ctx
.
submit_spinner
?
h
(
"span.fa.fa-spin.fa-spinner"
)
:
null
),
gettext
(
"Submit"
)
gettext
(
"Submit"
)
]
]
),
),
attemptsUsedInfo
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
)
)
])
);
);
};
};
...
@@ -351,7 +373,7 @@ function DragAndDropTemplates(configuration) {
...
@@ -351,7 +373,7 @@ function DragAndDropTemplates(configuration) {
go_to_beginning_button_class
+=
' sr'
;
go_to_beginning_button_class
+=
' sr'
;
}
}
return
(
return
(
h
(
"
section
.action-toolbar-item.sidebar-buttons"
,
{},
[
h
(
"
div
.action-toolbar-item.sidebar-buttons"
,
{},
[
sidebarButtonTemplate
(
sidebarButtonTemplate
(
go_to_beginning_button_class
,
go_to_beginning_button_class
,
"fa-arrow-up"
,
"fa-arrow-up"
,
...
@@ -397,13 +419,7 @@ function DragAndDropTemplates(configuration) {
...
@@ -397,13 +419,7 @@ function DragAndDropTemplates(configuration) {
return
h
(
return
h
(
popupSelector
,
popupSelector
,
{
{
style
:
{
display
:
have_messages
?
'block'
:
'none'
},
style
:
{
display
:
have_messages
?
'block'
:
'none'
}
attributes
:
{
"tabindex"
:
"-1"
,
'aria-live'
:
'polite'
,
'aria-atomic'
:
'true'
,
'aria-relevant'
:
'additions'
,
}
},
},
[
[
h
(
h
(
...
@@ -491,18 +507,24 @@ function DragAndDropTemplates(configuration) {
...
@@ -491,18 +507,24 @@ function DragAndDropTemplates(configuration) {
return
h
(
'div.problem-progress'
,
{
return
h
(
'div.problem-progress'
,
{
id
:
configuration
.
url_name
+
'-problem-progress'
,
id
:
configuration
.
url_name
+
'-problem-progress'
,
attributes
:
{
'role'
:
'status'
,
'aria-live'
:
'polite
'
}
attributes
:
{
role
:
'status
'
}
},
progress_text
);
},
progress_text
);
};
};
var
mainTemplate
=
function
(
ctx
)
{
var
mainTemplate
=
function
(
ctx
)
{
var
main_element_properties
=
{
attributes
:
{
role
:
'group'
}};
var
problemProgress
=
progressTemplate
(
ctx
);
var
problemProgress
=
progressTemplate
(
ctx
);
var
problemTitle
=
null
;
var
problemTitle
=
null
;
if
(
ctx
.
show_title
)
{
if
(
ctx
.
show_title
)
{
var
problem_title_id
=
configuration
.
url_name
+
'-problem-title'
;
problemTitle
=
h
(
'h3.problem-title'
,
{
problemTitle
=
h
(
'h3.problem-title'
,
{
id
:
problem_title_id
,
innerHTML
:
ctx
.
title_html
,
innerHTML
:
ctx
.
title_html
,
attributes
:
{
'aria-describedby'
:
problemProgress
.
properties
.
id
}
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
;
var
problemHeader
=
ctx
.
show_problem_header
?
h
(
'h4.title1'
,
gettext
(
'Problem'
))
:
null
;
// Render only items in the bank here, including placeholders. Placed
// Render only items in the bank here, including placeholders. Placed
...
@@ -523,36 +545,37 @@ function DragAndDropTemplates(configuration) {
...
@@ -523,36 +545,37 @@ function DragAndDropTemplates(configuration) {
item_bank_properties
.
attributes
[
'role'
]
=
'button'
;
item_bank_properties
.
attributes
[
'role'
]
=
'button'
;
}
}
return
(
return
(
h
(
'
section.themed-xblock.xblock--drag-and-drop'
,
[
h
(
'
div.themed-xblock.xblock--drag-and-drop'
,
main_element_properties
,
[
problemTitle
,
problemTitle
,
problemProgress
,
problemProgress
,
h
(
'div'
,
[
forwardKeyboardHelpButtonTemplate
(
ctx
)]),
h
(
'div'
,
[
forwardKeyboardHelpButtonTemplate
(
ctx
)]),
h
(
'
section
.problem'
,
[
h
(
'
div
.problem'
,
[
problemHeader
,
problemHeader
,
h
(
'p'
,
{
innerHTML
:
ctx
.
problem_html
}),
h
(
'p'
,
{
innerHTML
:
ctx
.
problem_html
}),
]),
]),
h
(
'
section
.drag-container'
,
{},
[
h
(
'
div
.drag-container'
,
{},
[
h
(
'div.item-bank'
,
item_bank_properties
,
[
h
(
'div.item-bank'
,
item_bank_properties
,
[
renderCollection
(
itemTemplate
,
items_in_bank
,
ctx
),
renderCollection
(
itemTemplate
,
items_in_bank
,
ctx
),
renderCollection
(
itemPlaceholderTemplate
,
items_placed
,
ctx
)
renderCollection
(
itemPlaceholderTemplate
,
items_placed
,
ctx
)
]),
]),
h
(
'div.target'
,
h
(
'div.target'
,
{
attributes
:
{
'role'
:
'group'
,
'arial-label'
:
gettext
(
'Drop Targets'
)}},
[
{},
itemFeedbackPopupTemplate
(
ctx
),
[
h
(
'div.target-img-wrapper'
,
[
itemFeedbackPopupTemplate
(
ctx
),
h
(
'img.target-img'
,
{
src
:
ctx
.
target_img_src
,
alt
:
ctx
.
target_img_description
}),
h
(
'div.target-img-wrapper'
,
[
]),
h
(
'img.target-img'
,
{
src
:
ctx
.
target_img_src
,
alt
:
ctx
.
target_img_description
}),
]
),
renderCollection
(
zoneTemplate
,
ctx
.
zones
,
ctx
)
renderCollection
(
zoneTemplate
,
ctx
.
zones
,
ctx
)
]),
]),
]),
]),
h
(
"
section.actions-toolbar"
,
{
},
[
h
(
"
div.actions-toolbar"
,
{
attributes
:
{
'role'
:
'group'
,
'aria-label'
:
gettext
(
'Actions'
)}
},
[
sidebarTemplate
(
ctx
),
sidebarTemplate
(
ctx
),
(
ctx
.
show_submit_answer
?
submitAnswerTemplate
(
ctx
)
:
null
),
(
ctx
.
show_submit_answer
?
submitAnswerTemplate
(
ctx
)
:
null
),
]),
]),
keyboardHelpPopupTemplate
(
ctx
),
keyboardHelpPopupTemplate
(
ctx
),
feedbackTemplate
(
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) {
...
@@ -664,6 +687,9 @@ function DragAndDropBlock(runtime, element, configuration) {
// to watch for load events on any child element, since load events do not bubble.
// to watch for load events on any child element, since load events do not bubble.
element
.
addEventListener
(
'load'
,
webkitFix
,
true
);
element
.
addEventListener
(
'load'
,
webkitFix
,
true
);
// Remove the spinner and create a blank slate for virtualDom to take over.
$root
.
empty
();
applyState
();
applyState
();
initDroppable
();
initDroppable
();
...
@@ -904,7 +930,7 @@ function DragAndDropBlock(runtime, element, configuration) {
...
@@ -904,7 +930,7 @@ function DragAndDropBlock(runtime, element, configuration) {
return
feedback_msgs_list
.
map
(
function
(
message
)
{
return
message
.
message
;
}).
join
(
'
\
n'
);
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
new_vdom
=
render
(
state
);
var
patches
=
virtualDom
.
diff
(
__vdom
,
new_vdom
);
var
patches
=
virtualDom
.
diff
(
__vdom
,
new_vdom
);
root
=
virtualDom
.
patch
(
root
,
patches
);
root
=
virtualDom
.
patch
(
root
,
patches
);
...
@@ -912,6 +938,51 @@ function DragAndDropBlock(runtime, element, configuration) {
...
@@ -912,6 +938,51 @@ function DragAndDropBlock(runtime, element, configuration) {
__vdom
=
new_vdom
;
__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
)
{
var
publishEvent
=
function
(
data
)
{
$
.
ajax
({
$
.
ajax
({
type
:
'POST'
,
type
:
'POST'
,
...
@@ -981,16 +1052,18 @@ function DragAndDropBlock(runtime, element, configuration) {
...
@@ -981,16 +1052,18 @@ function DragAndDropBlock(runtime, element, configuration) {
return
false
;
return
false
;
};
};
var
placeItem
=
function
(
$zone
,
$item
)
{
var
placeGrabbedItem
=
function
(
$zone
)
{
var
item_id
;
if
(
$item
!==
undefined
)
{
item_id
=
$item
.
data
(
'value'
);
}
else
{
item_id
=
$selectedItem
.
data
(
'value'
);
}
var
zone
=
String
(
$zone
.
data
(
'uid'
));
var
zone
=
String
(
$zone
.
data
(
'uid'
));
var
zone_align
=
$zone
.
data
(
'zone_align'
);
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
()]);
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
)
{
if
(
configuration
.
max_items_per_zone
&&
configuration
.
max_items_per_zone
<=
items_in_zone_count
)
{
...
@@ -1036,18 +1109,17 @@ function DragAndDropBlock(runtime, element, configuration) {
...
@@ -1036,18 +1109,17 @@ function DragAndDropBlock(runtime, element, configuration) {
}
else
if
(
isCancelKey
(
evt
))
{
}
else
if
(
isCancelKey
(
evt
))
{
evt
.
preventDefault
();
evt
.
preventDefault
();
state
.
keyboard_placement_mode
=
false
;
state
.
keyboard_placement_mode
=
false
;
release
Item
(
$selectedItem
);
release
GrabbedItems
(
);
}
else
if
(
isActionKey
(
evt
))
{
}
else
if
(
isActionKey
(
evt
))
{
evt
.
preventDefault
();
evt
.
preventDefault
();
evt
.
stopPropagation
();
evt
.
stopPropagation
();
state
.
keyboard_placement_mode
=
false
;
state
.
keyboard_placement_mode
=
false
;
releaseItem
(
$selectedItem
);
if
(
$zone
.
is
(
'.item-bank'
))
{
if
(
$zone
.
is
(
'.item-bank'
))
{
delete
state
.
items
[
$selectedItem
.
data
(
'value'
)];
delete
state
.
items
[
$selectedItem
.
data
(
'value'
)];
applyState
();
}
else
{
}
else
{
placeItem
(
$zone
);
place
Grabbed
Item
(
$zone
);
}
}
releaseGrabbedItems
();
}
}
}
else
if
(
isTabKey
(
evt
)
&&
!
evt
.
shiftKey
)
{
}
else
if
(
isTabKey
(
evt
)
&&
!
evt
.
shiftKey
)
{
// If the user just dropped an item to this zone, next TAB keypress
// If the user just dropped an item to this zone, next TAB keypress
...
@@ -1074,8 +1146,7 @@ function DragAndDropBlock(runtime, element, configuration) {
...
@@ -1074,8 +1146,7 @@ function DragAndDropBlock(runtime, element, configuration) {
tolerance
:
'pointer'
,
tolerance
:
'pointer'
,
drop
:
function
(
evt
,
ui
)
{
drop
:
function
(
evt
,
ui
)
{
var
$zone
=
$
(
this
);
var
$zone
=
$
(
this
);
var
$item
=
ui
.
helper
;
placeGrabbedItem
(
$zone
);
placeItem
(
$zone
,
$item
);
}
}
});
});
...
@@ -1087,7 +1158,7 @@ function DragAndDropBlock(runtime, element, configuration) {
...
@@ -1087,7 +1158,7 @@ function DragAndDropBlock(runtime, element, configuration) {
drop
:
function
(
evt
,
ui
)
{
drop
:
function
(
evt
,
ui
)
{
var
$item
=
ui
.
helper
;
var
$item
=
ui
.
helper
;
var
item_id
=
$item
.
data
(
'value'
);
var
item_id
=
$item
.
data
(
'value'
);
release
Item
(
$item
);
release
GrabbedItems
(
);
delete
state
.
items
[
item_id
];
delete
state
.
items
[
item_id
];
applyState
();
applyState
();
}
}
...
@@ -1140,7 +1211,7 @@ function DragAndDropBlock(runtime, element, configuration) {
...
@@ -1140,7 +1211,7 @@ function DragAndDropBlock(runtime, element, configuration) {
stop
:
function
(
evt
,
ui
)
{
stop
:
function
(
evt
,
ui
)
{
// Revert to original position.
// Revert to original position.
$item
.
css
(
$item
.
data
(
'initial-position'
));
$item
.
css
(
$item
.
data
(
'initial-position'
));
release
Item
(
$
(
this
)
);
release
GrabbedItems
(
);
}
}
});
});
}
catch
(
e
)
{
}
catch
(
e
)
{
...
@@ -1152,31 +1223,27 @@ function DragAndDropBlock(runtime, element, configuration) {
...
@@ -1152,31 +1223,27 @@ function DragAndDropBlock(runtime, element, configuration) {
var
grabItem
=
function
(
$item
,
interaction_type
)
{
var
grabItem
=
function
(
$item
,
interaction_type
)
{
var
item_id
=
$item
.
data
(
'value'
);
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
);
closePopup
(
false
);
// applyState(true) skips destroying and initializing draggable
// applyState(true) skips destroying and initializing draggable
applyState
(
true
);
applyState
(
true
);
};
};
var
releaseItem
=
function
(
$item
)
{
var
releaseGrabbedItems
=
function
()
{
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
)
{
configuration
.
items
.
forEach
(
function
(
item
)
{
configuration
.
items
.
forEach
(
function
(
item
)
{
if
(
item
.
id
===
item_id
)
{
item
.
grabbed
=
false
;
if
(
grabbed
)
{
delete
item
.
grabbed_with
;
item
.
grabbed
=
true
;
item
.
grabbed_with
=
interaction_type
;
}
else
{
item
.
grabbed
=
false
;
delete
item
.
grabbed_with
;
}
}
});
});
// applyState(true) skips destroying and initializing draggable
applyState
(
true
);
};
};
var
destroyDraggable
=
function
()
{
var
destroyDraggable
=
function
()
{
...
@@ -1220,6 +1287,7 @@ function DragAndDropBlock(runtime, element, configuration) {
...
@@ -1220,6 +1287,7 @@ function DragAndDropBlock(runtime, element, configuration) {
state
.
finished
=
true
;
state
.
finished
=
true
;
state
.
overall_feedback
=
data
.
overall_feedback
;
state
.
overall_feedback
=
data
.
overall_feedback
;
}
}
setScreenReaderMessages
();
}
}
applyState
();
applyState
();
if
(
state
.
feedback
&&
state
.
feedback
.
length
>
0
)
{
if
(
state
.
feedback
&&
state
.
feedback
.
length
>
0
)
{
...
@@ -1324,6 +1392,7 @@ function DragAndDropBlock(runtime, element, configuration) {
...
@@ -1324,6 +1392,7 @@ function DragAndDropBlock(runtime, element, configuration) {
}
else
{
}
else
{
state
.
finished
=
true
;
state
.
finished
=
true
;
}
}
setScreenReaderMessages
();
}).
always
(
function
()
{
}).
always
(
function
()
{
state
.
submit_spinner
=
false
;
state
.
submit_spinner
=
false
;
applyState
();
applyState
();
...
@@ -1451,7 +1520,8 @@ function DragAndDropBlock(runtime, element, configuration) {
...
@@ -1451,7 +1520,8 @@ function DragAndDropBlock(runtime, element, configuration) {
showing_answer
:
state
.
showing_answer
,
showing_answer
:
state
.
showing_answer
,
show_answer_spinner
:
state
.
show_answer_spinner
,
show_answer_spinner
:
state
.
show_answer_spinner
,
disable_go_to_beginning_button
:
!
canGoToBeginning
(),
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
);
return
renderView
(
context
);
...
...
drag_and_drop_v2/templates/html/drag_and_drop.html
View file @
69ee413d
{% load i18n %}
{% load i18n %}
<section
class=
"themed-xblock xblock--drag-and-drop"
>
<div
class=
"themed-xblock xblock--drag-and-drop"
>
<i
class=
"fa fa-spin fa-spinner initial-load-spinner"
></i>
{% trans "Loading drag and drop problem." %}
<span
class=
"fa fa-spin fa-spinner initial-load-spinner"
aria-hidden=
"true"
></span>
</section>
{% 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 @@
...
@@ -4,10 +4,12 @@
<div
class=
"xblock--drag-and-drop--editor editor-with-buttons"
>
<div
class=
"xblock--drag-and-drop--editor editor-with-buttons"
>
{{ js_templates|safe }}
{{ js_templates|safe }}
<section
class=
"drag-builder"
>
<div
class=
"drag-builder"
>
<div
class=
"tab feedback-tab"
>
<section
class=
"tab feedback-tab"
>
<header
class=
"tab-header"
>
<section
class=
"tab-content"
>
<h3>
{% trans "Basic Settings" %}
</h3>
</header>
<div
class=
"tab-content"
>
<form
class=
"feedback-form"
>
<form
class=
"feedback-form"
>
<label
class=
"h4"
>
<label
class=
"h4"
>
<span>
{% trans fields.display_name.display_name %}
</span>
<span>
{% trans fields.display_name.display_name %}
</span>
...
@@ -80,14 +82,14 @@
...
@@ -80,14 +82,14 @@
<textarea
class=
"final-feedback"
>
{{ self.data.feedback.finish }}
</textarea>
<textarea
class=
"final-feedback"
>
{{ self.data.feedback.finish }}
</textarea>
</label>
</label>
</form>
</form>
</
section
>
</
div
>
</
div
>
</
section
>
<
div
class=
"tab zones-tab hidden"
>
<
section
class=
"tab zones-tab hidden"
>
<header
class=
"tab-header"
>
<header
class=
"tab-header"
>
<h3>
{% trans "Zones" %}
</h3>
<h3>
{% trans "Zones" %}
</h3>
</header>
</header>
<
section
class=
"tab-content"
>
<
div
class=
"tab-content"
>
<form
class=
"target-image-form"
>
<form
class=
"target-image-form"
>
<label
class=
"h4"
for=
"background-url-{{id_suffix}}"
>
<label
class=
"h4"
for=
"background-url-{{id_suffix}}"
>
<span>
{% trans "Background URL" %}
</span>
<span>
{% trans "Background URL" %}
</span>
...
@@ -114,8 +116,8 @@
...
@@ -114,8 +116,8 @@
{% endblocktrans %}
{% endblocktrans %}
</div>
</div>
</form>
</form>
</
section
>
</
div
>
<
section
class=
"tab-content"
>
<
div
class=
"tab-content"
>
<form
class=
"display-labels-form"
>
<form
class=
"display-labels-form"
>
<h4>
{% trans "Zone labels" %}
</h4>
<h4>
{% trans "Zone labels" %}
</h4>
<label
class=
"checkbox-label"
>
<label
class=
"checkbox-label"
>
...
@@ -130,8 +132,8 @@
...
@@ -130,8 +132,8 @@
<span>
{% trans "Display zone borders on the image" %}
</span>
<span>
{% trans "Display zone borders on the image" %}
</span>
</label>
</label>
</form>
</form>
</
section
>
</
div
>
<
section
class=
"tab-content"
>
<
div
class=
"tab-content"
>
<h4>
{% trans "Zone definitions" %}
</h4>
<h4>
{% trans "Zone definitions" %}
</h4>
<div
class=
"zone-editor"
>
<div
class=
"zone-editor"
>
<div
class=
"controls"
>
<div
class=
"controls"
>
...
@@ -147,14 +149,14 @@
...
@@ -147,14 +149,14 @@
</div>
</div>
</div>
</div>
</div>
</div>
</
section
>
</
div
>
</
div
>
</
section
>
<
div
class=
"tab items-tab hidden"
>
<
section
class=
"tab items-tab hidden"
>
<header
class=
"tab-header"
>
<header
class=
"tab-header"
>
<h3>
{% trans "Items" %}
</h3>
<h3>
{% trans "Items" %}
</h3>
</header>
</header>
<
section
class=
"tab-content"
>
<
div
class=
"tab-content"
>
<form
class=
"item-styles-form"
>
<form
class=
"item-styles-form"
>
<label
class=
"h4"
>
<label
class=
"h4"
>
<span>
{% trans fields.item_background_color.display_name %}
</span>
<span>
{% trans fields.item_background_color.display_name %}
</span>
...
@@ -184,18 +186,18 @@
...
@@ -184,18 +186,18 @@
{% trans fields.max_items_per_zone.help %}
{% trans fields.max_items_per_zone.help %}
</div>
</div>
</form>
</form>
</
section
>
</
div
>
<
section
class=
"tab-content"
>
<
div
class=
"tab-content"
>
<h4>
{% trans "Item definitions" %}
</h4>
<h4>
{% trans "Item definitions" %}
</h4>
<form
class=
"items-form"
></form>
<form
class=
"items-form"
></form>
<button
class=
"btn add-item add-element"
>
<button
class=
"btn add-item add-element"
>
<span
class=
"icon add"
aria-hidden=
"true"
></span>
<span
class=
"icon add"
aria-hidden=
"true"
></span>
{% trans "Add an item" %}
{% trans "Add an item" %}
</button>
</button>
</
section
>
</
div
>
</
div
>
</
section
>
</
section
>
</
div
>
<div
class=
"xblock-actions"
>
<div
class=
"xblock-actions"
>
<ul
class=
"action-buttons"
>
<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)"
...
@@ -595,6 +595,31 @@ msgid_plural "{possible} points possible (ungraded)"
msgstr[0] ""
msgstr[0] ""
msgstr[1] ""
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
#: utils.py:44
msgid "Your highest score is {score}"
msgid "Your highest score is {score}"
msgstr ""
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) Ⱡ'σяєм ιρѕυ
...
@@ -694,6 +694,31 @@ msgstr[1] "{possible} pöïnts pössïßlé (üngrädéd) Ⱡ'σяєм ιρѕυ
msgid "Close"
msgid "Close"
msgstr "Çlösé Ⱡ'σяєм ιρѕ#"
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
#: utils.py:44
msgid "Your highest score is {score}"
msgid "Your highest score is {score}"
msgstr "Ýöür hïghést sçöré ïs {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):
...
@@ -23,7 +23,7 @@ def package_data(pkg, root_list):
setup
(
setup
(
name
=
'xblock-drag-and-drop-v2'
,
name
=
'xblock-drag-and-drop-v2'
,
version
=
'2.0.1
3
'
,
version
=
'2.0.1
4
'
,
description
=
'XBlock - Drag-and-Drop v2'
,
description
=
'XBlock - Drag-and-Drop v2'
,
packages
=
[
'drag_and_drop_v2'
],
packages
=
[
'drag_and_drop_v2'
],
install_requires
=
[
install_requires
=
[
...
...
tests/integration/test_base.py
View file @
69ee413d
...
@@ -48,7 +48,7 @@ ItemDefinition = namedtuple( # pylint: disable=invalid-name
...
@@ -48,7 +48,7 @@ ItemDefinition = namedtuple( # pylint: disable=invalid-name
class
BaseIntegrationTest
(
SeleniumBaseTest
):
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__
module_name
=
__name__
_additional_escapes
=
{
_additional_escapes
=
{
...
@@ -239,22 +239,32 @@ class DefaultDataTestMixin(object):
...
@@ -239,22 +239,32 @@ class DefaultDataTestMixin(object):
class
InteractionTestBase
(
object
):
class
InteractionTestBase
(
object
):
POPUP_ERROR_CLASS
=
"popup-incorrect"
POPUP_ERROR_CLASS
=
"popup-incorrect"
@classmethod
def
setUp
(
self
):
def
_get_items_with_zone
(
cls
,
items_map
):
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
{
return
{
item_key
:
definition
for
item_key
,
definition
in
items_map
.
items
()
item_key
:
definition
for
item_key
,
definition
in
items_map
.
items
()
if
definition
.
zone_ids
!=
[]
if
definition
.
zone_ids
!=
[]
}
}
@
class
method
@
static
method
def
_get_items_without_zone
(
cls
,
items_map
):
def
_get_items_without_zone
(
items_map
):
return
{
return
{
item_key
:
definition
for
item_key
,
definition
in
items_map
.
items
()
item_key
:
definition
for
item_key
,
definition
in
items_map
.
items
()
if
definition
.
zone_ids
==
[]
if
definition
.
zone_ids
==
[]
}
}
@
class
method
@
static
method
def
_get_items_by_zone
(
cls
,
items_map
):
def
_get_items_by_zone
(
items_map
):
zone_ids
=
set
([
definition
.
zone_ids
[
0
]
for
_
,
definition
in
items_map
.
items
()
if
definition
.
zone_ids
])
zone_ids
=
set
([
definition
.
zone_ids
[
0
]
for
_
,
definition
in
items_map
.
items
()
if
definition
.
zone_ids
])
return
{
return
{
zone_id
:
{
item_key
:
definition
for
item_key
,
definition
in
items_map
.
items
()
zone_id
:
{
item_key
:
definition
for
item_key
,
definition
in
items_map
.
items
()
...
@@ -262,15 +272,17 @@ class InteractionTestBase(object):
...
@@ -262,15 +272,17 @@ class InteractionTestBase(object):
for
zone_id
in
zone_ids
for
zone_id
in
zone_ids
}
}
def
setUp
(
self
):
@staticmethod
super
(
InteractionTestBase
,
self
)
.
setUp
()
def
_get_incorrect_zone_for_item
(
item
,
zones
):
"""Returns the first zone that is not correct for this item."""
scenario_xml
=
self
.
_get_scenario_xml
()
zone_id
=
None
self
.
_add_scenario
(
self
.
PAGE_ID
,
self
.
PAGE_TITLE
,
scenario_xml
)
zone_title
=
None
self
.
_page
=
self
.
go_to_page
(
self
.
PAGE_TITLE
)
for
z_id
,
z_title
in
zones
:
# Resize window so that the entire drag container is visible.
if
z_id
not
in
item
.
zone_ids
:
# Selenium has issues when dragging to an area that is off screen.
zone_id
=
z_id
self
.
browser
.
set_window_size
(
1024
,
800
)
zone_title
=
z_title
break
return
[
zone_id
,
zone_title
]
def
_get_item_by_value
(
self
,
item_value
):
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
]
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):
...
@@ -312,7 +324,7 @@ class InteractionTestBase(object):
both the HTML attribute and the DOM property are set to false.
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'.
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
)
return
self
.
browser
.
execute_script
(
script
)
def
assertDraggable
(
self
,
item_value
):
def
assertDraggable
(
self
,
item_value
):
...
@@ -370,7 +382,7 @@ class InteractionTestBase(object):
...
@@ -370,7 +382,7 @@ class InteractionTestBase(object):
item
.
send_keys
(
""
)
item
.
send_keys
(
""
)
item
.
send_keys
(
action_key
)
item
.
send_keys
(
action_key
)
# Focus is on first *zone* now
# 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.
# 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
if
zone_id
is
None
:
# moving back to the bank
zone
=
self
.
_get_item_bank
()
zone
=
self
.
_get_item_bank
()
...
@@ -387,9 +399,12 @@ class InteractionTestBase(object):
...
@@ -387,9 +399,12 @@ class InteractionTestBase(object):
ActionChains
(
self
.
browser
)
.
send_keys
(
Keys
.
TAB
)
.
perform
()
ActionChains
(
self
.
browser
)
.
send_keys
(
Keys
.
TAB
)
.
perform
()
zone
.
send_keys
(
action_key
)
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'
)
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
):
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
)
...
@@ -474,3 +489,10 @@ class InteractionTestBase(object):
...
@@ -474,3 +489,10 @@ class InteractionTestBase(object):
def
assert_button_enabled
(
self
,
submit_button
,
enabled
=
True
):
def
assert_button_enabled
(
self
,
submit_button
,
enabled
=
True
):
self
.
assertEqual
(
submit_button
.
is_enabled
(),
enabled
)
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
...
@@ -76,7 +76,7 @@ class EventsFiredTest(DefaultDataTestMixin, ParameterizedTestsMixin, BaseEventsT
@data
(
*
enumerate
(
scenarios
))
# pylint: disable=star-args
@data
(
*
enumerate
(
scenarios
))
# pylint: disable=star-args
@unpack
@unpack
def
test_event
(
self
,
index
,
event
):
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
]
dummy
,
name
,
published_data
=
self
.
publish
.
call_args_list
[
index
][
0
]
self
.
assertEqual
(
name
,
event
[
'name'
])
self
.
assertEqual
(
name
,
event
[
'name'
])
self
.
assertEqual
(
published_data
,
event
[
'data'
])
self
.
assertEqual
(
published_data
,
event
[
'data'
])
...
...
tests/integration/test_interaction.py
View file @
69ee413d
...
@@ -50,35 +50,92 @@ class ParameterizedTestsMixin(object):
...
@@ -50,35 +50,92 @@ class ParameterizedTestsMixin(object):
ActionChains
(
self
.
browser
)
.
send_keys
(
Keys
.
TAB
)
.
perform
()
ActionChains
(
self
.
browser
)
.
send_keys
(
Keys
.
TAB
)
.
perform
()
self
.
assertFocused
(
go_to_beginning_button
)
self
.
assertFocused
(
go_to_beginning_button
)
def
parameterized_item_positive_feedback_on_good_move
(
def
parameterized_item_positive_feedback_on_good_move
_standard
(
self
,
items_map
,
scroll_down
=
100
,
action_key
=
None
,
assessment_mode
=
Fals
e
self
,
items_map
,
scroll_down
=
100
,
action_key
=
None
,
feedback
=
Non
e
):
):
if
feedback
is
None
:
feedback
=
self
.
feedback
popup
=
self
.
_get_popup
()
popup
=
self
.
_get_popup
()
feedback_popup_content
=
self
.
_get_popup_content
()
feedback_popup_content
=
self
.
_get_popup_content
()
# Scroll drop zones into view to make sure Selenium can successfully drop items
# Scroll drop zones into view to make sure Selenium can successfully drop items
self
.
scroll_down
(
pixels
=
scroll_down
)
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
.
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
.
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'
)
feedback_popup_html
=
feedback_popup_content
.
get_attribute
(
'innerHTML'
)
if
assessment_mode
:
self
.
assertEqual
(
feedback_popup_html
,
"<p>{}</p>"
.
format
(
definition
.
feedback_positive
))
self
.
assertEqual
(
feedback_popup_html
,
''
)
self
.
assert_popup_correct
(
popup
)
self
.
assertFalse
(
popup
.
is_displayed
())
self
.
assertTrue
(
popup
.
is_displayed
())
if
action_key
:
expected_sr_texts
=
[
definition
.
feedback_positive
]
# Next TAB keypress should move focus to "Go to Beginning button"
if
i
==
len
(
items_with_zones
)
-
1
:
self
.
_test_next_tab_goes_to_go_to_beginning_button
()
# We just dropped the last item, so the problem is done and we should see the final feedback.
overall_feedback
=
feedback
[
'final'
]
else
:
else
:
self
.
assertEqual
(
feedback_popup_html
,
"<p>{}</p>"
.
format
(
definition
.
feedback_positive
))
overall_feedback
=
feedback
[
'intro'
]
self
.
assert_popup_correct
(
popup
)
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
.
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
)
self
.
_test_popup_focus_and_close
(
popup
,
action_key
)
def
parameterized_item_negative_feedback_on_bad_move
(
def
parameterized_item_negative_feedback_on_bad_move
_assessment
(
self
,
items_map
,
all_zones
,
scroll_down
=
100
,
action_key
=
None
,
assessment_mode
=
Fals
e
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
()
popup
=
self
.
_get_popup
()
feedback_popup_content
=
self
.
_get_popup_content
()
feedback_popup_content
=
self
.
_get_popup_content
()
...
@@ -86,30 +143,17 @@ class ParameterizedTestsMixin(object):
...
@@ -86,30 +143,17 @@ class ParameterizedTestsMixin(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
():
# Get first zone that is not correct for this item.
zone_id
,
zone_title
=
self
.
_get_incorrect_zone_for_item
(
definition
,
all_zones
)
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
if
zone_id
is
not
None
:
# Some items may be placed in any zone, ignore those.
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
.
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
))
self
.
wait_until_ondrop_xhr_finished
(
self
.
_get_item_by_value
(
definition
.
item_id
))
feedback_popup_html
=
feedback_popup_content
.
get_attribute
(
'innerHTML'
)
feedback_popup_html
=
feedback_popup_content
.
get_attribute
(
'innerHTML'
)
self
.
assertEqual
(
feedback_popup_html
,
''
)
self
.
assertEqual
(
feedback_popup_html
,
''
)
self
.
assertFalse
(
popup
.
is_displayed
())
self
.
assertFalse
(
popup
.
is_displayed
())
self
.
assert_placed_item
(
definition
.
item_id
,
zone_title
,
assessment_mode
=
True
)
self
.
assert_placed_item
(
definition
.
item_id
,
zone_title
,
assessment_mode
=
True
)
self
.
assert_reader_feedback_messages
([])
if
action_key
:
if
action_key
:
self
.
_test_next_tab_goes_to_go_to_beginning_button
()
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
)
def
parameterized_move_items_between_zones
(
self
,
items_map
,
all_zones
,
scroll_down
=
100
,
action_key
=
None
):
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
# Scroll drop zones into view to make sure Selenium can successfully drop items
...
@@ -247,11 +291,13 @@ class StandardInteractionTest(DefaultDataTestMixin, InteractionTestBase, Paramet
...
@@ -247,11 +291,13 @@ class StandardInteractionTest(DefaultDataTestMixin, InteractionTestBase, Paramet
"""
"""
@data
(
*
ITEM_DRAG_KEYBOARD_KEYS
)
@data
(
*
ITEM_DRAG_KEYBOARD_KEYS
)
def
test_item_positive_feedback_on_good_move
(
self
,
action_key
):
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
)
@data
(
*
ITEM_DRAG_KEYBOARD_KEYS
)
def
test_item_negative_feedback_on_bad_move
(
self
,
action_key
):
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
)
@data
(
*
ITEM_DRAG_KEYBOARD_KEYS
)
def
test_cannot_move_items_between_zones
(
self
,
action_key
):
def
test_cannot_move_items_between_zones
(
self
,
action_key
):
...
@@ -332,6 +378,23 @@ class StandardInteractionTest(DefaultDataTestMixin, InteractionTestBase, Paramet
...
@@ -332,6 +378,23 @@ class StandardInteractionTest(DefaultDataTestMixin, InteractionTestBase, Paramet
# After placing all items, we get the full score.
# After placing all items, we get the full score.
self
.
assertEqual
(
progress
.
text
,
'1/1 point (ungraded)'
)
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
):
class
MultipleValidOptionsInteractionTest
(
DefaultDataTestMixin
,
InteractionTestBase
,
BaseIntegrationTest
):
...
@@ -477,16 +540,22 @@ class MultipleBlocksDataInteraction(ParameterizedTestsMixin, InteractionTestBase
...
@@ -477,16 +540,22 @@ class MultipleBlocksDataInteraction(ParameterizedTestsMixin, InteractionTestBase
def
test_item_positive_feedback_on_good_move
(
self
):
def
test_item_positive_feedback_on_good_move
(
self
):
self
.
_switch_to_block
(
0
)
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
.
_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
):
def
test_item_negative_feedback_on_bad_move
(
self
):
self
.
_switch_to_block
(
0
)
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
.
_switch_to_block
(
1
)
self
.
parameterized_item_negative_feedback_on_bad_move
(
self
.
parameterized_item_negative_feedback_on_bad_move
_standard
(
self
.
item_maps
[
'block2'
],
self
.
all_zones
[
'block2'
],
scroll_down
=
1000
self
.
item_maps
[
'block2'
],
self
.
all_zones
[
'block2'
],
feedback
=
self
.
feedback
[
'block2'
],
scroll_down
=
1000
)
)
def
test_final_feedback_and_reset
(
self
):
def
test_final_feedback_and_reset
(
self
):
...
...
tests/integration/test_interaction_assessment.py
View file @
69ee413d
...
@@ -81,14 +81,12 @@ class AssessmentInteractionTest(
...
@@ -81,14 +81,12 @@ class AssessmentInteractionTest(
"""
"""
@data
(
*
ITEM_DRAG_KEYBOARD_KEYS
)
@data
(
*
ITEM_DRAG_KEYBOARD_KEYS
)
def
test_item_no_feedback_on_good_move
(
self
,
action_key
):
def
test_item_no_feedback_on_good_move
(
self
,
action_key
):
self
.
parameterized_item_positive_feedback_on_good_move
(
self
.
parameterized_item_positive_feedback_on_good_move_assessment
(
self
.
items_map
,
action_key
=
action_key
)
self
.
items_map
,
action_key
=
action_key
,
assessment_mode
=
True
)
@data
(
*
ITEM_DRAG_KEYBOARD_KEYS
)
@data
(
*
ITEM_DRAG_KEYBOARD_KEYS
)
def
test_item_no_feedback_on_bad_move
(
self
,
action_key
):
def
test_item_no_feedback_on_bad_move
(
self
,
action_key
):
self
.
parameterized_item_negative_feedback_on_bad_move
(
self
.
parameterized_item_negative_feedback_on_bad_move
_assessment
(
self
.
items_map
,
self
.
all_zones
,
action_key
=
action_key
,
assessment_mode
=
True
self
.
items_map
,
self
.
all_zones
,
action_key
=
action_key
)
)
@data
(
*
ITEM_DRAG_KEYBOARD_KEYS
)
@data
(
*
ITEM_DRAG_KEYBOARD_KEYS
)
...
@@ -250,6 +248,18 @@ class AssessmentInteractionTest(
...
@@ -250,6 +248,18 @@ class AssessmentInteractionTest(
"""
"""
Test updating overall feedback after submitting solution in assessment mode
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
# 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
)
self
.
place_item
(
0
,
TOP_ZONE_ID
,
Keys
.
RETURN
)
...
@@ -261,29 +271,25 @@ class AssessmentInteractionTest(
...
@@ -261,29 +271,25 @@ class AssessmentInteractionTest(
expected_grade
=
2.0
/
5.0
expected_grade
=
2.0
/
5.0
feedback_lines
=
[
feedback_lines
=
[
"FEEDBACK"
,
FeedbackMessages
.
correctly_placed
(
1
),
FeedbackMessages
.
correctly_placed
(
1
),
FeedbackMessages
.
not_placed
(
3
),
FeedbackMessages
.
not_placed
(
3
),
START_FEEDBACK
,
START_FEEDBACK
,
FeedbackMessages
.
GRADE_FEEDBACK_TPL
.
format
(
score
=
expected_grade
)
FeedbackMessages
.
GRADE_FEEDBACK_TPL
.
format
(
score
=
expected_grade
)
]
]
expected_feedback
=
"
\n
"
.
join
(
feedback_lines
)
check_feedback
(
feedback_lines
)
self
.
assertEqual
(
self
.
_get_feedback
()
.
text
,
expected_feedback
)
# Place the item into incorrect zone. The score does not change.
# Place the item into incorrect zone. The score does not change.
self
.
place_item
(
1
,
BOTTOM_ZONE_ID
,
Keys
.
RETURN
)
self
.
place_item
(
1
,
BOTTOM_ZONE_ID
,
Keys
.
RETURN
)
self
.
click_submit
()
self
.
click_submit
()
feedback_lines
=
[
feedback_lines
=
[
"FEEDBACK"
,
FeedbackMessages
.
correctly_placed
(
1
),
FeedbackMessages
.
correctly_placed
(
1
),
FeedbackMessages
.
misplaced_returned
(
1
),
FeedbackMessages
.
misplaced_returned
(
1
),
FeedbackMessages
.
not_placed
(
2
),
FeedbackMessages
.
not_placed
(
2
),
START_FEEDBACK
,
START_FEEDBACK
,
FeedbackMessages
.
GRADE_FEEDBACK_TPL
.
format
(
score
=
expected_grade
)
FeedbackMessages
.
GRADE_FEEDBACK_TPL
.
format
(
score
=
expected_grade
)
]
]
expected_feedback
=
"
\n
"
.
join
(
feedback_lines
)
check_feedback
(
feedback_lines
,
[
"No, this item does not belong here. Try again."
])
self
.
assertEqual
(
self
.
_get_feedback
()
.
text
,
expected_feedback
)
# reach final attempt
# reach final attempt
for
_
in
xrange
(
self
.
MAX_ATTEMPTS
-
3
):
for
_
in
xrange
(
self
.
MAX_ATTEMPTS
-
3
):
...
@@ -299,13 +305,11 @@ class AssessmentInteractionTest(
...
@@ -299,13 +305,11 @@ class AssessmentInteractionTest(
expected_grade
=
1.0
expected_grade
=
1.0
feedback_lines
=
[
feedback_lines
=
[
"FEEDBACK"
,
FeedbackMessages
.
correctly_placed
(
4
),
FeedbackMessages
.
correctly_placed
(
4
),
FINISH_FEEDBACK
,
FINISH_FEEDBACK
,
FeedbackMessages
.
FINAL_ATTEMPT_TPL
.
format
(
score
=
expected_grade
)
FeedbackMessages
.
FINAL_ATTEMPT_TPL
.
format
(
score
=
expected_grade
)
]
]
expected_feedback
=
"
\n
"
.
join
(
feedback_lines
)
check_feedback
(
feedback_lines
)
self
.
assertEqual
(
self
.
_get_feedback
()
.
text
,
expected_feedback
)
def
test_per_item_feedback_multiple_misplaced
(
self
):
def
test_per_item_feedback_multiple_misplaced
(
self
):
self
.
place_item
(
0
,
MIDDLE_ZONE_ID
,
Keys
.
RETURN
)
self
.
place_item
(
0
,
MIDDLE_ZONE_ID
,
Keys
.
RETURN
)
...
...
tests/integration/test_render.py
View file @
69ee413d
...
@@ -194,7 +194,7 @@ class TestDragAndDropRender(BaseIntegrationTest):
...
@@ -194,7 +194,7 @@ class TestDragAndDropRender(BaseIntegrationTest):
'#-Zone_{}'
.
format
(
zone_number
),
**
zone_box_percentages
'#-Zone_{}'
.
format
(
zone_number
),
**
zone_box_percentages
)
)
zone_name
=
zone
.
find_element_by_css_selector
(
'p.zone-name'
)
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'
)
zone_description
=
zone
.
find_element_by_css_selector
(
'p.zone-description'
)
self
.
assertEqual
(
zone_description
.
text
,
'This describes zone {}'
.
format
(
zone_number
))
self
.
assertEqual
(
zone_description
.
text
,
'This describes zone {}'
.
format
(
zone_number
))
# Zone description should only be visible to screen readers:
# Zone description should only be visible to screen readers:
...
@@ -204,12 +204,10 @@ class TestDragAndDropRender(BaseIntegrationTest):
...
@@ -204,12 +204,10 @@ class TestDragAndDropRender(BaseIntegrationTest):
self
.
load_scenario
()
self
.
load_scenario
()
popup
=
self
.
_get_popup
()
popup
=
self
.
_get_popup
()
popup_wrapper
=
self
.
_get_popup_wrapper
()
popup_content
=
self
.
_get_popup_content
()
popup_content
=
self
.
_get_popup_content
()
self
.
assertFalse
(
popup
.
is_displayed
())
self
.
assertFalse
(
popup
.
is_displayed
())
self
.
assertIn
(
'popup'
,
popup
.
get_attribute
(
'class'
))
self
.
assertIn
(
'popup'
,
popup
.
get_attribute
(
'class'
))
self
.
assertEqual
(
popup_content
.
text
,
""
)
self
.
assertEqual
(
popup_content
.
text
,
""
)
self
.
assertEqual
(
popup_wrapper
.
get_attribute
(
'aria-live'
),
'polite'
)
@data
(
None
,
Keys
.
RETURN
)
@data
(
None
,
Keys
.
RETURN
)
def
test_go_to_beginning_button
(
self
,
action_key
):
def
test_go_to_beginning_button
(
self
,
action_key
):
...
@@ -253,9 +251,7 @@ class TestDragAndDropRender(BaseIntegrationTest):
...
@@ -253,9 +251,7 @@ class TestDragAndDropRender(BaseIntegrationTest):
def
test_feedback
(
self
):
def
test_feedback
(
self
):
self
.
load_scenario
()
self
.
load_scenario
()
feedback
=
self
.
_get_feedback
()
feedback_message
=
self
.
_get_feedback_message
()
feedback_message
=
self
.
_get_feedback_message
()
self
.
assertEqual
(
feedback
.
get_attribute
(
'aria-live'
),
'polite'
)
self
.
assertEqual
(
feedback_message
.
text
,
START_FEEDBACK
)
self
.
assertEqual
(
feedback_message
.
text
,
START_FEEDBACK
)
def
test_background_image
(
self
):
def
test_background_image
(
self
):
...
...
tests/integration/test_title_and_question.py
View file @
69ee413d
...
@@ -26,10 +26,10 @@ class TestDragAndDropTitleAndProblem(BaseIntegrationTest):
...
@@ -26,10 +26,10 @@ class TestDragAndDropTitleAndProblem(BaseIntegrationTest):
self
.
addCleanup
(
scenarios
.
remove_scenario
,
const_page_id
)
self
.
addCleanup
(
scenarios
.
remove_scenario
,
const_page_id
)
page
=
self
.
go_to_page
(
const_page_name
)
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
)
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
)
self
.
assertEqual
(
self
.
get_element_html
(
problem
),
problem_text
)
@unpack
@unpack
...
...
tests/unit/test_basics.py
View file @
69ee413d
...
@@ -45,7 +45,7 @@ class BasicTests(TestCaseMixin, unittest.TestCase):
...
@@ -45,7 +45,7 @@ class BasicTests(TestCaseMixin, unittest.TestCase):
def
test_template_contents
(
self
):
def
test_template_contents
(
self
):
context
=
{}
context
=
{}
student_fragment
=
self
.
block
.
runtime
.
render
(
self
.
block
,
'student_view'
,
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
)
self
.
assertIn
(
'Loading drag and drop problem.'
,
student_fragment
.
content
)
def
test_get_configuration
(
self
):
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