Commit d1e4d124 by Matjaz Gregoric

Simplify test infrastructure & update tests.

Also includes Travis CI integration.
parent c7fd6641
language: python
python:
- "2.7"
before_install:
- "export DISPLAY=:99"
- "sh -e /etc/init.d/xvfb start"
install:
- "sh install_test_deps.sh"
- "pip uninstall -y xblock-drag-and-drop-v2"
- "python setup.py sdist"
- "pip install dist/xblock-drag-and-drop-v2-0.1.tar.gz"
script: pep8 drag_and_drop_v2 --max-line-length=120 && python run_tests.py
notifications:
email: false
\ No newline at end of file
...@@ -93,22 +93,15 @@ You can define an arbitrary number of drag items. ...@@ -93,22 +93,15 @@ You can define an arbitrary number of drag items.
Testing Testing
------- -------
In a virtualenv, run Inside a fresh virtualenv, run
```bash ```bash
$ cd .../xblock-drag-and-drop-v2/ $ cd .../xblock-drag-and-drop-v2/
$ pip install -r tests/requirements.txt $ sh install_test_deps.sh
``` ```
To run the tests, from the xblock-drag-and-drop-v2 repository root: To run the tests, from the xblock-drag-and-drop-v2 repository root:
```bash ```bash
$ tests/manage.py test --rednose $ python run_tests.py
```
To include coverage report (although selenium tends to crash with
segmentation faults when collection test coverage):
```bash
$ tests/manage.py test --rednose --with-cover --cover-package=drag_and_drop_v2
``` ```
# Installs xblock-sdk and dependencies needed to run the tests suite.
# Run this script inside a fresh virtual environment.
pip install -e git://github.com/edx/xblock-sdk.git#egg=xblock-sdk
pip install -r $VIRTUAL_ENV/src/xblock-sdk/requirements.txt
pip install -r $VIRTUAL_ENV/src/xblock-sdk/test-requirements.txt
python setup.py develop
[REPORTS]
reports=no
[FORMAT]
max-line-length=120
[MESSAGES CONTROL]
disable=
locally-disabled,
missing-docstring,
too-many-ancestors,
too-many-public-methods,
unused-argument
[SIMILARITIES]
min-similarity-lines=8
#!/usr/bin/env python
"""
Run tests for the Drag and Drop V2 XBlock.
This script is required to run our selenium tests inside the xblock-sdk workbench
because the workbench SDK's settings file is not inside any python module.
"""
import os
import sys
import workbench
if __name__ == "__main__":
# Find the location of the XBlock SDK. Note: it must be installed in development mode.
# ('python setup.py develop' or 'pip install -e')
xblock_sdk_dir = os.path.dirname(os.path.dirname(workbench.__file__))
sys.path.append(xblock_sdk_dir)
# Use the workbench settings file:
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "settings")
# Configure a range of ports in case the default port of 8081 is in use
os.environ.setdefault("DJANGO_LIVE_TEST_SERVER_ADDRESS", "localhost:8081-8099")
from django.core.management import execute_from_command_line
args = sys.argv[1:]
paths = [arg for arg in args if arg[0] != '-']
if not paths:
paths = ["tests/"]
options = [arg for arg in args if arg not in paths]
execute_from_command_line([sys.argv[0], "test"] + paths + options)
...@@ -28,7 +28,10 @@ setup( ...@@ -28,7 +28,10 @@ setup(
packages=['drag_and_drop_v2'], packages=['drag_and_drop_v2'],
install_requires=[ install_requires=[
'XBlock', 'XBlock',
'xblock-utils',
'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',
}, },
......
...@@ -89,5 +89,7 @@ ...@@ -89,5 +89,7 @@
"start": "Intro Feed", "start": "Intro Feed",
"finish": "Final Feed" "finish": "Final Feed"
}, },
"targetImg": "http://i0.kym-cdn.com/photos/images/newsfeed/000/030/404/1260585284155.png" "targetImg": "http://i0.kym-cdn.com/photos/images/newsfeed/000/030/404/1260585284155.png",
"title": "Drag and Drop",
"question_text": ""
} }
...@@ -68,5 +68,7 @@ ...@@ -68,5 +68,7 @@
"feedback": { "feedback": {
"start": "Intro Feed" "start": "Intro Feed"
}, },
"targetImg": "http://i0.kym-cdn.com/photos/images/newsfeed/000/030/404/1260585284155.png" "targetImg": "http://i0.kym-cdn.com/photos/images/newsfeed/000/030/404/1260585284155.png",
"title": "Drag and Drop",
"question_text": ""
} }
...@@ -68,5 +68,7 @@ ...@@ -68,5 +68,7 @@
"feedback": { "feedback": {
"start": "Intro Feed" "start": "Intro Feed"
}, },
"targetImg": "http://i0.kym-cdn.com/photos/images/newsfeed/000/030/404/1260585284155.png" "targetImg": "http://i0.kym-cdn.com/photos/images/newsfeed/000/030/404/1260585284155.png",
"title": "Drag and Drop",
"question_text": ""
} }
...@@ -89,5 +89,7 @@ ...@@ -89,5 +89,7 @@
"start": "Intro Feed", "start": "Intro Feed",
"finish": "Final <b>Feed</b>" "finish": "Final <b>Feed</b>"
}, },
"targetImg": "http://i0.kym-cdn.com/photos/images/newsfeed/000/030/404/1260585284155.png" "targetImg": "http://i0.kym-cdn.com/photos/images/newsfeed/000/030/404/1260585284155.png",
"title": "Drag and Drop",
"question_text": ""
} }
...@@ -4,32 +4,21 @@ from selenium.webdriver.support.ui import WebDriverWait ...@@ -4,32 +4,21 @@ from selenium.webdriver.support.ui import WebDriverWait
from tests.utils import load_resource from tests.utils import load_resource
from workbench import scenarios from workbench import scenarios
from workbench.test.selenium_test import SeleniumTest
from xblockutils.base_test import SeleniumBaseTest
# Classes ########################################################### # Classes ###########################################################
class BaseIntegrationTest(SeleniumTest): class BaseIntegrationTest(SeleniumBaseTest):
default_css_selector = 'section.xblock--drag-and-drop'
module_name = __name__
_additional_escapes = { _additional_escapes = {
'"': "&quot;", '"': "&quot;",
"'": "&apos;" "'": "&apos;"
} }
def setUp(self):
super(BaseIntegrationTest, self).setUp()
# Use test scenarios
self.browser.get(self.live_server_url) # Needed to load tests once
scenarios.SCENARIOS.clear()
# Suzy opens the browser to visit the workbench
self.browser.get(self.live_server_url)
# She knows it's the site by the header
header1 = self.browser.find_element_by_css_selector('h1')
self.assertEqual(header1.text, 'XBlock scenarios')
def _make_scenario_xml(self, display_name, question_text, completed): def _make_scenario_xml(self, display_name, question_text, completed):
return """ return """
<vertical_demo> <vertical_demo>
...@@ -48,8 +37,8 @@ class BaseIntegrationTest(SeleniumTest): ...@@ -48,8 +37,8 @@ class BaseIntegrationTest(SeleniumTest):
self.addCleanup(scenarios.remove_scenario, identifier) self.addCleanup(scenarios.remove_scenario, identifier)
def _get_items(self): def _get_items(self):
items_container = self._page.find_element_by_css_selector('ul.items') items_container = self._page.find_element_by_css_selector('.items')
return items_container.find_elements_by_css_selector('li.option') return items_container.find_elements_by_css_selector('.option')
def _get_zones(self): def _get_zones(self):
return self._page.find_elements_by_css_selector(".drag-container .zone") return self._page.find_elements_by_css_selector(".drag-container .zone")
...@@ -57,25 +46,13 @@ class BaseIntegrationTest(SeleniumTest): ...@@ -57,25 +46,13 @@ class BaseIntegrationTest(SeleniumTest):
def _get_feedback_message(self): def _get_feedback_message(self):
return self._page.find_element_by_css_selector(".feedback .message") return self._page.find_element_by_css_selector(".feedback .message")
def go_to_page(self, page_name, css_selector='section.xblock--drag-and-drop'):
"""
Navigate to the page `page_name`, as listed on the workbench home
Returns the DOM element on the visited page located by the `css_selector`
"""
self.browser.get(self.live_server_url)
self.browser.find_element_by_link_text(page_name).click()
return self.browser.find_element_by_css_selector(css_selector)
def get_element_html(self, element): def get_element_html(self, element):
return element.get_attribute('innerHTML').strip() return element.get_attribute('innerHTML').strip()
def get_element_classes(self, element): def get_element_classes(self, element):
return element.get_attribute('class').split() return element.get_attribute('class').split()
def scroll_to(self, y): def wait_until_html_in(self, html, elem):
self.browser.execute_script('window.scrollTo(0, {0})'.format(y))
def wait_until_contains_html(self, html, elem):
wait = WebDriverWait(elem, 2) wait = WebDriverWait(elem, 2)
wait.until(lambda e: html in e.get_attribute('innerHTML'), wait.until(lambda e: html in e.get_attribute('innerHTML'),
u"{} should be in {}".format(html, elem.get_attribute('innerHTML'))) u"{} should be in {}".format(html, elem.get_attribute('innerHTML')))
...@@ -84,4 +61,3 @@ class BaseIntegrationTest(SeleniumTest): ...@@ -84,4 +61,3 @@ class BaseIntegrationTest(SeleniumTest):
wait = WebDriverWait(elem, 2) wait = WebDriverWait(elem, 2)
wait.until(lambda e: class_name in e.get_attribute('class').split(), wait.until(lambda e: class_name in e.get_attribute('class').split(),
u"Class name {} not in {}".format(class_name, elem.get_attribute('class'))) u"Class name {} not in {}".format(class_name, elem.get_attribute('class')))
...@@ -48,15 +48,17 @@ class InteractionTestFixture(BaseIntegrationTest): ...@@ -48,15 +48,17 @@ class InteractionTestFixture(BaseIntegrationTest):
scenario_xml = self._get_scenario_xml() scenario_xml = self._get_scenario_xml()
self._add_scenario(self.PAGE_ID, self.PAGE_TITLE, scenario_xml) self._add_scenario(self.PAGE_ID, self.PAGE_TITLE, scenario_xml)
self.browser.get(self.live_server_url)
self._page = self.go_to_page(self.PAGE_TITLE) self._page = self.go_to_page(self.PAGE_TITLE)
# Resize window so that the entire drag container is visible.
# Selenium has issues when dragging to an area that is off screen.
self.browser.set_window_size(1024, 800)
def _get_item_by_value(self, item_value): def _get_item_by_value(self, item_value):
items_container = self._page.find_element_by_css_selector('ul.items') items_container = self._page.find_element_by_css_selector('.items')
return items_container.find_elements_by_xpath("//li[@data-value='{item_id}']".format(item_id=item_value))[0] return items_container.find_elements_by_xpath("//div[@data-value='{item_id}']".format(item_id=item_value))[0]
def _get_zone_by_id(self, zone_id): def _get_zone_by_id(self, zone_id):
zones_container = self._page.find_element_by_css_selector('div.target') zones_container = self._page.find_element_by_css_selector('.target')
return zones_container.find_elements_by_xpath("//div[@data-zone='{zone_id}']".format(zone_id=zone_id))[0] return zones_container.find_elements_by_xpath("//div[@data-zone='{zone_id}']".format(zone_id=zone_id))[0]
def _get_input_div_by_value(self, item_value): def _get_input_div_by_value(self, item_value):
...@@ -68,7 +70,6 @@ class InteractionTestFixture(BaseIntegrationTest): ...@@ -68,7 +70,6 @@ class InteractionTestFixture(BaseIntegrationTest):
element.find_element_by_class_name('input').send_keys(value) element.find_element_by_class_name('input').send_keys(value)
element.find_element_by_class_name('submit-input').click() element.find_element_by_class_name('submit-input').click()
def drag_item_to_zone(self, item_value, zone_id): def drag_item_to_zone(self, item_value, zone_id):
element = self._get_item_by_value(item_value) element = self._get_item_by_value(item_value)
target = self._get_zone_by_id(zone_id) target = self._get_zone_by_id(zone_id)
...@@ -76,11 +77,11 @@ class InteractionTestFixture(BaseIntegrationTest): ...@@ -76,11 +77,11 @@ class InteractionTestFixture(BaseIntegrationTest):
action_chains.drag_and_drop(element, target).perform() action_chains.drag_and_drop(element, target).perform()
def test_item_positive_feedback_on_good_move(self): def test_item_positive_feedback_on_good_move(self):
feedback_popup = self._page.find_element_by_css_selector(".popup-content")
for definition in self._get_correct_item_for_zone().values(): for definition in self._get_correct_item_for_zone().values():
if not definition.input: if not definition.input:
self.drag_item_to_zone(definition.item_id, definition.zone_id) self.drag_item_to_zone(definition.item_id, definition.zone_id)
self.wait_until_contains_html(definition.feedback_positive, feedback_popup) feedback_popup = self._page.find_element_by_css_selector(".popup-content")
self.wait_until_html_in(definition.feedback_positive, feedback_popup)
def test_item_positive_feedback_on_good_input(self): def test_item_positive_feedback_on_good_input(self):
feedback_popup = self._page.find_element_by_css_selector(".popup-content") feedback_popup = self._page.find_element_by_css_selector(".popup-content")
...@@ -90,7 +91,7 @@ class InteractionTestFixture(BaseIntegrationTest): ...@@ -90,7 +91,7 @@ class InteractionTestFixture(BaseIntegrationTest):
self._send_input(definition.item_id, definition.input) self._send_input(definition.item_id, definition.input)
input_div = self._get_input_div_by_value(definition.item_id) input_div = self._get_input_div_by_value(definition.item_id)
self.wait_until_has_class('correct', input_div) self.wait_until_has_class('correct', input_div)
self.wait_until_contains_html(definition.feedback_positive, feedback_popup) self.wait_until_html_in(definition.feedback_positive, feedback_popup)
def test_item_negative_feedback_on_bad_move(self): def test_item_negative_feedback_on_bad_move(self):
feedback_popup = self._page.find_element_by_css_selector(".popup-content") feedback_popup = self._page.find_element_by_css_selector(".popup-content")
...@@ -100,7 +101,7 @@ class InteractionTestFixture(BaseIntegrationTest): ...@@ -100,7 +101,7 @@ class InteractionTestFixture(BaseIntegrationTest):
if zone == definition.zone_id: if zone == definition.zone_id:
continue continue
self.drag_item_to_zone(definition.item_id, zone) self.drag_item_to_zone(definition.item_id, zone)
self.wait_until_contains_html(definition.feedback_negative, feedback_popup) self.wait_until_html_in(definition.feedback_negative, feedback_popup)
def test_item_positive_feedback_on_bad_input(self): def test_item_positive_feedback_on_bad_input(self):
feedback_popup = self._page.find_element_by_css_selector(".popup-content") feedback_popup = self._page.find_element_by_css_selector(".popup-content")
...@@ -110,7 +111,7 @@ class InteractionTestFixture(BaseIntegrationTest): ...@@ -110,7 +111,7 @@ class InteractionTestFixture(BaseIntegrationTest):
self._send_input(definition.item_id, '1999999') self._send_input(definition.item_id, '1999999')
input_div = self._get_input_div_by_value(definition.item_id) input_div = self._get_input_div_by_value(definition.item_id)
self.wait_until_has_class('incorrect', input_div) self.wait_until_has_class('incorrect', input_div)
self.wait_until_contains_html(definition.feedback_negative, feedback_popup) self.wait_until_html_in(definition.feedback_negative, feedback_popup)
def test_final_feedback_and_reset(self): def test_final_feedback_and_reset(self):
feedback_message = self._get_feedback_message() feedback_message = self._get_feedback_message()
...@@ -128,16 +129,13 @@ class InteractionTestFixture(BaseIntegrationTest): ...@@ -128,16 +129,13 @@ class InteractionTestFixture(BaseIntegrationTest):
input_div = self._get_input_div_by_value(item_key) input_div = self._get_input_div_by_value(item_key)
self.wait_until_has_class('correct', input_div) self.wait_until_has_class('correct', input_div)
self.wait_until_contains_html(self.feedback['final'], feedback_message) self.wait_until_exists('.reset-button')
self.wait_until_html_in(self.feedback['final'], self._get_feedback_message())
# scrolling to have `reset` visible, otherwise it does not receive a click reset = self._page.find_element_by_css_selector('.reset-button')
# this is due to xblock workbench header that consumes top 40px - selenium scrolls so page so that target
# element is a the very top.
self.scroll_to(100)
reset = self._page.find_element_by_css_selector(".reset-button")
reset.click() reset.click()
self.wait_until_contains_html(self.feedback['intro'], feedback_message) self.wait_until_html_in(self.feedback['intro'], self._get_feedback_message())
locations_after_reset = get_locations() locations_after_reset = get_locations()
for item_key in items.keys(): for item_key in items.keys():
......
from nose_parameterized import parameterized from ddt import ddt, unpack, data
from tests.integration.test_base import BaseIntegrationTest from tests.integration.test_base import BaseIntegrationTest
from workbench import scenarios from workbench import scenarios
@ddt
class TestDragAndDropTitleAndQuestion(BaseIntegrationTest): class TestDragAndDropTitleAndQuestion(BaseIntegrationTest):
@parameterized.expand([ @unpack
@data(
('plain1', 'title1', 'question1'), ('plain1', 'title1', 'question1'),
('plain2', 'title2', 'question2'), ('plain2', 'title2', 'question2'),
('html1', 'title with <i>HTML</i>', 'Question with <i>HTML</i>'), ('html1', 'title with <i>HTML</i>', 'Question with <i>HTML</i>'),
('html2', '<span style="color:red">Title: HTML?</span>', '<span style="color:red">Span question</span>'), ('html2', '<span style="color:red">Title: HTML?</span>', '<span style="color:red">Span question</span>'),
]) )
def test_title_and_question_parameters(self, _, display_name, question_text): def test_title_and_question_parameters(self, _, display_name, question_text):
const_page_name = 'Test block parameters' const_page_name = 'Test block parameters'
const_page_id = 'test_block_title' const_page_id = 'test_block_title'
......
#!/usr/bin/env python
"""Manage.py file for xblock-drag-and-drop-v2"""
import os
import sys
if __name__ == "__main__":
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "settings")
from django.core.management import execute_from_command_line
execute_from_command_line(sys.argv)
bok_choy==0.3.2
cookiecutter==0.7.1
coverage==3.7.1
diff-cover==0.7.2
Django==1.4.16
django_nose==1.2
fs==0.5.0
lazy==1.2
lxml==3.4.1
mock==1.0.1
nose==1.3.4
nose-parameterized==0.3.5
pep8==1.5.7
pylint==0.28
pypng==0.0.17
rednose==0.4.1
requests==2.4.3
selenium==2.44.0
simplejson==3.6.5
webob==1.4
-e git+https://github.com/open-craft/XBlock.git@3ece535ee8e095f21de2f8b28cc720145749f0d6#egg=XBlock
-e git+https://github.com/edx/acid-block.git@459aff7b63db8f2c5decd1755706c1a64fb4ebb1#egg=acid-xblock
-e git+https://github.com/pmitros/django-pyfs.git@514607d78535fd80bfd23184cd292ee5799b500d#egg=djpyfs
-e git+https://github.com/open-craft/xblock-sdk.git@39b00c58931664ce29bb4b38df827fe237a69613#egg=xblock-sdk
-e .
DEBUG = True
INSTALLED_APPS = (
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'workbench',
'sample_xblocks.basic',
'django_nose',
)
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': 'drag_and_drop_v2.db'
}
}
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.locmem.LocMemCache'
}
}
TEMPLATE_LOADERS = (
'django.template.loaders.filesystem.Loader',
'django.template.loaders.eggs.Loader',
)
ROOT_URLCONF = 'urls'
TEST_RUNNER = 'django_nose.NoseTestSuiteRunner'
STATIC_ROOT = ''
STATIC_URL = '/static/'
WORKBENCH = {'reset_state_on_restart': False}
...@@ -7,6 +7,7 @@ from webob import Request ...@@ -7,6 +7,7 @@ from webob import Request
from mock import Mock from mock import Mock
from workbench.runtime import WorkbenchRuntime from workbench.runtime import WorkbenchRuntime
from xblock.fields import ScopeIds
from xblock.runtime import KvsFieldData, DictKeyValueStore from xblock.runtime import KvsFieldData, DictKeyValueStore
from nose.tools import ( from nose.tools import (
...@@ -33,10 +34,14 @@ def make_request(body, method='POST'): ...@@ -33,10 +34,14 @@ def make_request(body, method='POST'):
def make_block(): def make_block():
runtime = WorkbenchRuntime() block_type = 'drag_and_drop_v2'
key_store = DictKeyValueStore() key_store = DictKeyValueStore()
db_model = KvsFieldData(key_store) field_data = KvsFieldData(key_store)
return drag_and_drop_v2.DragAndDropBlock(runtime, db_model, Mock()) runtime = WorkbenchRuntime()
def_id = runtime.id_generator.create_definition(block_type)
usage_id = runtime.id_generator.create_usage(def_id)
scope_ids = ScopeIds('user', block_type, def_id, usage_id)
return drag_and_drop_v2.DragAndDropBlock(runtime, field_data, scope_ids=scope_ids)
def test_templates_contents(): def test_templates_contents():
...@@ -46,17 +51,9 @@ def test_templates_contents(): ...@@ -46,17 +51,9 @@ def test_templates_contents():
block.question_text = "Question Drag & Drop" block.question_text = "Question Drag & Drop"
block.weight = 5 block.weight = 5
student_fragment = block.render('student_view', Mock()) student_fragment = block.runtime.render(block, 'student_view', ['ingore'])# block.render('student_view', Mock())
assert_in('<section class="xblock--drag-and-drop">', assert_in('<section class="xblock--drag-and-drop">',
student_fragment.content) student_fragment.content)
assert_in('{{ value }}', student_fragment.content)
assert_in("Test Drag & Drop", student_fragment.content)
assert_in("Question Drag & Drop", student_fragment.content)
studio_fragment = block.render('studio_view', Mock())
assert_in('<div class="xblock--drag-and-drop editor-with-buttons">',
studio_fragment.content)
assert_in('{{ value }}', studio_fragment.content)
def test_studio_submit(): def test_studio_submit():
block = make_block() block = make_block()
...@@ -176,7 +173,7 @@ class BaseDragAndDropAjaxFixture(object): ...@@ -176,7 +173,7 @@ class BaseDragAndDropAjaxFixture(object):
expected = self.get_data_response() expected = self.get_data_response()
expected["state"] = { expected["state"] = {
"items": { "items": {
"1": {"top": "22px", "left": "222px", "correct_input": False} "1": {"top": "22px", "left": "222px", "absolute": True, "correct_input": False}
}, },
"finished": False "finished": False
} }
...@@ -196,7 +193,8 @@ class BaseDragAndDropAjaxFixture(object): ...@@ -196,7 +193,8 @@ class BaseDragAndDropAjaxFixture(object):
expected = self.get_data_response() expected = self.get_data_response()
expected["state"] = { expected["state"] = {
"items": { "items": {
"1": {"top": "22px", "left": "222px", "input": "250", "correct_input": False} "1": {"top": "22px", "left": "222px", "absolute": True,
"input": "250", "correct_input": False}
}, },
"finished": False "finished": False
} }
...@@ -216,7 +214,8 @@ class BaseDragAndDropAjaxFixture(object): ...@@ -216,7 +214,8 @@ class BaseDragAndDropAjaxFixture(object):
expected = self.get_data_response() expected = self.get_data_response()
expected["state"] = { expected["state"] = {
"items": { "items": {
"1": {"top": "22px", "left": "222px", "input": "103", "correct_input": True} "1": {"top": "22px", "left": "222px", "absolute": True,
"input": "103", "correct_input": True}
}, },
"finished": False "finished": False
} }
...@@ -255,7 +254,8 @@ class BaseDragAndDropAjaxFixture(object): ...@@ -255,7 +254,8 @@ class BaseDragAndDropAjaxFixture(object):
expected = self.get_data_response() expected = self.get_data_response()
expected["state"] = { expected["state"] = {
"items": { "items": {
"0": {"top": "11px", "left": "111px", "correct_input": True} "0": {"top": "11px", "left": "111px", "absolute": True,
"correct_input": True}
}, },
"finished": False "finished": False
} }
...@@ -277,8 +277,9 @@ class BaseDragAndDropAjaxFixture(object): ...@@ -277,8 +277,9 @@ class BaseDragAndDropAjaxFixture(object):
expected = self.get_data_response() expected = self.get_data_response()
expected["state"] = { expected["state"] = {
"items": { "items": {
"0": {"top": "11px", "left": "111px", "correct_input": True}, "0": {"top": "11px", "left": "111px", "absolute": True, "correct_input": True},
"1": {"top": "22px", "left": "222px", "input": "99", "correct_input": True} "1": {"top": "22px", "left": "222px", "absolute": True, "input": "99",
"correct_input": True}
}, },
"finished": True "finished": True
} }
...@@ -336,8 +337,8 @@ def test_ajax_solve_and_reset(): ...@@ -336,8 +337,8 @@ def test_ajax_solve_and_reset():
block.handle('do_attempt', make_request(data)) block.handle('do_attempt', make_request(data))
assert_true(block.completed) assert_true(block.completed)
assert_equals(block.item_state, {'0': {"top": "11px", "left": "111px"}, assert_equals(block.item_state, {'0': {"top": "11px", "left": "111px", "absolute": True},
'1': {"top": "22px", "left": "222px"}}) '1': {"top": "22px", "left": "222px", "absolute": True}})
block.handle('reset', make_request("{}")) block.handle('reset', make_request("{}"))
......
from django.conf.urls import include, url
urlpatterns = [
url(r'^', include('workbench.urls')),
]
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