Commit 1a119b6e by Tim Krones

Merge pull request #48 from open-craft/add-theming

Make DnDv2 themable
parents b9708906 ad9887f1
...@@ -14,6 +14,6 @@ script: ...@@ -14,6 +14,6 @@ script:
- pylint drag_and_drop_v2 tests - pylint drag_and_drop_v2 tests
- python run_tests.py - python run_tests.py
notifications: notifications:
email: false email: false
addons: addons:
firefox: "36.0" firefox: "36.0"
...@@ -36,9 +36,44 @@ Install the requirements into the Python virtual environment of your ...@@ -36,9 +36,44 @@ Install the requirements into the Python virtual environment of your
root folder: root folder:
```bash ```bash
$ pip install -e . $ pip install -r requirements.txt
``` ```
Theming
-------
The Drag and Drop XBlock ships with an alternate theme called "Apros"
that you can enable by adding the following entry to `XBLOCK_SETTINGS`
in `lms.env.json`:
```json
"drag-and-drop-v2": {
"theme": {
"package": "drag_and_drop_v2",
"locations": ["public/themes/apros.css"]
}
}
```
You can use the same approach to apply a custom theme:
`"package"` can refer to any Python package in your virtualenv, which
means you can develop and maintain your own theme in a separate
package. There is no need to fork or modify this repository in any way
to customize the look and feel of your Drag and Drop exercises.
`"locations"` is a list of relative paths pointing to CSS files
belonging to your theme. While the XBlock loads, files will be added
to it in the order that they appear in this list. (This means that if
there are rules with identical selectors spread out over different
files, rules in files that appear later in the list will take
precedence over those that appear earlier.)
Finally, note that the default (unthemed) appearance of the Drag and
Drop XBlock has been optimized for accessibility, so its use is
encouraged -- especially for courses targeting large and/or
potentially diverse audiences.
Enabling in Studio Enabling in Studio
------------------ ------------------
...@@ -105,15 +140,27 @@ You can define an arbitrary number of drag items. ...@@ -105,15 +140,27 @@ You can define an arbitrary number of drag items.
Testing Testing
------- -------
Inside a fresh virtualenv, run Inside a fresh virtualenv, `cd` into the root folder of this repository
(`xblock-drag-and-drop-v2`) and run
```bash ```bash
$ cd .../xblock-drag-and-drop-v2/
$ sh install_test_deps.sh $ sh install_test_deps.sh
``` ```
To run the tests, from the xblock-drag-and-drop-v2 repository root: You can then run the entire test suite via
```bash ```bash
$ python run_tests.py $ python run_tests.py
``` ```
To only run the unit test suite, do
```bash
$ python run_tests.py tests/unit/
```
Similarly, you can run the integration test suite via
```bash
$ python run_tests.py tests/integration/
```
...@@ -11,14 +11,22 @@ import urllib ...@@ -11,14 +11,22 @@ import urllib
from xblock.core import XBlock from xblock.core import XBlock
from xblock.fields import Scope, String, Dict, Float, Boolean from xblock.fields import Scope, String, Dict, Float, Boolean
from xblock.fragment import Fragment from xblock.fragment import Fragment
from xblockutils.resources import ResourceLoader
from xblockutils.settings import XBlockWithSettingsMixin, ThemableXBlockMixin
from .utils import _, render_template, load_resource # pylint: disable=unused-import from .utils import _ # pylint: disable=unused-import
from .default_data import DEFAULT_DATA from .default_data import DEFAULT_DATA
# Globals ###########################################################
loader = ResourceLoader(__name__)
# Classes ########################################################### # Classes ###########################################################
class DragAndDropBlock(XBlock): @XBlock.wants('settings')
class DragAndDropBlock(XBlock, XBlockWithSettingsMixin, ThemableXBlockMixin):
""" """
XBlock providing a Drag and Drop question XBlock providing a Drag and Drop question
""" """
...@@ -96,6 +104,7 @@ class DragAndDropBlock(XBlock): ...@@ -96,6 +104,7 @@ class DragAndDropBlock(XBlock):
default=False, default=False,
) )
block_settings_key = 'drag-and-drop-v2'
has_score = True has_score = True
def _(self, text): def _(self, text):
...@@ -108,7 +117,7 @@ class DragAndDropBlock(XBlock): ...@@ -108,7 +117,7 @@ class DragAndDropBlock(XBlock):
""" """
fragment = Fragment() fragment = Fragment()
fragment.add_content(render_template('/templates/html/drag_and_drop.html')) fragment.add_content(loader.render_template('/templates/html/drag_and_drop.html'))
css_urls = ( css_urls = (
'public/css/vendor/jquery-ui-1.10.4.custom.min.css', 'public/css/vendor/jquery-ui-1.10.4.custom.min.css',
'public/css/drag_and_drop.css' 'public/css/drag_and_drop.css'
...@@ -125,6 +134,8 @@ class DragAndDropBlock(XBlock): ...@@ -125,6 +134,8 @@ class DragAndDropBlock(XBlock):
for js_url in js_urls: for js_url in js_urls:
fragment.add_javascript_url(self.runtime.local_resource_url(self, js_url)) fragment.add_javascript_url(self.runtime.local_resource_url(self, js_url))
self.include_theme_files(fragment)
fragment.initialize_js('DragAndDropBlock', self.get_configuration()) fragment.initialize_js('DragAndDropBlock', self.get_configuration())
return fragment return fragment
...@@ -166,7 +177,7 @@ class DragAndDropBlock(XBlock): ...@@ -166,7 +177,7 @@ class DragAndDropBlock(XBlock):
Editing view in Studio Editing view in Studio
""" """
js_templates = load_resource('/templates/html/js_templates.html') js_templates = loader.load_unicode('/templates/html/js_templates.html')
help_texts = { help_texts = {
field_name: self._(field.help) field_name: self._(field.help)
for field_name, field in self.fields.viewitems() if hasattr(field, "help") for field_name, field in self.fields.viewitems() if hasattr(field, "help")
...@@ -179,7 +190,7 @@ class DragAndDropBlock(XBlock): ...@@ -179,7 +190,7 @@ class DragAndDropBlock(XBlock):
} }
fragment = Fragment() fragment = Fragment()
fragment.add_content(render_template('/templates/html/drag_and_drop_edit.html', context)) fragment.add_content(loader.render_template('/templates/html/drag_and_drop_edit.html', context))
css_urls = ( css_urls = (
'public/css/vendor/jquery-ui-1.10.4.custom.min.css', 'public/css/vendor/jquery-ui-1.10.4.custom.min.css',
......
...@@ -36,14 +36,11 @@ ...@@ -36,14 +36,11 @@
.xblock--drag-and-drop .drag-container { .xblock--drag-and-drop .drag-container {
width: auto; width: auto;
padding: 1%; padding: 1%;
background: #ebf0f2; background-color: #ebf0f2;
} }
/*.xblock--drag-and-drop .clear { /*** DRAGGABLE ITEMS ***/
clear: both;
}*/
/** Draggable Items **/
.xblock--drag-and-drop .item-bank { .xblock--drag-and-drop .item-bank {
display: -ms-flexbox; display: -ms-flexbox;
display: flex; display: flex;
...@@ -105,6 +102,8 @@ ...@@ -105,6 +102,8 @@
.xblock--drag-and-drop .drag-container .option .item-content { .xblock--drag-and-drop .drag-container .option .item-content {
display: inline-block; 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) */
} }
/* Placed option */ /* Placed option */
...@@ -169,12 +168,12 @@ ...@@ -169,12 +168,12 @@
} }
.xblock--drag-and-drop .drag-container .option .numerical-input.correct .input { .xblock--drag-and-drop .drag-container .option .numerical-input.correct .input {
background: #ceffce; background-color: #ceffce;
color: #087108; color: #087108;
} }
.xblock--drag-and-drop .drag-container .option .numerical-input.incorrect .input { .xblock--drag-and-drop .drag-container .option .numerical-input.incorrect .input {
background: #ffcece; background-color: #ffcece;
color: #ad0d0d; color: #ad0d0d;
} }
...@@ -182,7 +181,8 @@ ...@@ -182,7 +181,8 @@
opacity: 0.5; opacity: 0.5;
} }
/*** Drop Target ***/ /*** DROP TARGET ***/
.xblock--drag-and-drop .target { .xblock--drag-and-drop .target {
display: table; display: table;
/* 'display: table' makes this have the smallest size that fits the .target-img /* 'display: table' makes this have the smallest size that fits the .target-img
...@@ -192,7 +192,7 @@ ...@@ -192,7 +192,7 @@
height: auto; height: auto;
position: relative; position: relative;
margin-top: 1%; margin-top: 1%;
background: #fff; background-color: #fff;
} }
.xblock--drag-and-drop .target-img-wrapper { .xblock--drag-and-drop .target-img-wrapper {
...@@ -265,7 +265,9 @@ ...@@ -265,7 +265,9 @@
} }
/*** FEEDBACK ***/ /*** FEEDBACK ***/
.xblock--drag-and-drop .feedback { .xblock--drag-and-drop .feedback {
margin-bottom: 1%;
border-top: solid 1px #bdbdbd; border-top: solid 1px #bdbdbd;
} }
...@@ -275,7 +277,7 @@ ...@@ -275,7 +277,7 @@
top: 5%; top: 5%;
right: 5%; right: 5%;
border: 1px solid #fff; border: 1px solid #fff;
background: none repeat scroll 0 0 rgba(0, 0, 0, 0.8); background-color: rgba(0, 0, 0, 0.8);
width: 500px; width: 500px;
max-width: 90%; max-width: 90%;
min-height: 50px; min-height: 50px;
...@@ -303,6 +305,8 @@ ...@@ -303,6 +305,8 @@
font-size: 18pt; font-size: 18pt;
} }
/*** KEYBOARD HELP ***/
.xblock--drag-and-drop .keyboard-help { .xblock--drag-and-drop .keyboard-help {
margin-top: 3px; margin-top: 3px;
margin-bottom: 6px; margin-bottom: 6px;
...@@ -324,7 +328,7 @@ ...@@ -324,7 +328,7 @@
left: 0; left: 0;
width: 100%; width: 100%;
height: 100%; height: 100%;
background: #000; background-color: #000;
opacity: 0.5; opacity: 0.5;
z-index: 1500; z-index: 1500;
} }
...@@ -348,7 +352,7 @@ ...@@ -348,7 +352,7 @@
.xblock--drag-and-drop .modal-content { .xblock--drag-and-drop .modal-content {
border-radius: 5px; border-radius: 5px;
background: white; background-color: #ffffff;
margin-bottom: 5px; margin-bottom: 5px;
padding: 5px; padding: 5px;
} }
...@@ -378,6 +382,6 @@ ...@@ -378,6 +382,6 @@
padding: 0; padding: 0;
position: absolute; position: absolute;
width: 1px; width: 1px;
background: #ffffff; background-color: #ffffff;
color: #000000; color: #000000;
} }
...@@ -68,7 +68,7 @@ ...@@ -68,7 +68,7 @@
.xblock--drag-and-drop--editor .tab { .xblock--drag-and-drop--editor .tab {
width: 100%; width: 100%;
background: #eee; background-color: #eee;
padding: 3px 0; padding: 3px 0;
position: relative; position: relative;
} }
...@@ -177,7 +177,7 @@ ...@@ -177,7 +177,7 @@
} }
.xblock--drag-and-drop--editor .items-form .item { .xblock--drag-and-drop--editor .items-form .item {
background: #8fcaec; background-color: #8fcaec;
padding: 10px 0 1px; padding: 10px 0 1px;
margin: 15px 0; margin: 15px 0;
} }
...@@ -219,7 +219,7 @@ ...@@ -219,7 +219,7 @@
/** Buttons **/ /** Buttons **/
.xblock--drag-and-drop--editor .btn { .xblock--drag-and-drop--editor .btn {
background: #1d5280; background-color: #1d5280;
color: #fff; color: #fff;
border: 1px solid #156ab4; border: 1px solid #156ab4;
border-radius: 6px; border-radius: 6px;
...@@ -257,7 +257,7 @@ ...@@ -257,7 +257,7 @@
width: 14px; width: 14px;
height: 14px; height: 14px;
border-radius: 7px; border-radius: 7px;
background: #1d5280; background-color: #1d5280;
position: relative; position: relative;
float: left; float: left;
margin: 0 5px 0 0; margin: 0 5px 0 0;
...@@ -274,7 +274,7 @@ ...@@ -274,7 +274,7 @@
content: ''; content: '';
height: 10px; height: 10px;
width: 2px; width: 2px;
background: #fff; background-color: #fff;
position: relative; position: relative;
display: inline; display: inline;
float: left; float: left;
...@@ -286,7 +286,7 @@ ...@@ -286,7 +286,7 @@
content: ''; content: '';
height: 2px; height: 2px;
width: 10px; width: 10px;
background: #fff; background-color: #fff;
position: relative; position: relative;
display: inline; display: inline;
float: left; float: left;
...@@ -298,7 +298,7 @@ ...@@ -298,7 +298,7 @@
content: ''; content: '';
height: 10px; height: 10px;
width: 2px; width: 2px;
background: #fff; background-color: #fff;
position: relative; position: relative;
display: inline; display: inline;
float: left; float: left;
...@@ -313,7 +313,7 @@ ...@@ -313,7 +313,7 @@
content: ''; content: '';
height: 2px; height: 2px;
width: 10px; width: 10px;
background: #fff; background-color: #fff;
position: relative; position: relative;
display: inline; display: inline;
float: left; float: left;
...@@ -325,10 +325,10 @@ ...@@ -325,10 +325,10 @@
} }
.xblock--drag-and-drop--editor .remove-item .icon.remove { .xblock--drag-and-drop--editor .remove-item .icon.remove {
background: #fff; background-color: #fff;
color: #0072a7; /* Override default color from Studio to ensure contrast is large enough */ color: #0072a7; /* Override default color from Studio to ensure contrast is large enough */
} }
.xblock--drag-and-drop--editor .remove-item .icon.remove:before, .xblock--drag-and-drop--editor .remove-item .icon.remove:before,
.xblock--drag-and-drop--editor .remove-item .icon.remove:after { .xblock--drag-and-drop--editor .remove-item .icon.remove:after {
background: #1d5280; background-color: #1d5280;
} }
...@@ -303,7 +303,7 @@ function DragAndDropBlock(runtime, element, configuration) { ...@@ -303,7 +303,7 @@ function DragAndDropBlock(runtime, element, configuration) {
// Make zone accept items that are dropped using the mouse // Make zone accept items that are dropped using the mouse
$root.find('.zone').droppable({ $root.find('.zone').droppable({
accept: '.xblock--drag-and-drop .item-bank .option', accept: '.item-bank .option',
tolerance: 'pointer', tolerance: 'pointer',
drop: function(evt, ui) { drop: function(evt, ui) {
var $zone = $(this); var $zone = $(this);
...@@ -331,9 +331,9 @@ function DragAndDropBlock(runtime, element, configuration) { ...@@ -331,9 +331,9 @@ function DragAndDropBlock(runtime, element, configuration) {
// Make item draggable using the mouse // Make item draggable using the mouse
try { try {
$item.draggable({ $item.draggable({
containment: $root.find('.xblock--drag-and-drop .drag-container'), containment: $root.find('.drag-container'),
cursor: 'move', cursor: 'move',
stack: $root.find('.xblock--drag-and-drop .item-bank .option'), stack: $root.find('.item-bank .option'),
revert: 'invalid', revert: 'invalid',
revertDuration: 150, revertDuration: 150,
start: function(evt, ui) { start: function(evt, ui) {
......
...@@ -199,7 +199,7 @@ ...@@ -199,7 +199,7 @@
var items_placed = $.grep(ctx.items, is_item_placed); var items_placed = $.grep(ctx.items, is_item_placed);
var items_in_bank = $.grep(ctx.items, is_item_placed, true); var items_in_bank = $.grep(ctx.items, is_item_placed, true);
return ( return (
h('section.xblock--drag-and-drop', [ h('section.themed-xblock.xblock--drag-and-drop', [
problemHeader, problemHeader,
h('section.problem', {role: 'application'}, [ h('section.problem', {role: 'application'}, [
questionHeader, questionHeader,
......
.themed-xblock.xblock--drag-and-drop {
background-color: #fff;
}
/* Shared styles used in header and footer */
.themed-xblock.xblock--drag-and-drop .title1 {
color: #555555;
text-transform: uppercase;
font-weight: bold;
font-style: normal;
}
/* drag-container holds the .item-bank and the .target */
.themed-xblock.xblock--drag-and-drop .drag-container {
background-color: #ebf0f2;
}
.themed-xblock.xblock--drag-and-drop .item-bank {
border-radius: 0px;
}
/*** DRAGGABLE ITEMS ***/
.themed-xblock.xblock--drag-and-drop .drag-container .option {
border-radius: 0px;
font-size: 14px;
background-color: #2e83cd;
color: #fff;
opacity: 1;
}
.themed-xblock.xblock--drag-and-drop .drag-container .option .numerical-input.correct .input {
background-color: #ceffce;
color: #087108;
}
.themed-xblock.xblock--drag-and-drop .drag-container .option .numerical-input.incorrect .input {
background-color: #ffcece;
color: #ad0d0d;
}
.themed-xblock.xblock--drag-and-drop .drag-container .option.fade {
opacity: 0.5;
}
/*** DROP TARGET ***/
.themed-xblock.xblock--drag-and-drop .target {
background-color: #fff;
}
.themed-xblock.xblock--drag-and-drop .zone p {
font-family: Arial;
font-size: 16px;
font-weight: bold;
text-align: center;
text-transform: uppercase;
}
/*** FEEDBACK ***/
.themed-xblock.xblock--drag-and-drop .feedback {
border-top: solid 1px #bdbdbd;
}
.themed-xblock.xblock--drag-and-drop .popup {
background-color: #66a5b5;
}
.themed-xblock.xblock--drag-and-drop .popup .popup-content {
color: #ffffff;
font-size: 14px;
}
.themed-xblock.xblock--drag-and-drop .popup .close {
cursor: pointer;
color: #ffffff;
font-family: "fontawesome";
font-size: 18pt;
}
.themed-xblock.xblock--drag-and-drop .keyboard-help-button,
.themed-xblock.xblock--drag-and-drop .reset-button {
cursor: pointer;
color: #3384ca;
}
{% load i18n %} {% load i18n %}
<section class="xblock--drag-and-drop"> <section class="themed-xblock xblock--drag-and-drop">
<i class="fa fa-spin fa-spinner initial-load-spinner"></i>{% trans "Loading drag and drop exercise." %} <i class="fa fa-spin fa-spinner initial-load-spinner"></i>{% trans "Loading drag and drop exercise." %}
</section> </section>
...@@ -123,7 +123,7 @@ ...@@ -123,7 +123,7 @@
</div> </div>
<div class="row"> <div class="row">
<label for="item-{{id}}-numerical-margin"> <label for="item-{{id}}-numerical-margin">
{{i18n "Margin ± (when a numerical value is required, values entered by students must not differ from the expected value by more than this margin; default is zero)"}} {{i18n "Margin +/- (when a numerical value is required, values entered by students must not differ from the expected value by more than this margin; default is zero)"}}
</label> </label>
<input type="number" <input type="number"
step="0.1" step="0.1"
......
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
# Imports ###########################################################
import pkg_resources
from django.template import Context, Template
# Functions #########################################################
# Make '_' a no-op so we can scrape strings # Make '_' a no-op so we can scrape strings
def _(text): def _(text):
return text return text
def load_resource(resource_path):
"""
Gets the content of a resource
"""
resource_content = pkg_resources.resource_string(__name__, resource_path)
return resource_content
def render_template(template_path, context=None):
"""
Evaluate a template by resource path, applying the provided context
"""
if context is None:
context = {}
template_str = load_resource(template_path)
template = Template(template_str)
return template.render(Context(context))
...@@ -3,4 +3,4 @@ ...@@ -3,4 +3,4 @@
pip install -e git://github.com/edx/xblock-sdk.git@4e8e713e7dd886b8d2eb66b5001216b66b9af81a#egg=xblock-sdk pip install -e git://github.com/edx/xblock-sdk.git@4e8e713e7dd886b8d2eb66b5001216b66b9af81a#egg=xblock-sdk
cd $VIRTUAL_ENV/src/xblock-sdk/ && pip install -r requirements/base.txt \ cd $VIRTUAL_ENV/src/xblock-sdk/ && pip install -r requirements/base.txt \
&& pip install -r requirements/test.txt && cd - && pip install -r requirements/test.txt && cd -
python setup.py develop pip install -r requirements.txt
git+https://github.com/edx/xblock-utils.git@v1.0.0#egg=xblock-utils==v1.0.0
-e .
...@@ -31,7 +31,6 @@ setup( ...@@ -31,7 +31,6 @@ setup(
'xblock-utils', 'xblock-utils',
'ddt' 'ddt'
], ],
dependency_links = ['http://github.com/edx/xblock-utils/tarball/master#egg=xblock-utils'],
entry_points={ entry_points={
'xblock.v1': 'drag-and-drop-v2 = drag_and_drop_v2:DragAndDropBlock', 'xblock.v1': 'drag-and-drop-v2 = drag_and_drop_v2:DragAndDropBlock',
}, },
......
# Imports ########################################################### # Imports ###########################################################
from xml.sax.saxutils import escape from xml.sax.saxutils import escape
from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support.ui import WebDriverWait
from ..utils import load_resource
from workbench import scenarios from workbench import scenarios
from xblockutils.resources import ResourceLoader
from xblockutils.base_test import SeleniumBaseTest from xblockutils.base_test import SeleniumBaseTest
# Globals ###########################################################
loader = ResourceLoader(__name__)
# Classes ########################################################### # Classes ###########################################################
class BaseIntegrationTest(SeleniumBaseTest): class BaseIntegrationTest(SeleniumBaseTest):
default_css_selector = 'section.xblock--drag-and-drop' default_css_selector = 'section.themed-xblock.xblock--drag-and-drop'
module_name = __name__ module_name = __name__
_additional_escapes = { _additional_escapes = {
...@@ -41,7 +48,7 @@ class BaseIntegrationTest(SeleniumBaseTest): ...@@ -41,7 +48,7 @@ class BaseIntegrationTest(SeleniumBaseTest):
) )
def _get_custom_scenario_xml(self, filename): def _get_custom_scenario_xml(self, filename):
data = load_resource(filename) data = loader.load_unicode(filename)
return "<vertical_demo><drag-and-drop-v2 data='{data}'/></vertical_demo>".format( return "<vertical_demo><drag-and-drop-v2 data='{data}'/></vertical_demo>".format(
data=escape(data, self._additional_escapes) data=escape(data, self._additional_escapes)
) )
......
...@@ -8,7 +8,7 @@ class TestCustomDataDragAndDropRendering(BaseIntegrationTest): ...@@ -8,7 +8,7 @@ class TestCustomDataDragAndDropRendering(BaseIntegrationTest):
def setUp(self): def setUp(self):
super(TestCustomDataDragAndDropRendering, self).setUp() super(TestCustomDataDragAndDropRendering, self).setUp()
scenario_xml = self._get_custom_scenario_xml("integration/data/test_html_data.json") scenario_xml = self._get_custom_scenario_xml("data/test_html_data.json")
self._add_scenario(self.PAGE_ID, self.PAGE_TITLE, scenario_xml) self._add_scenario(self.PAGE_ID, self.PAGE_TITLE, scenario_xml)
self._page = self.go_to_page(self.PAGE_TITLE) self._page = self.go_to_page(self.PAGE_TITLE)
......
# Imports ###########################################################
from ddt import ddt, data from ddt import ddt, data
from selenium.common.exceptions import NoSuchElementException from selenium.common.exceptions import NoSuchElementException
from selenium.webdriver import ActionChains from selenium.webdriver import ActionChains
from selenium.webdriver.common.keys import Keys from selenium.webdriver.common.keys import Keys
from xblockutils.resources import ResourceLoader
from drag_and_drop_v2.default_data import ( from drag_and_drop_v2.default_data import (
TOP_ZONE_TITLE, MIDDLE_ZONE_TITLE, BOTTOM_ZONE_TITLE, TOP_ZONE_TITLE, MIDDLE_ZONE_TITLE, BOTTOM_ZONE_TITLE,
ITEM_CORRECT_FEEDBACK, ITEM_INCORRECT_FEEDBACK, ITEM_NO_ZONE_FEEDBACK, ITEM_CORRECT_FEEDBACK, ITEM_INCORRECT_FEEDBACK, ITEM_NO_ZONE_FEEDBACK,
START_FEEDBACK, FINISH_FEEDBACK START_FEEDBACK, FINISH_FEEDBACK
) )
from .test_base import BaseIntegrationTest from .test_base import BaseIntegrationTest
from ..utils import load_resource
# Globals ###########################################################
loader = ResourceLoader(__name__)
ZONES_MAP = { ZONES_MAP = {
0: TOP_ZONE_TITLE, 0: TOP_ZONE_TITLE,
1: MIDDLE_ZONE_TITLE, 1: MIDDLE_ZONE_TITLE,
...@@ -20,6 +27,8 @@ ZONES_MAP = { ...@@ -20,6 +27,8 @@ ZONES_MAP = {
} }
# Classes ###########################################################
class ItemDefinition(object): class ItemDefinition(object):
def __init__(self, item_id, zone_id, feedback_positive, feedback_negative, input_value=None): def __init__(self, item_id, zone_id, feedback_positive, feedback_negative, input_value=None):
self.feedback_negative = feedback_negative self.feedback_negative = feedback_negative
...@@ -121,6 +130,7 @@ class InteractionTestBase(object): ...@@ -121,6 +130,7 @@ class InteractionTestBase(object):
def assert_placed_item(self, item_value, zone_id): def assert_placed_item(self, item_value, zone_id):
item = self._get_placed_item_by_value(item_value) item = self._get_placed_item_by_value(item_value)
self.wait_until_visible(item)
item_content = item.find_element_by_css_selector('.item-content') item_content = item.find_element_by_css_selector('.item-content')
item_description = item.find_element_by_css_selector('.sr') item_description = item.find_element_by_css_selector('.sr')
item_description_id = 'item-{}-description'.format(item_value) item_description_id = 'item-{}-description'.format(item_value)
...@@ -134,6 +144,7 @@ class InteractionTestBase(object): ...@@ -134,6 +144,7 @@ class InteractionTestBase(object):
def assert_reverted_item(self, item_value): def assert_reverted_item(self, item_value):
item = self._get_item_by_value(item_value) item = self._get_item_by_value(item_value)
self.wait_until_visible(item)
item_content = item.find_element_by_css_selector('.item-content') item_content = item.find_element_by_css_selector('.item-content')
self.assertEqual(item.get_attribute('class'), 'option ui-draggable') self.assertEqual(item.get_attribute('class'), 'option ui-draggable')
...@@ -377,7 +388,7 @@ class CustomDataInteractionTest(BasicInteractionTest, BaseIntegrationTest): ...@@ -377,7 +388,7 @@ class CustomDataInteractionTest(BasicInteractionTest, BaseIntegrationTest):
} }
def _get_scenario_xml(self): def _get_scenario_xml(self):
return self._get_custom_scenario_xml("integration/data/test_data.json") return self._get_custom_scenario_xml("data/test_data.json")
class CustomHtmlDataInteractionTest(BasicInteractionTest, BaseIntegrationTest): class CustomHtmlDataInteractionTest(BasicInteractionTest, BaseIntegrationTest):
...@@ -395,15 +406,15 @@ class CustomHtmlDataInteractionTest(BasicInteractionTest, BaseIntegrationTest): ...@@ -395,15 +406,15 @@ class CustomHtmlDataInteractionTest(BasicInteractionTest, BaseIntegrationTest):
} }
def _get_scenario_xml(self): def _get_scenario_xml(self):
return self._get_custom_scenario_xml("integration/data/test_html_data.json") return self._get_custom_scenario_xml("data/test_html_data.json")
class MultipleBlocksDataInteraction(InteractionTestBase, BaseIntegrationTest): class MultipleBlocksDataInteraction(InteractionTestBase, BaseIntegrationTest):
PAGE_TITLE = 'Drag and Drop v2 Multiple Blocks' PAGE_TITLE = 'Drag and Drop v2 Multiple Blocks'
PAGE_ID = 'drag_and_drop_v2_multi' PAGE_ID = 'drag_and_drop_v2_multi'
BLOCK1_DATA_FILE = "integration/data/test_data.json" BLOCK1_DATA_FILE = "data/test_data.json"
BLOCK2_DATA_FILE = "integration/data/test_data_other.json" BLOCK2_DATA_FILE = "data/test_data_other.json"
item_maps = { item_maps = {
'block1': { 'block1': {
...@@ -430,7 +441,7 @@ class MultipleBlocksDataInteraction(InteractionTestBase, BaseIntegrationTest): ...@@ -430,7 +441,7 @@ class MultipleBlocksDataInteraction(InteractionTestBase, BaseIntegrationTest):
def _get_scenario_xml(self): def _get_scenario_xml(self):
blocks_xml = "\n".join([ blocks_xml = "\n".join([
"<drag-and-drop-v2 data='{data}'/>".format(data=load_resource(filename)) "<drag-and-drop-v2 data='{data}'/>".format(data=loader.load_unicode(filename))
for filename in (self.BLOCK1_DATA_FILE, self.BLOCK2_DATA_FILE) for filename in (self.BLOCK1_DATA_FILE, self.BLOCK2_DATA_FILE)
]) ])
......
# Imports ###########################################################
from ddt import ddt, unpack, data from ddt import ddt, unpack, data
from selenium.common.exceptions import NoSuchElementException from selenium.common.exceptions import NoSuchElementException
from xblockutils.resources import ResourceLoader
from drag_and_drop_v2.default_data import START_FEEDBACK from drag_and_drop_v2.default_data import START_FEEDBACK
from ..utils import load_resource
from .test_base import BaseIntegrationTest from .test_base import BaseIntegrationTest
# Globals ###########################################################
loader = ResourceLoader(__name__)
# Classes ###########################################################
class Colors(object): class Colors(object):
WHITE = 'rgb(255, 255, 255)' WHITE = 'rgb(255, 255, 255)'
BLUE = 'rgb(29, 82, 128)' BLUE = 'rgb(29, 82, 128)'
...@@ -36,7 +46,7 @@ class TestDragAndDropRender(BaseIntegrationTest): ...@@ -36,7 +46,7 @@ class TestDragAndDropRender(BaseIntegrationTest):
SIDES = ['Top', 'Bottom', 'Left', 'Right'] SIDES = ['Top', 'Bottom', 'Left', 'Right']
def load_scenario(self, item_background_color="", item_text_color="", zone_labels=False, zone_borders=False): def load_scenario(self, item_background_color="", item_text_color="", zone_labels=False, zone_borders=False):
exercise_data = load_resource("integration/data/test_data_a11y.json") exercise_data = loader.load_unicode("data/test_data_a11y.json")
exercise_data = exercise_data.replace('{display_labels_value}', 'true' if zone_labels else 'false') exercise_data = exercise_data.replace('{display_labels_value}', 'true' if zone_labels else 'false')
exercise_data = exercise_data.replace('{display_borders_value}', 'true' if zone_borders else 'false') exercise_data = exercise_data.replace('{display_borders_value}', 'true' if zone_borders else 'false')
scenario_xml = """ scenario_xml = """
......
# Imports ###########################################################
import json import json
import unittest import unittest
from ..utils import ( from xblockutils.resources import ResourceLoader
make_block,
load_resource, from ..utils import make_block, TestCaseMixin
TestCaseMixin,
)
# Globals ###########################################################
loader = ResourceLoader(__name__)
# Classes ###########################################################
class BaseDragAndDropAjaxFixture(TestCaseMixin): class BaseDragAndDropAjaxFixture(TestCaseMixin):
ZONE_1 = None ZONE_1 = None
...@@ -32,15 +39,15 @@ class BaseDragAndDropAjaxFixture(TestCaseMixin): ...@@ -32,15 +39,15 @@ class BaseDragAndDropAjaxFixture(TestCaseMixin):
@classmethod @classmethod
def initial_data(cls): def initial_data(cls):
return json.loads(load_resource('unit/data/{}/data.json'.format(cls.FOLDER))) return json.loads(loader.load_unicode('data/{}/data.json'.format(cls.FOLDER)))
@classmethod @classmethod
def initial_settings(cls): def initial_settings(cls):
return json.loads(load_resource('unit/data/{}/settings.json'.format(cls.FOLDER))) return json.loads(loader.load_unicode('data/{}/settings.json'.format(cls.FOLDER)))
@classmethod @classmethod
def expected_configuration(cls): def expected_configuration(cls):
return json.loads(load_resource('unit/data/{}/config_out.json'.format(cls.FOLDER))) return json.loads(loader.load_unicode('data/{}/config_out.json'.format(cls.FOLDER)))
@classmethod @classmethod
def initial_feedback(cls): def initial_feedback(cls):
......
...@@ -17,7 +17,7 @@ class BasicTests(TestCaseMixin, unittest.TestCase): ...@@ -17,7 +17,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="xblock--drag-and-drop">', student_fragment.content) self.assertIn('<section class="themed-xblock xblock--drag-and-drop">', student_fragment.content)
self.assertIn('Loading drag and drop exercise.', student_fragment.content) self.assertIn('Loading drag and drop exercise.', student_fragment.content)
def test_get_configuration(self): def test_get_configuration(self):
......
import json import json
import pkg_resources
import re import re
from mock import patch from mock import patch
...@@ -32,14 +31,6 @@ def make_block(): ...@@ -32,14 +31,6 @@ def make_block():
return drag_and_drop_v2.DragAndDropBlock(runtime, field_data, scope_ids=scope_ids) return drag_and_drop_v2.DragAndDropBlock(runtime, field_data, scope_ids=scope_ids)
def load_resource(resource_path):
"""
Gets the content of a resource
"""
resource_content = pkg_resources.resource_string(__name__, resource_path)
return unicode(resource_content)
class TestCaseMixin(object): class TestCaseMixin(object):
""" Helpful mixins for unittest TestCase subclasses """ """ Helpful mixins for unittest TestCase subclasses """
maxDiff = None maxDiff = None
......
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