Commit 6b977414 by Braden MacDonald

Update this branch with latest changes from master

# Conflicts:
#	tests/integration/test_interaction.py
parents 9e32e448 d31dce44
language: python
sudo: false
python:
- "2.7"
before_install:
......
......@@ -137,6 +137,263 @@ any zone.
You can define an arbitrary number of drag items.
Analytics Events
----------------
The following analytics events are provided by this block.
## `edx.drag_and_drop_v2.loaded`
Fired when the Drag and Drop XBlock is finished loading.
Example ("common" fields that are not interesting in this context have been left out):
```
{
...
"event": {},
"event_source": "server", -- Common field, contains event source.
"event_type": "edx.drag_and_drop_v2.loaded", -- Common field, contains event name.
...
```
Real event example (taken from a devstack):
```
{
"username": "staff",
"event_type": "edx.drag_and_drop_v2.loaded",
"ip": "10.0.2.2",
"agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.11; rv:43.0) Gecko/20100101 Firefox/43.0",
"host": "precise64",
"referer": "http://example.com/courses/course-v1:DnD+DnD+DnD/courseware/ec546c58d2f447b7a9223c57b5de7344/756071f8de7f47c3b0ae726586ebbe16/1?activate_block_id=block-v1%3ADnD%2BDnD%2BDnD%2Btype%40vertical%2Bblock%40d2fc47476ca14c55816c4a1264a27280",
"accept_language": "en;q=1.0, en;q=0.5",
"event": {},
"event_source": "server",
"context": {
"course_user_tags": {},
"user_id": 5,
"org_id": "DnD",
"module": {
"usage_key": "block-v1:DnD+DnD+DnD+type@drag-and-drop-v2+block@6b80ce1e8b78426898b47a834d72ffd3",
"display_name": "Drag and Drop"
},
"course_id": "course-v1:DnD+DnD+DnD",
"path": "/courses/course-v1:DnD+DnD+DnD/xblock/block-v1:DnD+DnD+DnD+type@drag-and-drop-v2+block@6b80ce1e8b78426898b47a834d72ffd3/handler/publish_event"
},
"time": "2016-01-13T01:52:41.330049+00:00",
"page": "x_module"
}
```
## `edx.drag_and_drop_v2.item.picked_up`
Fired when a student picks up a draggable item.
Example ("common" fields that are not interesting in this context have been left out):
```
{
...
"event": {
"item_id": 0, -- ID of the draggable item.
},
"event_source": "server", -- Common field, contains event source.
"event_type": "edx.drag_and_drop_v2.picked_up", -- Common field, contains event name.
...
```
Real event example (taken from a devstack):
```
{
"username": "staff",
"event_type": "edx.drag_and_drop_v2.item.picked_up",
"ip": "10.0.2.2",
"agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.11; rv:43.0) Gecko/20100101 Firefox/43.0",
"host": "precise64",
"referer": "http://example.com/courses/course-v1:DnD+DnD+DnD/courseware/ec546c58d2f447b7a9223c57b5de7344/756071f8de7f47c3b0ae726586ebbe16/1?activate_block_id=block-v1%3ADnD%2BDnD%2BDnD%2Btype%40vertical%2Bblock%40d2fc47476ca14c55816c4a1264a27280",
"accept_language": "en;q=1.0, en;q=0.5",
"event": {
"item_id": 0,
},
"event_source": "server",
"context": {
"course_user_tags": {},
"user_id": 5,
"org_id": "DnD",
"module": {
"usage_key": "block-v1:DnD+DnD+DnD+type@drag-and-drop-v2+block@6b80ce1e8b78426898b47a834d72ffd3",
"display_name": "Drag and Drop"
},
"course_id": "course-v1:DnD+DnD+DnD",
"path": "/courses/course-v1:DnD+DnD+DnD/xblock/block-v1:DnD+DnD+DnD+type@drag-and-drop-v2+block@6b80ce1e8b78426898b47a834d72ffd3/handler/publish_event"
},
"time": "2016-01-13T01:58:44.395935+00:00",
"page": "x_module"
}
```
## `edx.drag_and_drop_v2.item.dropped`
Fired when a student drops a draggable item.
This event will be emitted when a student drops a draggable item.
Example ("common" fields that are not interesting in this context have been left out):
```
{
...
"event": {
"input": null,
"is_correct": true, -- False if there is an input in the draggable item, and the student provided the wrong answer. Otherwise true.
"is_correct_location": true, -- Whether the draggable item has been placed in the correct location.
"item_id": 0, -- ID of the draggable item.
"location": "The Top Zone", -- Name of the location the item was dragged to.
},
"event_source": "server", -- Common field, contains event source.
"event_type": "edx.drag_and_drop_v2.dropped", -- Common field, contains event name.
...
```
Real event example (taken from a devstack):
```
{
"username": "staff",
"event_type": "edx.drag_and_drop_v2.item.dropped",
"ip": "10.0.2.2",
"agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.11; rv:43.0) Gecko/20100101 Firefox/43.0",
"host": "precise64",
"referer": "http://example.com/courses/course-v1:DnD+DnD+DnD/courseware/ec546c58d2f447b7a9223c57b5de7344/756071f8de7f47c3b0ae726586ebbe16/1?activate_block_id=block-v1%3ADnD%2BDnD%2BDnD%2Btype%40vertical%2Bblock%40d2fc47476ca14c55816c4a1264a27280",
"accept_language": "en;q=1.0, en;q=0.5",
"event": {
"is_correct_location": true,
"is_correct": true,
"location": "The Top Zone",
"item_id": 0,
"input": null
},
"event_source": "server",
"context": {
"course_user_tags": {},
"user_id": 5,
"org_id": "DnD",
"module": {
"usage_key": "block-v1:DnD+DnD+DnD+type@drag-and-drop-v2+block@6b80ce1e8b78426898b47a834d72ffd3",
"display_name": "Drag and Drop"
},
"course_id": "course-v1:DnD+DnD+DnD",
"path": "/courses/course-v1:DnD+DnD+DnD/xblock/block-v1:DnD+DnD+DnD+type@drag-and-drop-v2+block@6b80ce1e8b78426898b47a834d72ffd3/handler/do_attempt"
},
"time": "2016-01-13T01:58:45.202313+00:00",
"page": "x_module"
}
```
## `edx.drag_and_drop_v2.feedback.opened`
Fired when the feedback pop-up is opened.
Example ("common" fields that are not interesting in this context have been left out):
```
{
...
"event": {
"content": "Correct! This one belongs to The Top Zone.", -- Content of the feedback popup.
"truncated": false, -- Boolean indicating whether "content" field was truncated.
},
"event_source": "server", -- Common field, contains event source.
"event_type": "edx.drag_and_drop_v2.feedback.opened", -- Common field, contains event name.
...
```
Real event example (taken from a devstack):
```
{
"username": "staff",
"event_type": "edx.drag_and_drop_v2.feedback.opened",
"ip": "10.0.2.2",
"agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.11; rv:43.0) Gecko/20100101 Firefox/43.0",
"host": "precise64",
"referer": "http://example.com/courses/course-v1:DnD+DnD+DnD/courseware/ec546c58d2f447b7a9223c57b5de7344/756071f8de7f47c3b0ae726586ebbe16/1?activate_block_id=block-v1%3ADnD%2BDnD%2BDnD%2Btype%40vertical%2Bblock%40d2fc47476ca14c55816c4a1264a27280",
"accept_language": "en;q=1.0, en;q=0.5",
"event": {
"content": "Correct! This one belongs to The Top Zone.",
"truncated": false,
},
"event_source": "server",
"context": {
"course_user_tags": {},
"user_id": 5,
"org_id": "DnD",
"module": {
"usage_key": "block-v1:DnD+DnD+DnD+type@drag-and-drop-v2+block@6b80ce1e8b78426898b47a834d72ffd3",
"display_name": "Drag and Drop"
},
"course_id": "course-v1:DnD+DnD+DnD",
"path": "/courses/course-v1:DnD+DnD+DnD/xblock/block-v1:DnD+DnD+DnD+type@drag-and-drop-v2+block@6b80ce1e8b78426898b47a834d72ffd3/handler/publish_event"
},
"time": "2016-01-13T01:58:45.844986+00:00",
"page": "x_module"
}
```
## `edx.drag_and_drop_v2.feedback.closed`
Fired when the feedback popup is closed.
Example ("common" fields that are not interesting in this context have been left out):
```
{
...
"event": {
"content": "No, this item does not belong here. Try again." -- Message of the feedback popup that was closed.
"manually": true, -- Whether or not the user closed the feedback window manually or if it was auto-closed.
"truncated": false, -- Boolean indicating whether "content" field was truncated.
},
"event_source": "server", -- Common field, contains event source.
"event_type": "edx.drag_and_drop_v2.feedback.closed", -- Common field, contains event name.
...
```
Real event example (taken from a devstack):
```
{
"username": "staff",
"event_type": "edx.drag_and_drop_v2.feedback.closed",
"ip": "10.0.2.2",
"agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.11; rv:43.0) Gecko/20100101 Firefox/43.0",
"host": "precise64",
"referer": "http://example.com/courses/course-v1:DnD+DnD+DnD/courseware/ec546c58d2f447b7a9223c57b5de7344/756071f8de7f47c3b0ae726586ebbe16/1?activate_block_id=block-v1%3ADnD%2BDnD%2BDnD%2Btype%40vertical%2Bblock%40d2fc47476ca14c55816c4a1264a27280",
"accept_language": "en;q=1.0, en;q=0.5",
"event": {
"content": "No, this item does not belong here. Try again."
"manually": true
"truncated": false,
},
"event_source": "server",
"context": {
"course_user_tags": {},
"user_id": 5,
"org_id": "DnD",
"module": {
"usage_key": "block-v1:DnD+DnD+DnD+type@drag-and-drop-v2+block@13d1b859a2304c858e1810ccc23f29b2",
"display_name": "Drag and Drop"
},
"course_id": "course-v1:DnD+DnD+DnD",
"path": "/courses/course-v1:DnD+DnD+DnD/xblock/block-v1:DnD+DnD+DnD+type@drag-and-drop-v2+block@13d1b859a2304c858e1810ccc23f29b2/handler/publish_event"
},
"time": "2016-01-13T02:07:00.988534+00:00",
"page": "x_module"
}
```
Testing
-------
......
......@@ -198,7 +198,6 @@ class DragAndDropBlock(XBlock, XBlockWithSettingsMixin, ThemableXBlockMixin):
)
js_urls = (
'public/js/vendor/jquery-ui-1.10.4.custom.min.js',
'public/js/vendor/jquery.html5-placeholder-shim.js',
'public/js/vendor/handlebars-v1.1.2.js',
'public/js/drag_and_drop_edit.js',
)
......@@ -287,9 +286,7 @@ class DragAndDropBlock(XBlock, XBlockWithSettingsMixin, ThemableXBlockMixin):
# so we have to figure that we're running in Studio for now
pass
self.runtime.publish(self, 'xblock.drag-and-drop-v2.item.dropped', {
'user_id': self.scope_ids.user_id,
'component_id': self._get_unique_id(),
self.runtime.publish(self, 'edx.drag_and_drop_v2.item.dropped', {
'item_id': item['id'],
'location': attempt.get('zone'),
'input': attempt.get('input'),
......@@ -444,9 +441,6 @@ class DragAndDropBlock(XBlock, XBlockWithSettingsMixin, ThemableXBlockMixin):
except KeyError:
return {'result': 'error', 'message': 'Missing event_type in JSON data'}
data['user_id'] = self.scope_ids.user_id
data['component_id'] = self._get_unique_id()
self.runtime.publish(self, event_type, data)
return {'result': 'success'}
......
......@@ -244,7 +244,7 @@
border: 2px solid #a5a5a5;
}
.xblock--drag-and-drop .zone p {
.xblock--drag-and-drop .drag-container .target .zone p {
width: 100%;
font-family: Arial;
font-size: 16px;
......@@ -255,17 +255,6 @@
margin-bottom: auto;
}
/*** IE9 alignment fix ***/
.lt-ie10 .xblock--drag-and-drop .zone {
display: table;
}
.lt-ie10 .xblock--drag-and-drop .zone p {
display: table-cell;
vertical-align: middle;
text-align: center;
}
/*** FEEDBACK ***/
.xblock--drag-and-drop .feedback {
......
......@@ -50,17 +50,6 @@
margin-bottom: auto;
}
/*** IE9 alignment fix ***/
.lt-ie10 .xblock--drag-and-drop--editor .zone {
display: table;
}
.lt-ie10 .xblock--drag-and-drop--editor .zone p {
display: table-cell;
vertical-align: middle;
text-align: center;
}
/** Builder **/
.xblock--drag-and-drop--editor .hidden {
display: none !important;
......
......@@ -13,13 +13,15 @@ function DragAndDropBlock(runtime, element, configuration) {
var bgImgNaturalWidth = undefined; // pixel width of the background image (when not scaled)
var __vdom = virtualDom.h(); // blank virtual DOM
// Event string size limit.
var MAX_LENGTH = 255;
// Keyboard accessibility
var ESC = 27;
var RET = 13;
var SPC = 32;
var TAB = 9;
var M = 77;
var QUESTION_MARK = 63;
var placementMode = false;
var $selectedItem;
......@@ -48,9 +50,6 @@ function DragAndDropBlock(runtime, element, configuration) {
// Set up event handlers:
$(document).on('keydown mousedown touchstart', closePopup);
$(document).on('keypress', function(evt) {
runOnKey(evt, QUESTION_MARK, showKeyboardHelp);
});
$element.on('click', '.keyboard-help-button', showKeyboardHelp);
$element.on('keydown', '.keyboard-help-button', function(evt) {
runOnKey(evt, RET, showKeyboardHelp);
......@@ -69,7 +68,7 @@ function DragAndDropBlock(runtime, element, configuration) {
initDroppable();
// Indicate that exercise is done loading
publishEvent({event_type: 'xblock.drag-and-drop-v2.loaded'});
publishEvent({event_type: 'edx.drag_and_drop_v2.loaded'});
}).fail(function() {
$root.text(gettext("An error occurred. Unable to load drag and drop exercise."));
});
......@@ -96,6 +95,15 @@ function DragAndDropBlock(runtime, element, configuration) {
}
};
var truncateField = function(data, fieldName){
if (data[fieldName].length > MAX_LENGTH) {
data[fieldName] = data[fieldName].substring(0, MAX_LENGTH);
data['truncated'] = true;
} else {
data['truncated'] = false;
}
};
var focusModalButton = function() {
$root.find('.keyboard-help-dialog .modal-dismiss-button ').focus();
};
......@@ -204,28 +212,26 @@ function DragAndDropBlock(runtime, element, configuration) {
* Update the DOM to reflect 'state'.
*/
var applyState = function() {
// Is there a change to the feedback popup?
if (state.feedback !== previousFeedback) {
if (state.feedback) {
if (previousFeedback) {
publishEvent({
event_type: 'xblock.drag-and-drop-v2.feedback.closed',
content: previousFeedback,
manually: false,
});
// Has the feedback popup been closed?
if (state.closing) {
var data = {
event_type: 'edx.drag_and_drop_v2.feedback.closed',
content: previousFeedback || state.feedback,
manually: state.manually_closed,
};
truncateField(data, 'content');
publishEvent(data);
delete state.feedback;
delete state.closing;
}
publishEvent({
event_type: 'xblock.drag-and-drop-v2.feedback.opened',
content: state.feedback,
});
} else {
publishEvent({
event_type: 'xblock.drag-and-drop-v2.feedback.closed',
// Has feedback been set?
if (state.feedback) {
var data = {
event_type: 'edx.drag_and_drop_v2.feedback.opened',
content: state.feedback,
manually: true,
});
}
previousFeedback = state.feedback;
};
truncateField(data, 'content');
publishEvent(data);
}
updateDOM();
......@@ -380,7 +386,7 @@ function DragAndDropBlock(runtime, element, configuration) {
var $item = $(this);
grabItem($item);
publishEvent({
event_type: 'xblock.drag-and-drop-v2.item.picked-up',
event_type: 'edx.drag_and_drop_v2.item.picked_up',
item_id: $item.data('value'),
});
},
......@@ -515,7 +521,14 @@ function DragAndDropBlock(runtime, element, configuration) {
return;
}
delete state.feedback;
state.closing = true;
previousFeedback = state.feedback;
if (target.is(close_button)) {
state.manually_closed = true;
} else {
state.manually_closed = false;
}
applyState();
};
......
......@@ -94,9 +94,6 @@ function DragAndDropEditBlock(runtime, element, params) {
$fbkTab.addClass('hidden');
$zoneTab.removeClass('hidden');
// Placeholder shim for IE9
$.placeholder.shim();
$(this).one('click', function(e) {
// $zoneTab -> $itemTab
e.preventDefault();
......@@ -111,9 +108,6 @@ function DragAndDropEditBlock(runtime, element, params) {
$zoneTab.addClass('hidden');
$itemTab.removeClass('hidden');
// Placeholder shim for IE9
$.placeholder.shim();
$(this).addClass('hidden');
$('.save-button', element).parent()
.removeClass('hidden')
......@@ -155,8 +149,6 @@ function DragAndDropEditBlock(runtime, element, params) {
_fn.build.$el.targetImage.attr('alt', new_description);
_fn.data.targetImgDescription = new_description;
// Placeholder shim for IE9
$.placeholder.shim();
})
.on('click', '.display-labels-form input', function(e) {
_fn.data.displayLabels = $('.display-labels-form input', element).is(':checked');
......@@ -219,8 +211,6 @@ function DragAndDropEditBlock(runtime, element, params) {
// Add zone div to target
_fn.build.form.zone.renderZonesPreview();
// Placeholder shim for IE9
$.placeholder.shim();
},
remove: function(e) {
var $el = $(e.currentTarget).closest('.zone-row'),
......@@ -243,8 +233,6 @@ function DragAndDropEditBlock(runtime, element, params) {
_fn.build.form.zone.formCount--;
_fn.build.form.zone.disableDelete();
// Placeholder shim for IE9
$.placeholder.shim();
},
enableDelete: function() {
if (_fn.build.form.zone.formCount > 1) {
......@@ -378,8 +366,6 @@ function DragAndDropEditBlock(runtime, element, params) {
$form.append(tpl(ctx));
_fn.build.form.item.enableDelete();
// Placeholder shim for IE9
$.placeholder.shim();
},
remove: function(e) {
var $el = $(e.currentTarget).closest('.item');
......@@ -390,8 +376,6 @@ function DragAndDropEditBlock(runtime, element, params) {
_fn.build.form.item.count--;
_fn.build.form.item.disableDelete();
// Placeholder shim for IE9
$.placeholder.shim();
},
enableDelete: function() {
if (_fn.build.form.item.count > 1) {
......
(function($) {
// @todo Document this.
$.extend($,{ placeholder: {
browser_supported: function() {
return this._supported !== undefined ?
this._supported :
( this._supported = !!('placeholder' in $('<input type="text">')[0]) );
},
shim: function(opts) {
var config = {
color: '#888',
cls: 'placeholder',
selector: 'input[placeholder], textarea[placeholder]'
};
$.extend(config,opts);
return !this.browser_supported() && $(config.selector)._placeholder_shim(config);
}
}});
$.extend($.fn,{
_placeholder_shim: function(config) {
function calcPositionCss(target)
{
var op = $(target).offsetParent().offset();
var ot = $(target).offset();
return {
top: ot.top - op.top,
left: ot.left - op.left,
width: $(target).width()
};
}
function adjustToResizing(label) {
var $target = label.data('target');
if(typeof $target !== "undefined") {
label.css(calcPositionCss($target));
$(window).one("resize", function () { adjustToResizing(label); });
}
}
return this.each(function() {
var $this = $(this);
if( $this.is(':visible') ) {
if( $this.data('placeholder') ) {
var $ol = $this.data('placeholder');
$ol.css(calcPositionCss($this));
return true;
}
var possible_line_height = {};
if( !$this.is('textarea') && $this.css('height') != 'auto') {
possible_line_height = { lineHeight: $this.css('height'), whiteSpace: 'nowrap' };
}
var isBorderBox = ($this.css('box-sizing') === 'border-box');
var ol = $('<label />')
.text($this.attr('placeholder'))
.addClass(config.cls)
.css($.extend({
position:'absolute',
display: 'inline',
'float':'none',
overflow:'hidden',
textAlign: 'left',
color: config.color,
cursor: 'text',
paddingTop: isBorderBox ? '0' : $this.css('padding-top'),
paddingRight: $this.css('padding-right'),
paddingBottom: isBorderBox ? '0' : $this.css('padding-bottom'),
paddingLeft: $this.css('padding-left'),
fontSize: $this.css('font-size'),
fontFamily: $this.css('font-family'),
fontStyle: $this.css('font-style'),
fontWeight: $this.css('font-weight'),
textTransform: $this.css('text-transform'),
backgroundColor: 'transparent',
zIndex: 99
}, possible_line_height))
.css(calcPositionCss(this))
.attr('for', this.id)
.data('target',$this)
.click(function(){
if (!$(this).data('target').is(':disabled')) {
$(this).data('target').focus();
}
})
.insertBefore(this);
$this
.data('placeholder',ol)
.keydown(function(){
ol.hide();
})
.blur(function() {
ol[$this.val().length ? 'hide' : 'show']();
}).triggerHandler('blur');
$(window).one("resize", function () { adjustToResizing(ol); });
}
});
}
});
})(jQuery);
jQuery(document).add(window).bind('ready load', function() {
if (jQuery.placeholder) {
jQuery.placeholder.shim();
}
});
......@@ -197,7 +197,6 @@
h('li', gettext('Press "Enter", "Space", "Ctrl-m", or "⌘-m" on an item to select it for dropping, then navigate to the zone you want to drop it on.')),
h('li', gettext('Press "Enter", "Space", "Ctrl-m", or "⌘-m" to drop the item on the current zone.')),
h('li', gettext('Press "Escape" if you want to cancel the drop operation (e.g. because you would like to select a different item).')),
h('li', gettext('Press "?" at any time to bring up this dialog.')),
])
]),
h('div.modal-actions', [
......
......@@ -51,7 +51,7 @@
background-color: #fff;
}
.themed-xblock.xblock--drag-and-drop .zone p {
.themed-xblock.xblock--drag-and-drop .drag-container .target .zone p {
font-family: Arial;
font-size: 16px;
font-weight: bold;
......
......@@ -29,7 +29,8 @@ setup(
install_requires=[
'XBlock',
'xblock-utils',
'ddt'
'ddt',
'mock',
],
entry_points={
'xblock.v1': 'drag-and-drop-v2 = drag_and_drop_v2:DragAndDropBlock',
......
# Imports ###########################################################
from ddt import ddt, data
from ddt import ddt, data, unpack
from mock import Mock, patch
from selenium.common.exceptions import NoSuchElementException
from selenium.webdriver import ActionChains
from selenium.webdriver.common.keys import Keys
from workbench.runtime import WorkbenchRuntime
from xblockutils.resources import ResourceLoader
from drag_and_drop_v2.default_data import (
......@@ -77,6 +79,14 @@ class InteractionTestBase(object):
element = self._get_item_by_value(item_value)
return element.find_element_by_class_name('numerical-input')
def _get_dialog_components(self, dialog): # pylint: disable=no-self-use
dialog_modal_overlay = dialog.find_element_by_css_selector('.modal-window-overlay')
dialog_modal = dialog.find_element_by_css_selector('.modal-window')
return dialog_modal_overlay, dialog_modal
def _get_dialog_dismiss_button(self, dialog_modal): # pylint: disable=no-self-use
return dialog_modal.find_element_by_css_selector('.modal-dismiss-button')
def _get_zone_position(self, zone_id):
return self.browser.execute_script(
'return $("div[data-zone=\'{zone_id}\']").prevAll(".zone").length'.format(zone_id=zone_id)
......@@ -256,9 +266,8 @@ class InteractionTestBase(object):
def interact_with_keyboard_help(self, scroll_down=250, use_keyboard=False):
keyboard_help_button = self._get_keyboard_help_button()
keyboard_help_dialog = self._get_keyboard_help_dialog()
dialog_modal_overlay = keyboard_help_dialog.find_element_by_css_selector('.modal-window-overlay')
dialog_modal = keyboard_help_dialog.find_element_by_css_selector('.modal-window')
dialog_dismiss_button = dialog_modal.find_element_by_css_selector('.modal-dismiss-button')
dialog_modal_overlay, dialog_modal = self._get_dialog_components(keyboard_help_dialog)
dialog_dismiss_button = self._get_dialog_dismiss_button(dialog_modal)
# Scroll "Keyboard help" button into view to make sure Selenium can successfully click it
self.scroll_down(pixels=scroll_down)
......@@ -279,21 +288,26 @@ class InteractionTestBase(object):
self.assertFalse(dialog_modal_overlay.is_displayed())
self.assertFalse(dialog_modal.is_displayed())
if use_keyboard: # Try again with "?" key
self._page.send_keys("?")
if use_keyboard: # Check if "Keyboard Help" dialog can be dismissed using "ESC"
keyboard_help_button.send_keys(Keys.RETURN)
self.assertTrue(dialog_modal_overlay.is_displayed())
self.assertTrue(dialog_modal.is_displayed())
self._page.send_keys(Keys.ESCAPE)
self.assertFalse(dialog_modal_overlay.is_displayed())
self.assertFalse(dialog_modal.is_displayed())
def _switch_to_block(self, idx):
""" Only needed if ther eare multiple blocks on the page. """
self._page = self.browser.find_elements_by_css_selector(self.default_css_selector)[idx]
self.scroll_down(0)
class BasicInteractionTest(InteractionTestBase):
class DefaultDataTestMixin(object):
"""
Testing interactions with Drag and Drop XBlock against default data. If default data changes this will break.
Provides a test scenario with default options.
"""
PAGE_TITLE = 'Drag and Drop v2'
PAGE_ID = 'drag_and_drop_v2'
......@@ -321,6 +335,11 @@ class BasicInteractionTest(InteractionTestBase):
def _get_scenario_xml(self): # pylint: disable=no-self-use
return "<vertical_demo><drag-and-drop-v2/></vertical_demo>"
class BasicInteractionTest(DefaultDataTestMixin, InteractionTestBase):
"""
Testing interactions with Drag and Drop XBlock against default data. If default data changes this will break.
"""
def test_item_positive_feedback_on_good_move(self):
self.parameterized_item_positive_feedback_on_good_move(self.items_map)
......@@ -341,6 +360,74 @@ class BasicInteractionTest(InteractionTestBase):
@ddt
class EventsFiredTest(DefaultDataTestMixin, InteractionTestBase, BaseIntegrationTest):
"""
Tests that the analytics events are fired and in the proper order.
"""
# These events must be fired in this order.
scenarios = (
{
'name': 'edx.drag_and_drop_v2.loaded',
'data': {},
},
{
'name': 'edx.drag_and_drop_v2.item.picked_up',
'data': {'item_id': 0},
},
{
'name': 'grade',
'data': {'max_value': 1, 'value': (1.0 / 3)},
},
{
'name': 'edx.drag_and_drop_v2.item.dropped',
'data': {
'input': None,
'is_correct': True,
'is_correct_location': True,
'item_id': 0,
'location': u'The Top Zone',
},
},
{
'name': 'edx.drag_and_drop_v2.feedback.opened',
'data': {
'content': u'Correct! This one belongs to The Top Zone.',
'truncated': False,
},
},
{
'name': 'edx.drag_and_drop_v2.feedback.closed',
'data': {
'manually': False,
'content': u'Correct! This one belongs to The Top Zone.',
'truncated': False,
},
},
)
def setUp(self):
mock = Mock()
context = patch.object(WorkbenchRuntime, 'publish', mock)
context.start()
self.addCleanup(context.stop)
self.publish = mock
super(EventsFiredTest, self).setUp()
def _get_scenario_xml(self): # pylint: disable=no-self-use
return "<vertical_demo><drag-and-drop-v2/></vertical_demo>"
@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)
dummy, name, published_data = self.publish.call_args_list[index][0]
self.assertEqual(name, event['name'])
self.assertEqual(
published_data, event['data']
)
@ddt
class KeyboardInteractionTest(BasicInteractionTest, BaseIntegrationTest):
@data(Keys.RETURN, Keys.SPACE, Keys.CONTROL+'m', Keys.COMMAND+'m')
def test_item_positive_feedback_on_good_move_with_keyboard(self, action_key):
......@@ -471,3 +558,14 @@ class MultipleBlocksDataInteraction(InteractionTestBase, BaseIntegrationTest):
self.parameterized_final_feedback_and_reset(self.item_maps['block1'], self.feedback['block1'])
self._switch_to_block(1)
self.parameterized_final_feedback_and_reset(self.item_maps['block2'], self.feedback['block2'], scroll_down=900)
def test_keyboard_help(self):
self._switch_to_block(0)
# Test mouse and keyboard interaction
self.interact_with_keyboard_help()
self.interact_with_keyboard_help(use_keyboard=True)
self._switch_to_block(1)
# Test mouse and keyboard interaction
self.interact_with_keyboard_help(scroll_down=900)
self.interact_with_keyboard_help(scroll_down=0, use_keyboard=True)
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment