Commit 37637e4b by Braden MacDonald

Merge pull request #57 from open-craft/jill/feature-item-alignment

Allow to set automatic alignment of dragged items in drop zones (second attempt)
parents b8e64fc1 700e4001
Development version:
--------------------
* Added the ability to specify automatic alignment of dropped items. (PR #57)
Version 2.0.4 (2016-03-10)
--------------------------
......
......@@ -11,6 +11,7 @@ The editor is fully guided. Features include:
* custom zone labels
* ability to show or hide zone borders
* custom text and background colors for items
* optional auto-alignment for items (left, right, center)
* image items
* items prompting for additional (numerical) input after being dropped
* decoy items that don't have a zone
......@@ -102,7 +103,7 @@ above the background image, the introductory feedback (shown
initially), and the final feedback (shown after the learner
successfully completes the drag and drop problem).
![Drop zone edit](https://raw.githubusercontent.com/edx-solutions/xblock-drag-and-drop-v2/c955a38dc3a1aaf609c586d293ce19b282e11ffd/doc/img/edit-view-zones.png)
![Drop zone edit](https://raw.githubusercontent.com/edx-solutions/xblock-drag-and-drop-v2/ebd0b52d971bbf93b9c3873f310bd72d336d865b/doc/img/edit-view-zones.png)
In the next step, you set the URL and description for the background
image and define the properties of the drop zones. For each zone you
......@@ -114,6 +115,16 @@ whether or not to display borders outlining the zones. It is possible
to define an arbitrary number of drop zones as long as their labels
are unique.
Additionally, you can specify the alignment for items once they are dropped in
the zone. No alignment is the default, and causes items to stay where the
learner drops them. Left alignment causes dropped items to be placed from left
to right across the zone. Right alignment causes the items to be placed from
right to left across the zone. Center alignment places items from top to bottom
along the center of the zone. If left, right, or center alignment is chosen,
items dropped in a zone will not overlap, but if the zone is not made large
enough for all its items, they will overflow the bottom of the zone, and
potentially, overlap the zones below.
![Drag item edit](https://raw.githubusercontent.com/edx-solutions/xblock-drag-and-drop-v2/c955a38dc3a1aaf609c586d293ce19b282e11ffd/doc/img/edit-view-items.png)
In the final step, you define the background and text color for drag
......
doc/img/edit-view-zones.png

52.5 KB | W: | H:

doc/img/edit-view-zones.png

77.3 KB | W: | H:

doc/img/edit-view-zones.png
doc/img/edit-view-zones.png
doc/img/edit-view-zones.png
doc/img/edit-view-zones.png
  • 2-up
  • Swipe
  • Onion skin
......@@ -133,6 +133,8 @@
display: inline-block;
width: 100%; /* Make sure size of content never exceeds size of item */
/* (this can happen if item displays image whose width exceeds computed max-width of item) */
overflow-wrap: break-word;
word-wrap: break-word;
}
/* Placed option */
......@@ -142,6 +144,33 @@
transform: translate(-50%, -50%); /* These blocks are to be centered on their absolute x,y position */
}
/* Placed options in an aligned zone */
.xblock--drag-and-drop .zone .item-wrapper {
width: 100%;
position: relative;
}
.xblock--drag-and-drop .zone .item-align .option {
transform: none;
position: relative;
vertical-align: top;
margin-right: 2px;
margin-bottom: 2px;
}
.xblock--drag-and-drop .zone .item-align-left {
text-align: left;
}
.xblock--drag-and-drop .zone .item-align-right {
text-align: right;
}
.xblock--drag-and-drop .zone .item-align-center {
text-align: center;
}
.xblock--drag-and-drop .zone .item-align-center .option {
display: block;
margin-left: auto;
margin-right: auto;
}
/* Focused option */
.xblock--drag-and-drop .drag-container .item-bank .option:focus,
.xblock--drag-and-drop .drag-container .item-bank .option:hover,
......
......@@ -144,7 +144,7 @@
here we make sure that both input fields get the same value for line-height */
}
.xblock--drag-and-drop--editor .zones-form .zone-row .layout {
.xblock--drag-and-drop--editor .zones-form .zone-row .alignment {
margin-bottom: 15px;
}
......@@ -160,6 +160,7 @@
height: 128px;
}
.xblock--drag-and-drop--editor .zones-form .zones-form-help,
.xblock--drag-and-drop--editor .target-image-form .target-image-form-help,
.xblock--drag-and-drop--editor .item-styles-form .item-styles-form-help {
margin-top: 5px;
......
......@@ -52,19 +52,18 @@ function DragNDropTemplates(url_name) {
);
};
var getZoneTitle = function(zoneUID, ctx) {
// Given the context and a zone UID, return the zone's title
var getZone = function(zoneUID, ctx) {
for (var i = 0; i < ctx.zones.length; i++) {
if (ctx.zones[i].uid === zoneUID) {
return ctx.zones[i].title;
return ctx.zones[i];
}
}
return "Unknown Zone"; // This title should never be seen, so does not need i18n
}
};
var itemTemplate = function(item, ctx) {
// Define properties
var className = (item.class_name) ? item.class_name : "";
var zone = getZone(item.zone, ctx) || {};
if (item.has_image) {
className += " " + "option-with-image";
}
......@@ -89,12 +88,31 @@ function DragNDropTemplates(url_name) {
style['outline-color'] = item.color;
}
if (item.is_placed) {
if (item.zone_align === 'none') {
// This is not an "aligned" zone, so the item gets positioned where the learner dropped it.
style.left = item.x_percent + "%";
style.top = item.y_percent + "%";
if (item.widthPercent) {
if (item.widthPercent) { // This item has an author-defined explicit width
style.width = item.widthPercent + "%";
style.maxWidth = item.widthPercent + "%"; // default maxWidth is ~33%
} else if (item.imgNaturalWidth) {
style.maxWidth = item.widthPercent + "%"; // default maxWidth is ~30%
}
} else {
// This is an "aligned" zone, so the item position within the zone is calculated by the browser.
// Allow for the input + button width for aligned items
if (item.input) {
style.marginRight = '190px';
}
// Make up for the fact we're in a wrapper container by calculating percentage differences.
var maxWidth = (item.widthPercent || 30) / 100;
var widthPercent = zone.width_percent / 100;
style.maxWidth = ((1 / (widthPercent / maxWidth)) * 100) + '%';
if (item.widthPercent) {
style.width = style.maxWidth;
}
}
// Finally, if the item is using automatic sizing and contains an image, we
// always prefer the natural width of the image (subject to the max-width):
if (item.imgNaturalWidth && !item.widthPercent) {
style.width = (item.imgNaturalWidth + 22) + "px"; // 22px is for 10px padding + 1px border each side
// ^ Hack to detect image width at runtime and make webkit consistent with Firefox
}
......@@ -126,10 +144,11 @@ function DragNDropTemplates(url_name) {
// Insert information about zone in which this item has been placed
var item_description_id = url_name + '-item-' + item.value + '-description';
item_content.properties.attributes = { 'aria-describedby': item_description_id };
var zone_title = (zone.title || "Unknown Zone"); // This "Unknown" text should never be seen, so does not need i18n
var item_description = h(
'div',
{ id: item_description_id, className: 'sr' },
gettext('Correctly placed in: ') + getZoneTitle(item.zone, ctx)
gettext('Correctly placed in: ') + zone_title
);
children.splice(1, 0, item_description);
}
......@@ -153,6 +172,17 @@ function DragNDropTemplates(url_name) {
var zoneTemplate = function(zone, ctx) {
var className = ctx.display_zone_labels ? 'zone-name' : 'zone-name sr';
var selector = ctx.display_zone_borders ? 'div.zone.zone-with-borders' : 'div.zone';
// If zone is aligned, mark its item alignment
// and render its placed items as children
var item_wrapper = 'div.item-wrapper';
var items_in_zone = [];
if (zone.align !== 'none') {
item_wrapper += '.item-align.item-align-' + zone.align;
var is_item_in_zone = function(i) { return i.is_placed && (i.zone === zone.uid); };
items_in_zone = $.grep(ctx.items, is_item_in_zone);
}
return (
h(
selector,
......@@ -163,6 +193,8 @@ function DragNDropTemplates(url_name) {
'dropzone': 'move',
'aria-dropeffect': 'move',
'data-uid': zone.uid,
'data-zone_id': zone.id,
'data-zone_align': zone.align,
'role': 'button',
},
style: {
......@@ -172,7 +204,8 @@ function DragNDropTemplates(url_name) {
},
[
h('p', { className: className }, zone.title),
h('p', { className: 'zone-description sr' }, zone.description)
h('p', { className: 'zone-description sr' }, zone.description),
h(item_wrapper, renderCollection(itemTemplate, items_in_zone, ctx))
]
)
);
......@@ -232,9 +265,13 @@ function DragNDropTemplates(url_name) {
if (ctx.popup_html && !ctx.last_action_correct) {
popupSelector += '.popup-incorrect';
}
// Render only items_in_bank and items_placed_unaligned here;
// items placed in aligned zones will be rendered by zoneTemplate.
var is_item_placed = function(i) { return i.is_placed; };
var items_placed = $.grep(ctx.items, is_item_placed);
var items_in_bank = $.grep(ctx.items, is_item_placed, true);
var is_item_placed_unaligned = function(i) { return i.zone_align === 'none'; };
var items_placed_unaligned = $.grep(items_placed, is_item_placed_unaligned);
return (
h('section.themed-xblock.xblock--drag-and-drop', [
problemTitle,
......@@ -271,7 +308,7 @@ function DragNDropTemplates(url_name) {
]
),
renderCollection(zoneTemplate, ctx.zones, ctx),
renderCollection(itemTemplate, items_placed, ctx),
renderCollection(itemTemplate, items_placed_unaligned, ctx)
]
),
]),
......@@ -335,6 +372,7 @@ function DragAndDropBlock(runtime, element, configuration) {
state = stateResult[0]; // stateResult is an array of [data, statusText, jqXHR]
migrateConfiguration(bgImg.width);
migrateState(bgImg.width, bgImg.height);
markItemZoneAlign();
bgImgNaturalWidth = bgImg.width;
// Set up event handlers:
......@@ -596,6 +634,8 @@ function DragAndDropBlock(runtime, element, configuration) {
$anchor = $zone;
}
var zone = String($zone.data('uid'));
var zone_id = $zone.data('zone_id');
var zone_align = $zone.data('zone_align');
var $target_img = $root.find('.target-img');
// Calculate the position of the item to place relative to the image.
......@@ -606,6 +646,7 @@ function DragAndDropBlock(runtime, element, configuration) {
state.items[item_id] = {
zone: zone,
zone_align: zone_align,
x_percent: x_pos_percent,
y_percent: y_pos_percent,
submitting_location: true,
......@@ -879,6 +920,7 @@ function DragAndDropBlock(runtime, element, configuration) {
if (item_user_state) {
itemProperties.is_placed = true;
itemProperties.zone = item_user_state.zone;
itemProperties.zone_align = item_user_state.zone_align;
itemProperties.x_percent = item_user_state.x_percent;
itemProperties.y_percent = item_user_state.y_percent;
}
......@@ -963,5 +1005,22 @@ function DragAndDropBlock(runtime, element, configuration) {
});
};
/**
* markItemZoneAlign: Mark the items placed in an aligned zone with the zone
* alignment, so they can be properly placed inside the zone.
* We have do this in JS, not python, since zone configurations may change.
*/
var markItemZoneAlign = function() {
var zone_alignments = {};
configuration.zones.forEach(function(zone) {
if (!zone.align) zone.align = 'none';
zone_alignments[zone.uid] = zone.align;
});
Object.keys(state.items).forEach(function(item_id) {
var item = state.items[item_id];
item.zone_align = zone_alignments[item.zone] || 'none';
});
};
init();
}
......@@ -14,6 +14,12 @@ function DragAndDropEditBlock(runtime, element, params) {
}
return Number(value).toFixed(Number(value) == parseInt(value) ? 0 : 1);
});
Handlebars.registerHelper('ifeq', function(v1, v2, options) {
if (v1 === v2) {
return options.fn(this);
}
return options.inverse(this);
});
var $element = $(element);
......@@ -172,6 +178,7 @@ function DragAndDropEditBlock(runtime, element, params) {
})
.on('click', '.remove-zone', _fn.build.form.zone.remove)
.on('input', '.zone-row input', _fn.build.form.zone.changedInputHandler)
.on('change', '.align-select', _fn.build.form.zone.changedInputHandler)
.on('click', '.target-image-form button', function(e) {
e.preventDefault();
......@@ -240,6 +247,7 @@ function DragAndDropEditBlock(runtime, element, params) {
height: oldZone.height || 100,
x: oldZone.x || 0,
y: oldZone.y || 0,
align: oldZone.align || ''
};
_fn.build.form.zone.zoneObjects.push(zoneObj);
......@@ -316,10 +324,12 @@ function DragAndDropEditBlock(runtime, element, params) {
y_percent: (+zoneObj.y) / imgHeight * 100,
width_percent: (+zoneObj.width) / imgWidth * 100,
height_percent: (+zoneObj.height) / imgHeight * 100,
align: zoneObj.align
})
);
});
},
changedInputHandler: function(ev) {
// Called when any of the inputs have changed.
var $changedInput = $(ev.currentTarget);
......@@ -337,6 +347,8 @@ function DragAndDropEditBlock(runtime, element, params) {
record.x = $changedInput.val();
} else if ($changedInput.hasClass('y')) {
record.y = $changedInput.val();
} else if ($changedInput.hasClass('align-select')) {
record.align = $changedInput.val();
}
_fn.build.form.zone.renderZonesPreview();
},
......
......@@ -51,6 +51,34 @@
class="coord y"
value="{{ zone.y }}" />
</div>
<div class="alignment">
<label for="zone-{{index}}-align">
{{i18n "Alignment"}}
</label>
<select id="zone-{{index}}-align"
class="align-select"
aria-describedby="zone-align-description">
<option value=""
{{#ifeq zone.align ""}}selected{{/ifeq}}>
{{i18n "none"}}
</option>
<option value="left"
{{#ifeq zone.align "left"}}selected{{/ifeq}}>
{{i18n "left"}}
</option>
<option value="center"
{{#ifeq zone.align "center"}}selected{{/ifeq}}>
{{i18n "center"}}
</option>
<option value="right"
{{#ifeq zone.align "right"}}selected{{/ifeq}}>
{{i18n "right"}}
</option>
</select>
<div id="zone-align-description" class="zones-form-help">
{{i18n "Align dropped items to the left, center, or right. Default is no alignment (items stay exactly where the user drops them)."}}
</div>
</div>
</div>
</script>
......
......@@ -2,16 +2,16 @@
{% if img == "wide" %}
"targetImg": "{{img_wide_url}}",
"zones": [
{"title": "Zone 1/3", "width": 533, "height": 200, "x": "0", "y": "0"},
{"title": "Zone 50%", "width": 800, "height": 200, "x": "0", "y": "350"},
{"title": "Zone 75%", "width": 1200, "height": 200, "x": "0", "y": "700"}
{"title": "Zone 1/3", "width": 533, "height": 200, "x": "0", "y": "0" {% if align_zones %}, "align": "left" {% endif %} },
{"title": "Zone 50%", "width": 800, "height": 200, "x": "0", "y": "350" {% if align_zones %}, "align": "center" {% endif %} },
{"title": "Zone 75%", "width": 1200, "height": 200, "x": "0", "y": "700" {% if align_zones %}, "align": "right" {% endif %} }
],
{% else %}
"targetImg": "{{img_square_url}}",
"zones": [
{"title": "Zone 1/3", "width": 166, "height": 100, "x": "0", "y": "0"},
{"title": "Zone 50%", "width": 250, "height": 100, "x": "0", "y": "200"},
{"title": "Zone 75%", "width": 375, "height": 100, "x": "0", "y": "400"}
{"title": "Zone 1/3", "width": 166, "height": 100, "x": "0", "y": "0" {% if align_zones %}, "align": "left" {% endif %} },
{"title": "Zone 50%", "width": 250, "height": 100, "x": "0", "y": "200" {% if align_zones %}, "align": "center" {% endif %} },
{"title": "Zone 75%", "width": 375, "height": 100, "x": "0", "y": "400" {% if align_zones %}, "align": "right" {% endif %} }
],
{% endif %}
"displayBorders": true,
......
{
"zones": [
{
"index": 1,
"title": "Zone No Align",
"width": 200,
"height": 100,
"x": 0,
"y": 0,
"align": "",
"id": "zone-none"
},
{
"index": 1,
"title": "Zone Invalid Align",
"width": 200,
"height": 100,
"x": 0,
"y": 100,
"align": "invalid",
"id": "zone-invalid"
},
{
"index": 2,
"title": "Zone Left Align",
"width": 200,
"height": 100,
"x": 0,
"y": 200,
"align": "left",
"id": "zone-left"
},
{
"index": 3,
"title": "Zone Right Align",
"width": 200,
"height": 100,
"x": 0,
"y": 300,
"align": "right",
"id": "zone-right"
},
{
"index": 4,
"title": "Zone Center Align",
"width": 200,
"height": 100,
"x": 0,
"y": 400,
"align": "center",
"id": "zone-center"
}
],
"items": [
{
"displayName": "AAAA",
"feedback": {
"incorrect": "No",
"correct": "Yes"
},
"zone": "Zone No Align",
"imageURL": "",
"id": 0
},
{
"displayName": "AAAA",
"feedback": {
"incorrect": "No",
"correct": "Yes"
},
"zone": "Zone No Align",
"imageURL": "",
"id": 1
},
{
"displayName": "AAAA",
"feedback": {
"incorrect": "No",
"correct": "Yes"
},
"zone": "Zone No Align",
"imageURL": "",
"id": 2
},
{
"displayName": "BBBB",
"feedback": {
"incorrect": "No",
"correct": "Yes"
},
"zone": "Zone Invalid Align",
"imageURL": "",
"id": 3
},
{
"displayName": "BBBB",
"feedback": {
"incorrect": "No",
"correct": "Yes"
},
"zone": "Zone Invalid Align",
"imageURL": "",
"id": 4
},
{
"displayName": "BBBB",
"feedback": {
"incorrect": "No",
"correct": "Yes"
},
"zone": "Zone Invalid Align",
"imageURL": "",
"id": 5
},
{
"displayName": "CCCC",
"feedback": {
"incorrect": "No",
"correct": "Yes"
},
"zone": "Zone Left Align",
"imageURL": "",
"id": 6
},
{
"displayName": "CCCC",
"feedback": {
"incorrect": "No",
"correct": "Yes"
},
"zone": "Zone Left Align",
"imageURL": "",
"id": 7
},
{
"displayName": "CCCC",
"feedback": {
"incorrect": "No",
"correct": "Yes"
},
"zone": "Zone Left Align",
"imageURL": "",
"id": 8
},
{
"displayName": "DDDD",
"feedback": {
"incorrect": "No",
"correct": "Yes"
},
"zone": "Zone Right Align",
"imageURL": "",
"id": 9
},
{
"displayName": "DDDD",
"feedback": {
"incorrect": "No",
"correct": "Yes"
},
"zone": "Zone Right Align",
"imageURL": "",
"id": 10
},
{
"displayName": "DDDD",
"feedback": {
"incorrect": "No",
"correct": "Yes"
},
"zone": "Zone Right Align",
"imageURL": "",
"id": 11
},
{
"displayName": "EEEE",
"feedback": {
"incorrect": "No",
"correct": "Yes"
},
"zone": "Zone Center Align",
"imageURL": "",
"id": 12
},
{
"displayName": "EEEE",
"feedback": {
"incorrect": "No",
"correct": "Yes"
},
"zone": "Zone Center Align",
"imageURL": "",
"id": 13
},
{
"displayName": "EEEE",
"feedback": {
"incorrect": "No",
"correct": "Yes"
},
"zone": "Zone Center Align",
"imageURL": "",
"id": 14
},
],
"feedback": {
"start": "Intro",
"finish": "Final"
},
}
......@@ -91,6 +91,13 @@ class BaseIntegrationTest(SeleniumBaseTest):
def scroll_down(self, pixels=50):
self.browser.execute_script("$(window).scrollTop({})".format(pixels))
def _get_style(self, selector, style, computed=True):
if computed:
query = 'return getComputedStyle($("{selector}").get(0)).{style}'
else:
query = 'return $("{selector}").get(0).style.{style}'
return self.browser.execute_script(query.format(selector=selector, style=style))
@staticmethod
def get_element_html(element):
return element.get_attribute('innerHTML').strip()
......
......@@ -137,7 +137,9 @@ class InteractionTestBase(object):
item = self._get_placed_item_by_value(item_value)
self.wait_until_visible(item)
item_content = item.find_element_by_css_selector('.item-content')
self.wait_until_visible(item_content)
item_description = item.find_element_by_css_selector('.sr')
self.wait_until_visible(item_description)
item_description_id = '-item-{}-description'.format(item_value)
self.assertIsNone(item.get_attribute('tabindex'))
......@@ -592,3 +594,90 @@ class MultipleBlocksDataInteraction(InteractionTestBase, BaseIntegrationTest):
# Test mouse and keyboard interaction
self.interact_with_keyboard_help(scroll_down=900)
self.interact_with_keyboard_help(scroll_down=0, use_keyboard=True)
@ddt
class ZoneAlignInteractionTest(InteractionTestBase, BaseIntegrationTest):
"""
Verifying Drag and Drop XBlock interactions using zone alignment.
"""
PAGE_TITLE = 'Drag and Drop v2'
PAGE_ID = 'drag_and_drop_v2'
ACTION_KEYS = (None, Keys.RETURN, Keys.SPACE, Keys.CONTROL+'m', Keys.COMMAND+'m')
def setUp(self):
super(ZoneAlignInteractionTest, self).setUp()
def _get_scenario_xml(self):
return self._get_custom_scenario_xml("data/test_zone_align.json")
def _assert_zone_align_item(self, item_id, zone_id, align, action_key=None):
"""
Test items placed in a zone with the given align setting.
Ensure that they are children of the zone.
"""
# parent container has the expected alignment
item_wrapper_selector = "div[data-uid='{zone_id}'] .item-wrapper".format(zone_id=zone_id)
self.assertEquals(self._get_style(item_wrapper_selector, 'textAlign'), align)
# Items placed in zones with align setting are children of the zone
zone_item_selector = '{item_wrapper_selector} .option'.format(item_wrapper_selector=item_wrapper_selector)
prev_placed_items = self._page.find_elements_by_css_selector(zone_item_selector)
self.place_item(item_id, zone_id, action_key)
placed_items = self._page.find_elements_by_css_selector(zone_item_selector)
self.assertEquals(len(placed_items), len(prev_placed_items) + 1)
# Not children of the target
target_item = '.target > .option'
self.assertEquals(len(self._page.find_elements_by_css_selector(target_item)), 0)
# Aligned items are relative positioned, with no transform or top/left
self.assertEquals(self._get_style(zone_item_selector, 'position'), 'relative')
self.assertEquals(self._get_style(zone_item_selector, 'transform'), 'none')
self.assertEquals(self._get_style(zone_item_selector, 'left'), '0px')
self.assertEquals(self._get_style(zone_item_selector, 'top'), '0px')
# Center-aligned items are display block
if align == 'center':
self.assertEquals(self._get_style(zone_item_selector, 'display'), 'block')
# but other aligned items are just inline-block
else:
self.assertEquals(self._get_style(zone_item_selector, 'display'), 'inline-block')
def test_no_zone_align(self):
"""
Test items placed in a zone with no align setting.
Ensure that they are children of div.target, not the zone.
"""
zone_id = "Zone No Align"
self.place_item(0, zone_id)
zone_item_selector = "div[data-uid='{zone_id}'] .item-wrapper .option".format(zone_id=zone_id)
self.assertEquals(len(self._page.find_elements_by_css_selector(zone_item_selector)), 0)
target_item_selector = '.target > .option'
placed_items = self._page.find_elements_by_css_selector(target_item_selector)
self.assertEquals(len(placed_items), 1)
self.assertEquals(placed_items[0].get_attribute('data-value'), '0')
# Non-aligned items are absolute positioned, with top/bottom set to px
self.assertEquals(self._get_style(target_item_selector, 'position'), 'absolute')
self.assertRegexpMatches(self._get_style(target_item_selector, 'left'), r'^\d+(\.\d+)?px$')
self.assertRegexpMatches(self._get_style(target_item_selector, 'top'), r'^\d+(\.\d+)?px$')
@data(
([3, 4, 5], "Zone Invalid Align", "start"),
([6, 7, 8], "Zone Left Align", "left"),
([9, 10, 11], "Zone Right Align", "right"),
([12, 13, 14], "Zone Center Align", "center"),
)
@unpack
def test_zone_align(self, items, zone, alignment):
reset = self._get_reset_button()
for item in items:
for action_key in self.ACTION_KEYS:
self._assert_zone_align_item(item, zone, alignment, action_key)
# Reset exercise
self.scroll_down(pixels=200)
reset.click()
self.scroll_down(pixels=0)
......@@ -65,13 +65,6 @@ class TestDragAndDropRender(BaseIntegrationTest):
self.browser.get(self.live_server_url)
self._page = self.go_to_page(self.PAGE_TITLE)
def _get_style(self, selector, style, computed=True):
if computed:
query = 'return getComputedStyle($("{selector}").get(0)).{style}'
else:
query = 'return $("{selector}").get(0).style.{style}'
return self.browser.execute_script(query.format(selector=selector, style=style))
def _assert_box_percentages(self, selector, left, top, width, height):
""" Assert that the element 'selector' has the specified position/size percentages """
values = {key: self._get_style(selector, key, False) for key in ['left', 'top', 'width', 'height']}
......@@ -188,6 +181,7 @@ class TestDragAndDropRender(BaseIntegrationTest):
self.assertEqual(zone.get_attribute('dropzone'), 'move')
self.assertEqual(zone.get_attribute('aria-dropeffect'), 'move')
self.assertEqual(zone.get_attribute('data-uid'), 'Zone {}'.format(zone_number))
self.assertEqual(zone.get_attribute('data-zone_align'), 'none')
self.assertIn('ui-droppable', self.get_element_classes(zone))
zone_box_percentages = box_percentages[index]
self._assert_box_percentages( # pylint: disable=star-args
......@@ -272,3 +266,31 @@ class TestDragAndDropRender(BaseIntegrationTest):
for zone in zones:
zone_name = zone.find_element_by_css_selector('p.zone-name')
self.assertNotIn('sr', zone_name.get_attribute('class'))
@ddt
class TestDragAndDropRenderZoneAlign(BaseIntegrationTest):
"""
Verifying Drag and Drop XBlock rendering using zone alignment.
"""
PAGE_TITLE = 'Drag and Drop v2'
PAGE_ID = 'drag_and_drop_v2'
def setUp(self):
super(TestDragAndDropRenderZoneAlign, self).setUp()
scenario_xml = self._get_custom_scenario_xml("data/test_zone_align.json")
self._add_scenario(self.PAGE_ID, self.PAGE_TITLE, scenario_xml)
self._page = self.go_to_page(self.PAGE_TITLE)
def test_zone_align(self):
expected_alignments = {
"#-Zone_No_Align": "start",
"#-Zone_Invalid_Align": "start",
"#-Zone_Left_Align": "left",
"#-Zone_Right_Align": "right",
"#-Zone_Center_Align": "center"
}
for zone_id, expected_alignment in expected_alignments.items():
selector = "{zone_id} .item-wrapper".format(zone_id=zone_id)
self.assertEquals(self._get_style(selector, "textAlign"), expected_alignment)
self.assertEquals(self._get_style(selector, "textAlign", computed=True), expected_alignment)
......@@ -48,9 +48,10 @@ class SizingTests(InteractionTestBase, BaseIntegrationTest):
"""
PAGE_TITLE = 'Drag and Drop v2 Sizing'
PAGE_ID = 'drag_and_drop_v2_sizing'
ALIGN_ZONES = False # Set to True to test the feature that aligns draggable items when dropped.
@staticmethod
def _get_scenario_xml():
@classmethod
def _get_scenario_xml(cls):
"""
Set up the test scenario:
* An upper dndv2 xblock with a wide image (1600x900 SVG)
......@@ -62,6 +63,7 @@ class SizingTests(InteractionTestBase, BaseIntegrationTest):
"""
params = {
"img": "wide",
"align_zones": cls.ALIGN_ZONES,
"img_wide_url": _svg_to_data_uri('dnd-bg-wide.svg'),
"img_square_url": _svg_to_data_uri('dnd-bg-square.svg'),
"img_400x300_url": _svg_to_data_uri('400x300.svg'),
......@@ -238,6 +240,18 @@ class SizingTests(InteractionTestBase, BaseIntegrationTest):
)
class AlignedSizingTests(SizingTests):
"""
Run the same tests as SizingTests, but with aligned zones.
The sizing of draggable items should be consistent when the "align" feature
of each zone is enabled. (This is the feature that aligns draggable items
once they're placed, rather than keeping them exactly where they were
dropped.)
"""
ALIGN_ZONES = True
class SizingBackwardsCompatibilityTests(InteractionTestBase, BaseIntegrationTest):
"""
Test backwards compatibility with data generated in older versions of this block.
......
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