Commit 362da63d by Braden MacDonald

Fix unit tests

parent c08357d0
...@@ -10,7 +10,7 @@ install: ...@@ -10,7 +10,7 @@ install:
- "python setup.py sdist" - "python setup.py sdist"
- "pip install dist/xblock-drag-and-drop-v2-0.1.tar.gz" - "pip install dist/xblock-drag-and-drop-v2-0.1.tar.gz"
script: script:
- pep8 drag_and_drop_v2 --max-line-length=120 - pep8 drag_and_drop_v2 tests --max-line-length=120
- python run_tests.py - python run_tests.py
notifications: notifications:
email: false email: false
......
...@@ -4,21 +4,21 @@ DEFAULT_DATA = { ...@@ -4,21 +4,21 @@ DEFAULT_DATA = {
"zones": [ "zones": [
{ {
"index": 1, "index": 1,
"width": 200, "id": "zone-1",
"title": _("Zone 1"), "title": _("Zone 1"),
"height": 100, "x": 160,
"x": "120", "y": 30,
"y": "200", "width": 196,
"id": "zone-1" "height": 178,
}, },
{ {
"index": 2, "index": 2,
"width": 200, "id": "zone-2",
"title": _("Zone 2"), "title": _("Zone 2"),
"height": 100, "x": 86,
"x": "120", "y": 210,
"y": "360", "width": 340,
"id": "zone-2" "height": 140,
} }
], ],
"items": [ "items": [
...@@ -31,10 +31,6 @@ DEFAULT_DATA = { ...@@ -31,10 +31,6 @@ DEFAULT_DATA = {
"zone": "Zone 1", "zone": "Zone 1",
"backgroundImage": "", "backgroundImage": "",
"id": 0, "id": 0,
"size": {
"width": "190px",
"height": "auto"
}
}, },
{ {
"displayName": "2", "displayName": "2",
...@@ -45,10 +41,6 @@ DEFAULT_DATA = { ...@@ -45,10 +41,6 @@ DEFAULT_DATA = {
"zone": "Zone 2", "zone": "Zone 2",
"backgroundImage": "", "backgroundImage": "",
"id": 1, "id": 1,
"size": {
"width": "190px",
"height": "auto"
}
}, },
{ {
"displayName": "X", "displayName": "X",
...@@ -59,14 +51,10 @@ DEFAULT_DATA = { ...@@ -59,14 +51,10 @@ DEFAULT_DATA = {
"zone": "none", "zone": "none",
"backgroundImage": "", "backgroundImage": "",
"id": 2, "id": 2,
"size": {
"width": "100px",
"height": "100px"
}
}, },
], ],
"feedback": { "feedback": {
"start": _("Intro Feed"), "start": _("Drag the items onto the image above."),
"finish": _("Final Feed") "finish": _("Good work! You have completed this drag and drop exercise.")
}, },
} }
...@@ -350,7 +350,7 @@ class DragAndDropBlock(XBlock): ...@@ -350,7 +350,7 @@ class DragAndDropBlock(XBlock):
return { return {
'items': item_state, 'items': item_state,
'finished': is_finished, 'finished': is_finished,
'overall_feedback': self.data['feedback']['finished' if is_finished else 'start'], 'overall_feedback': self.data['feedback']['finish' if is_finished else 'start'],
} }
def _get_item_state(self): def _get_item_state(self):
......
...@@ -302,8 +302,6 @@ function DragAndDropBlock(runtime, element, configuration) { ...@@ -302,8 +302,6 @@ function DragAndDropBlock(runtime, element, configuration) {
var itemProperties = { var itemProperties = {
value: item.id, value: item.id,
drag_disabled: Boolean(item_user_state || state.finished), drag_disabled: Boolean(item_user_state || state.finished),
width: item.size.width,
height: item.size.height,
class_name: item_user_state && ('input' in item_user_state || item_user_state.correct_input) ? 'fade': undefined, class_name: item_user_state && ('input' in item_user_state || item_user_state.correct_input) ? 'fade': undefined,
input: input, input: input,
content_html: item.backgroundImage ? '<img src="' + item.backgroundImage + '"/>' : item.displayName content_html: item.backgroundImage ? '<img src="' + item.backgroundImage + '"/>' : item.displayName
......
...@@ -6,6 +6,7 @@ This script is required to run our selenium tests inside the xblock-sdk workbenc ...@@ -6,6 +6,7 @@ This script is required to run our selenium tests inside the xblock-sdk workbenc
because the workbench SDK's settings file is not inside any python module. because the workbench SDK's settings file is not inside any python module.
""" """
import logging
import os import os
import sys import sys
import workbench import workbench
...@@ -21,6 +22,9 @@ if __name__ == "__main__": ...@@ -21,6 +22,9 @@ if __name__ == "__main__":
# Configure a range of ports in case the default port of 8081 is in use # 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") os.environ.setdefault("DJANGO_LIVE_TEST_SERVER_ADDRESS", "localhost:8081-8099")
# Silence too verbose Django logging
logging.disable(logging.DEBUG)
try: try:
os.mkdir('var') os.mkdir('var')
except OSError: except OSError:
......
{ {
"title": "DnDv2 XBlock with HTML instructions",
"show_title": false,
"question_text": "Solve this <strong>drag-and-drop</strong> problem.",
"show_question_header": false,
"target_img_expanded_url": "/expanded/url/to/drag_and_drop_v2/public/img/triangle.png",
"item_background_color": "white",
"item_text_color": "#000080",
"initial_feedback": "HTML <strong>Intro</strong> Feed",
"display_zone_labels": false,
"zones": [ "zones": [
{ {
"index": 1, "index": 1,
"width": 200,
"title": "Zone <i>1</i>", "title": "Zone <i>1</i>",
"x": 100,
"y": 200,
"width": 200,
"height": 100, "height": 100,
"y": "200",
"x": "100",
"id": "zone-1" "id": "zone-1"
}, },
{ {
"index": 2, "index": 2,
"width": 200,
"title": "Zone <b>2</b>", "title": "Zone <b>2</b>",
"height": 100,
"y": 0,
"x": 0, "x": 0,
"y": 0,
"width": 200,
"height": 100,
"id": "zone-2" "id": "zone-2"
} }
], ],
"items": [ "items": [
{ {
"displayName": "<b>1</b>", "displayName": "<b>1</b>",
"backgroundImage": "", "backgroundImage": "",
"id": 0, "id": 0,
"size": {
"width": "190px",
"height": "auto"
},
"inputOptions": false "inputOptions": false
}, },
{ {
"displayName": "<i>2</i>", "displayName": "<i>2</i>",
"backgroundImage": "", "backgroundImage": "",
"id": 1, "id": 1,
"size": {
"width": "190px",
"height": "auto"
},
"inputOptions": true "inputOptions": true
}, },
{ {
"displayName": "X", "displayName": "X",
"backgroundImage": "", "backgroundImage": "",
"id": 2, "id": 2,
"size": {
"width": "100px",
"height": "100px"
},
"inputOptions": false "inputOptions": false
}, },
{ {
"displayName": "", "displayName": "",
"backgroundImage": "http://i1.kym-cdn.com/entries/icons/square/000/006/151/tumblr_lltzgnHi5F1qzib3wo1_400.jpg", "backgroundImage": "http://placehold.it/100x300",
"id": 3, "id": 3,
"size": {
"width": "190px",
"height": "auto"
},
"inputOptions": false "inputOptions": false
} }
], ]
"state": {
"items": {},
"finished": false
},
"feedback": {
"start": "Intro Feed"
},
"targetImg": "http://i0.kym-cdn.com/photos/images/newsfeed/000/030/404/1260585284155.png",
"title": "Drag and Drop",
"show_title": true,
"question_text": "",
"show_question_header": true
} }
...@@ -5,8 +5,8 @@ ...@@ -5,8 +5,8 @@
"width": 200, "width": 200,
"title": "Zone <i>1</i>", "title": "Zone <i>1</i>",
"height": 100, "height": 100,
"y": "200", "y": 200,
"x": "100", "x": 100,
"id": "zone-1" "id": "zone-1"
}, },
{ {
...@@ -19,6 +19,7 @@ ...@@ -19,6 +19,7 @@
"id": "zone-2" "id": "zone-2"
} }
], ],
"items": [ "items": [
{ {
"displayName": "<b>1</b>", "displayName": "<b>1</b>",
...@@ -28,11 +29,7 @@ ...@@ -28,11 +29,7 @@
}, },
"zone": "Zone <i>1</i>", "zone": "Zone <i>1</i>",
"backgroundImage": "", "backgroundImage": "",
"id": 0, "id": 0
"size": {
"width": "190px",
"height": "auto"
}
}, },
{ {
"displayName": "<i>2</i>", "displayName": "<i>2</i>",
...@@ -43,10 +40,6 @@ ...@@ -43,10 +40,6 @@
"zone": "Zone <b>2</b>", "zone": "Zone <b>2</b>",
"backgroundImage": "", "backgroundImage": "",
"id": 1, "id": 1,
"size": {
"width": "190px",
"height": "auto"
},
"inputOptions": { "inputOptions": {
"value": 100, "value": 100,
"margin": 5 "margin": 5
...@@ -60,11 +53,7 @@ ...@@ -60,11 +53,7 @@
}, },
"zone": "none", "zone": "none",
"backgroundImage": "", "backgroundImage": "",
"id": 2, "id": 2
"size": {
"width": "100px",
"height": "100px"
}
}, },
{ {
"displayName": "", "displayName": "",
...@@ -73,21 +62,12 @@ ...@@ -73,21 +62,12 @@
"correct": "" "correct": ""
}, },
"zone": "none", "zone": "none",
"backgroundImage": "http://i1.kym-cdn.com/entries/icons/square/000/006/151/tumblr_lltzgnHi5F1qzib3wo1_400.jpg", "backgroundImage": "http://placehold.it/100x300",
"id": 3, "id": 3
"size": {
"width": "190px",
"height": "auto"
}
} }
], ],
"state": {
"items": {},
"finished": true
},
"feedback": { "feedback": {
"start": "Intro Feed", "start": "HTML <strong>Intro</strong> Feed",
"finish": "Final <b>Feed</b>" "finish": "Final <strong>feedback</strong>!"
}, }
"targetImg": "http://i0.kym-cdn.com/photos/images/newsfeed/000/030/404/1260585284155.png"
} }
{
"display_name": "DnDv2 XBlock with HTML instructions",
"show_title": false,
"question_text": "Solve this <strong>drag-and-drop</strong> problem.",
"show_question_header": false,
"weight": 1,
"item_background_color": "white",
"item_text_color": "#000080"
}
{ {
"title": "Drag and Drop",
"show_title": true,
"question_text": "",
"show_question_header": true,
"target_img_expanded_url": "http://i0.kym-cdn.com/photos/images/newsfeed/000/030/404/1260585284155.png",
"item_background_color": null,
"item_text_color": null,
"initial_feedback": "Intro Feed",
"display_zone_labels": false,
"zones": [ "zones": [
{ {
"index": 1, "index": 1,
"title": "Zone 1", "title": "Zone 1",
"id": "zone-1",
"height": 100,
"y": "200",
"x": "100", "x": "100",
"width": 200 "y": "200",
"width": 200,
"height": 100,
"id": "zone-1"
}, },
{ {
"index": 2, "index": 2,
"title": "Zone 2", "title": "Zone 2",
"id": "zone-2",
"height": 100,
"y": 0,
"x": 0, "x": 0,
"width": 200 "y": 0,
"width": 200,
"height": 100,
"id": "zone-2"
} }
], ],
"items": [ "items": [
{ {
"displayName": "1", "displayName": "1",
"backgroundImage": "", "backgroundImage": "",
"id": 0, "id": 0,
"size": { "inputOptions": false,
"width": "190px", "size": {"height": "auto", "width": "190px"}
"height": "auto"
},
"inputOptions": false
}, },
{ {
"displayName": "2", "displayName": "2",
"backgroundImage": "", "backgroundImage": "",
"id": 1, "id": 1,
"size": { "inputOptions": true,
"width": "190px", "size": {"height": "auto", "width": "190px"}
"height": "auto"
},
"inputOptions": true
}, },
{ {
"displayName": "X", "displayName": "X",
"backgroundImage": "", "backgroundImage": "",
"id": 2, "id": 2,
"size": { "inputOptions": false,
"width": "100px", "size": {"height": "100px", "width": "100px"}
"height": "100px"
},
"inputOptions": false
}, },
{ {
"displayName": "", "displayName": "",
"backgroundImage": "http://i1.kym-cdn.com/entries/icons/square/000/006/151/tumblr_lltzgnHi5F1qzib3wo1_400.jpg", "backgroundImage": "http://i1.kym-cdn.com/entries/icons/square/000/006/151/tumblr_lltzgnHi5F1qzib3wo1_400.jpg",
"id": 3, "id": 3,
"size": { "inputOptions": false,
"width": "190px", "size": {"height": "auto", "width": "190px"}
"height": "auto"
},
"inputOptions": false
} }
], ]
"state": {
"items": {},
"finished": false
},
"feedback": {
"start": "Intro Feed"
},
"targetImg": "http://i0.kym-cdn.com/photos/images/newsfeed/000/030/404/1260585284155.png",
"title": "Drag and Drop",
"show_title": true,
"question_text": "",
"show_question_header": true
} }
{
"title": "DnDv2 XBlock with plain text instructions",
"show_title": true,
"question_text": "Can you solve this drag-and-drop problem?",
"show_question_header": true,
"target_img_expanded_url": "http://placehold.it/800x600",
"item_background_color": null,
"item_text_color": null,
"initial_feedback": "This is the initial feedback.",
"display_zone_labels": false,
"zones": [
{
"index": 1,
"title": "Zone 1",
"y": 123,
"x": 234,
"width": 345,
"height": 456,
"id": "zone-1"
},
{
"index": 2,
"title": "Zone 2",
"y": 20,
"x": 10,
"width": 30,
"height": 40,
"id": "zone-2"
}
],
"items": [
{
"displayName": "1",
"backgroundImage": "",
"id": 0,
"inputOptions": false
},
{
"displayName": "2",
"backgroundImage": "",
"id": 1,
"inputOptions": true
},
{
"displayName": "X",
"backgroundImage": "",
"id": 2,
"inputOptions": false
},
{
"displayName": "",
"backgroundImage": "http://placehold.it/200x100",
"id": 3,
"inputOptions": false
}
]
}
{
"zones": [
{
"index": 1,
"title": "Zone 1",
"y": 123,
"x": 234,
"width": 345,
"height": 456,
"id": "zone-1"
},
{
"index": 2,
"title": "Zone 2",
"y": 20,
"x": 10,
"width": 30,
"height": 40,
"id": "zone-2"
}
],
"items": [
{
"displayName": "1",
"feedback": {
"incorrect": "No 1",
"correct": "Yes 1"
},
"zone": "Zone 1",
"backgroundImage": "",
"id": 0
},
{
"displayName": "2",
"feedback": {
"incorrect": "No 2",
"correct": "Yes 2"
},
"zone": "Zone 2",
"backgroundImage": "",
"id": 1,
"inputOptions": {
"value": 100,
"margin": 5
}
},
{
"displayName": "X",
"feedback": {
"incorrect": "",
"correct": ""
},
"zone": "none",
"backgroundImage": "",
"id": 2
},
{
"displayName": "",
"feedback": {
"incorrect": "",
"correct": ""
},
"zone": "none",
"backgroundImage": "http://placehold.it/200x100",
"id": 3
}
],
"feedback": {
"start": "This is the initial feedback.",
"finish": "This is the final feedback."
},
"targetImg": "http://placehold.it/800x600",
"displayLabels": false
}
{
"display_name": "DnDv2 XBlock with plain text instructions",
"show_title": true,
"question_text": "Can you solve this drag-and-drop problem?",
"show_question_header": true,
"weight": 1,
"item_background_color": "",
"item_text_color": ""
}
import json
import unittest
from mock import Mock
from nose.tools import assert_true, assert_false, assert_in
from .utils import (
make_block,
load_resource,
TestCaseMixin,
)
class BaseDragAndDropAjaxFixture(TestCaseMixin):
ZONE_1 = None
ZONE_2 = None
FEEDBACK = {
0: {"correct": None, "incorrect": None},
1: {"correct": None, "incorrect": None},
2: {"correct": None, "incorrect": None}
}
FINAL_FEEDBACK = None
FOLDER = None
def setUp(self):
self.patch_workbench()
self.block = make_block()
initial_settings = self.initial_settings()
for field in initial_settings:
setattr(self.block, field, initial_settings[field])
self.block.data = self.initial_data()
@classmethod
def initial_data(cls):
return json.loads(load_resource('data/{}/data.json'.format(cls.FOLDER)))
@classmethod
def initial_settings(cls):
return json.loads(load_resource('data/{}/settings.json'.format(cls.FOLDER)))
@classmethod
def expected_configuration(cls):
return json.loads(load_resource('data/{}/config_out.json'.format(cls.FOLDER)))
@classmethod
def initial_feedback(cls):
""" The initial overall_feedback value """
return cls.expected_configuration()["initial_feedback"]
def test_get_configuration(self):
self.assertEqual(self.expected_configuration(), self.block.get_configuration())
def test_do_attempt_wrong_with_feedback(self):
item_id, zone_id = 0, self.ZONE_2
data = {"val": item_id, "zone": zone_id, "x_percent": "33%", "y_percent": "11%"}
res = self.call_handler('do_attempt', data)
self.assertEqual(res, {
"overall_feedback": None,
"finished": False,
"correct": False,
"correct_location": False,
"feedback": self.FEEDBACK[item_id]["incorrect"]
})
def test_do_attempt_wrong_without_feedback(self):
item_id, zone_id = 2, self.ZONE_1
data = {"val": item_id, "zone": zone_id, "x_percent": "33%", "y_percent": "11%"}
res = self.call_handler('do_attempt', data)
self.assertEqual(res, {
"overall_feedback": None,
"finished": False,
"correct": False,
"correct_location": False,
"feedback": self.FEEDBACK[item_id]["incorrect"]
})
def test_do_attempt_correct(self):
item_id, zone_id = 0, self.ZONE_1
data = {"val": item_id, "zone": zone_id, "x_percent": "33%", "y_percent": "11%"}
res = self.call_handler('do_attempt', data)
self.assertEqual(res, {
"overall_feedback": None,
"finished": False,
"correct": True,
"correct_location": True,
"feedback": self.FEEDBACK[item_id]["correct"]
})
def test_do_attempt_with_input(self):
data = {"val": 1, "zone": self.ZONE_2, "x_percent": "0%", "y_percent": "85%"}
res = self.call_handler('do_attempt', data)
self.assertEqual(res, {
"finished": False,
"correct": False,
"correct_location": True,
"feedback": None,
"overall_feedback": None,
})
expected_state = {
'items': {
"1": {"x_percent": "0%", "y_percent": "85%", "correct_input": False},
},
'finished': False,
'overall_feedback': self.initial_feedback(),
}
self.assertEqual(expected_state, self.call_handler('get_user_state', method="GET"))
data = {"val": 1, "input": "250"}
res = self.call_handler('do_attempt', data)
self.assertEqual(res, {
"finished": False,
"correct": False,
"correct_location": True,
"feedback": self.FEEDBACK[1]['incorrect'],
"overall_feedback": None
})
expected_state = {
'items': {
"1": {"x_percent": "0%", "y_percent": "85%", "input": "250", "correct_input": False},
},
'finished': False,
'overall_feedback': self.initial_feedback(),
}
self.assertEqual(expected_state, self.call_handler('get_user_state', method="GET"))
data = {"val": 1, "input": "103"}
res = self.call_handler('do_attempt', data)
self.assertEqual(res, {
"finished": False,
"correct": True,
"correct_location": True,
"feedback": self.FEEDBACK[1]['correct'],
"overall_feedback": None,
})
expected_state = {
'items': {
"1": {"x_percent": "0%", "y_percent": "85%", "input": "103", "correct_input": True},
},
'finished': False,
'overall_feedback': self.initial_feedback(),
}
self.assertEqual(expected_state, self.call_handler('get_user_state', method="GET"))
def test_grading(self):
published_grades = []
def mock_publish(self, event, params):
if event == 'grade':
published_grades.append(params)
self.block.runtime.publish = mock_publish
self.call_handler('do_attempt', {
"val": 0, "zone": self.ZONE_1, "y_percent": "11%", "x_percent": "33%"
})
self.assertEqual(1, len(published_grades))
self.assertEqual({'value': 0.5, 'max_value': 1}, published_grades[-1])
self.call_handler('do_attempt', {
"val": 1, "zone": self.ZONE_2, "y_percent": "90%", "x_percent": "42%"
})
self.assertEqual(2, len(published_grades))
self.assertEqual({'value': 0.5, 'max_value': 1}, published_grades[-1])
self.call_handler('do_attempt', {"val": 1, "input": "99"})
self.assertEqual(3, len(published_grades))
self.assertEqual({'value': 1, 'max_value': 1}, published_grades[-1])
def test_do_attempt_final(self):
data = {"val": 0, "zone": self.ZONE_1, "x_percent": "33%", "y_percent": "11%"}
self.call_handler('do_attempt', data)
expected_state = {
"items": {
"0": {"x_percent": "33%", "y_percent": "11%", "correct_input": True}
},
"finished": False,
'overall_feedback': self.initial_feedback(),
}
self.assertEqual(expected_state, self.call_handler('get_user_state', method="GET"))
data = {"val": 1, "zone": self.ZONE_2, "x_percent": "22%", "y_percent": "22%"}
res = self.call_handler('do_attempt', data)
data = {"val": 1, "input": "99"}
res = self.call_handler('do_attempt', data)
self.assertEqual(res, {
"overall_feedback": self.FINAL_FEEDBACK,
"finished": True,
"correct": True,
"correct_location": True,
"feedback": self.FEEDBACK[1]["correct"]
})
expected_state = {
"items": {
"0": {"x_percent": "33%", "y_percent": "11%", "correct_input": True},
"1": {"x_percent": "22%", "y_percent": "22%", "input": "99", "correct_input": True}
},
"finished": True,
'overall_feedback': self.FINAL_FEEDBACK,
}
self.assertEqual(expected_state, self.call_handler('get_user_state', method="GET"))
class TestDragAndDropHtmlData(BaseDragAndDropAjaxFixture, unittest.TestCase):
FOLDER = "html"
ZONE_1 = "Zone <i>1</i>"
ZONE_2 = "Zone <b>2</b>"
FEEDBACK = {
0: {"correct": "Yes <b>1</b>", "incorrect": "No <b>1</b>"},
1: {"correct": "Yes <i>2</i>", "incorrect": "No <i>2</i>"},
2: {"correct": "", "incorrect": ""}
}
FINAL_FEEDBACK = "Final <strong>feedback</strong>!"
class TestDragAndDropPlainData(BaseDragAndDropAjaxFixture, unittest.TestCase):
FOLDER = "plain"
ZONE_1 = "Zone 1"
ZONE_2 = "Zone 2"
FEEDBACK = {
0: {"correct": "Yes 1", "incorrect": "No 1"},
1: {"correct": "Yes 2", "incorrect": "No 2"},
2: {"correct": "", "incorrect": ""}
}
FINAL_FEEDBACK = "This is the final feedback."
class TestOldDataFormat(TestDragAndDropPlainData):
"""
Make sure we can work with the slightly-older format for 'data' field values.
"""
FOLDER = "old"
FINAL_FEEDBACK = "Final Feed"
import unittest
from .utils import (
DEFAULT_START_FEEDBACK,
DEFAULT_FINISH_FEEDBACK,
make_request,
make_block,
TestCaseMixin,
)
class BasicTests(TestCaseMixin, unittest.TestCase):
""" Basic unit tests for the Drag and Drop block, using its default settings """
def setUp(self):
self.block = make_block()
self.patch_workbench()
def test_templates_contents(self):
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('Loading drag and drop exercise.', student_fragment.content)
def test_get_configuration(self):
"""
Test the get_configuration() method.
The result of this method is passed to the block's JavaScript during initialization.
"""
config = self.block.get_configuration()
zones = config.pop("zones")
items = config.pop("items")
self.assertEqual(config, {
"display_zone_labels": False,
"title": "Drag and Drop",
"show_title": True,
"question_text": "",
"show_question_header": True,
"target_img_expanded_url": '/expanded/url/to/drag_and_drop_v2/public/img/triangle.png',
"item_background_color": None,
"item_text_color": None,
"initial_feedback": DEFAULT_START_FEEDBACK,
})
self.assertEqual(zones, [
{
"index": 1,
"title": "Zone 1",
"id": "zone-1",
"x": 160,
"y": 30,
"width": 196,
"height": 178,
},
{
"index": 2,
"title": "Zone 2",
"id": "zone-2",
"x": 86,
"y": 210,
"width": 340,
"height": 140,
}
])
# Items should contain no answer data:
self.assertEqual(items, [
{"id": 0, "displayName": "1", "backgroundImage": "", "inputOptions": False},
{"id": 1, "displayName": "2", "backgroundImage": "", "inputOptions": False},
{"id": 2, "displayName": "X", "backgroundImage": "", "inputOptions": False},
])
def test_ajax_solve_and_reset(self):
# Check assumptions / initial conditions:
self.assertFalse(self.block.completed)
def assert_user_state_empty():
self.assertEqual(self.block.item_state, {})
self.assertEqual(self.call_handler("get_user_state"), {
'items': {},
'finished': False,
'overall_feedback': DEFAULT_START_FEEDBACK,
})
assert_user_state_empty()
# Drag both items into the correct spot:
data = {"val": 0, "zone": "Zone 1", "x_percent": "33%", "y_percent": "11%"}
self.call_handler('do_attempt', data)
data = {"val": 1, "zone": "Zone 2", "x_percent": "67%", "y_percent": "80%"}
self.call_handler('do_attempt', data)
# Check the result:
self.assertTrue(self.block.completed)
self.assertEqual(self.block.item_state, {
'0': {'x_percent': '33%', 'y_percent': '11%'},
'1': {'x_percent': '67%', 'y_percent': '80%'},
})
self.assertEqual(self.call_handler('get_user_state'), {
'items': {
'0': {'x_percent': '33%', 'y_percent': '11%', 'correct_input': True},
'1': {'x_percent': '67%', 'y_percent': '80%', 'correct_input': True},
},
'finished': True,
'overall_feedback': DEFAULT_FINISH_FEEDBACK,
})
# Reset to initial conditions
self.call_handler('reset', {})
self.assertTrue(self.block.completed)
assert_user_state_empty()
def test_studio_submit(self):
body = {
'display_name': "Test Drag & Drop",
'show_title': False,
'question_text': "Question Drag & Drop",
'show_question_header': False,
'item_background_color': 'cornflowerblue',
'item_text_color': 'coral',
'weight': '5',
'data': {
'foo': 1
},
}
res = self.call_handler('studio_submit', body)
self.assertEqual(res, {'result': 'success'})
self.assertEqual(self.block.show_title, False)
self.assertEqual(self.block.display_name, "Test Drag & Drop")
self.assertEqual(self.block.question_text, "Question Drag & Drop")
self.assertEqual(self.block.show_question_header, False)
self.assertEqual(self.block.item_background_color, "cornflowerblue")
self.assertEqual(self.block.item_text_color, "coral")
self.assertEqual(self.block.weight, 5)
self.assertEqual(self.block.data, {'foo': 1})
def test_expand_static_url(self):
""" Test the expand_static_url handler needed in Studio when changing the image """
res = self.call_handler('expand_static_url', '/static/blah.png')
self.assertEqual(res, {'url': '/course/test-course/assets/blah.png'})
def test_image_url(self):
""" Ensure that the default image and custom URLs are both expanded by the runtime """
self.assertEqual(self.block.data.get("targetImg"), None)
self.assertEqual(
self.block.get_configuration()["target_img_expanded_url"],
'/expanded/url/to/drag_and_drop_v2/public/img/triangle.png',
)
self.block.data["targetImg"] = "/static/foo.png"
self.assertEqual(
self.block.get_configuration()["target_img_expanded_url"],
'/course/test-course/assets/foo.png',
)
import logging
import json
import unittest
from webob import Request
from mock import Mock
from workbench.runtime import WorkbenchRuntime
from xblock.fields import ScopeIds
from xblock.runtime import KvsFieldData, DictKeyValueStore
from nose.tools import (
assert_equals, assert_true, assert_false,
assert_in
)
from tests.utils import load_resource
import drag_and_drop_v2
# Silence too verbose Django logging
logging.disable(logging.DEBUG)
def make_request(body, method='POST'):
request = Request.blank('/')
request.method = 'POST'
request.body = body.encode('utf-8')
request.method = method
return request
def make_block():
block_type = 'drag_and_drop_v2'
key_store = DictKeyValueStore()
field_data = KvsFieldData(key_store)
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():
block = make_block()
block.display_name = "Test Drag & Drop"
block.question_text = "Question Drag & Drop"
block.weight = 5
student_fragment = block.runtime.render(block, 'student_view', ['ingore'])# block.render('student_view', Mock())
assert_in('<section class="xblock--drag-and-drop">',
student_fragment.content)
def test_studio_submit():
block = make_block()
body = json.dumps({
'display_name': "Test Drag & Drop",
'show_title': False,
'question_text': "Question Drag & Drop",
'show_question_header': False,
'item_background_color': 'cornflowerblue',
'item_text_color': 'coral',
'weight': '5',
'data': {
'foo': 1
}
})
res = block.handle('studio_submit', make_request(body))
assert_equals(json.loads(res.body), {'result': 'success'})
assert_equals(block.show_title, False)
assert_equals(block.display_name, "Test Drag & Drop")
assert_equals(block.question_text, "Question Drag & Drop")
assert_equals(block.show_question_header, False)
assert_equals(block.item_background_color, "cornflowerblue")
assert_equals(block.item_text_color, "coral")
assert_equals(block.weight, 5)
assert_equals(block.data, {'foo': 1})
class BaseDragAndDropAjaxFixture(object):
_oldMaxDiff = None
ZONE_1 = None
ZONE_2 = None
FEEDBACK = {
0: {"correct": None, "incorrect": None},
1: {"correct": None, "incorrect": None},
2: {"correct": None, "incorrect": None}
}
FINAL_FEEDBACK = None
def __init__(self, *args, **kwargs):
self._initial_data = None
self._block = None
super(BaseDragAndDropAjaxFixture, self).__init__(*args, **kwargs)
@classmethod
def setUpClass(cls):
cls._oldMaxDiff = assert_equals.__self__.maxDiff
assert_equals.__self__.maxDiff = None
@classmethod
def tearDownClass(cls):
assert_equals.__self__.maxDiff = cls._oldMaxDiff
def setUp(self):
self._block = make_block()
self._initial_data = self.initial_data()
self._block.data = self._initial_data
def tearDown(self):
self._block = None
def initial_data(self):
raise NotImplementedError
def get_data_response(self):
raise NotImplementedError
def test_get_data_returns_expected_data(self):
expected_response = self.get_data_response()
get_data_response = json.loads(self._block.handle('get_data', Mock()).body)
assert_equals(expected_response, get_data_response)
def test_do_attempt_wrong_with_feedback(self):
item_id, zone_id = 0, self.ZONE_2
data = json.dumps({"val": item_id, "zone": zone_id, "top": "31px", "left": "216px"})
res = json.loads(self._block.handle('do_attempt', make_request(data)).body)
assert_equals(res, {
"final_feedback": None,
"finished": False,
"correct": False,
"correct_location": False,
"feedback": self.FEEDBACK[item_id]["incorrect"]
})
def test_do_attempt_wrong_without_feedback(self):
item_id, zone_id = 2, self.ZONE_1
data = json.dumps({"val": item_id, "zone": zone_id, "top": "42px", "left": "100px"})
res = json.loads(self._block.handle('do_attempt', make_request(data)).body)
assert_equals(res, {
"final_feedback": None,
"finished": False,
"correct": False,
"correct_location": False,
"feedback": self.FEEDBACK[item_id]["incorrect"]
})
def test_do_attempt_correct(self):
item_id, zone_id = 0, self.ZONE_1
data = json.dumps({"val": item_id, "zone": zone_id, "top": "11px", "left": "111px"})
res = json.loads(self._block.handle('do_attempt', make_request(data)).body)
assert_equals(res, {
"final_feedback": None,
"finished": False,
"correct": True,
"correct_location": True,
"feedback": self.FEEDBACK[item_id]["correct"]
})
def test_do_attempt_with_input(self):
data = json.dumps({"val": 1, "zone": self.ZONE_2, "top": "22px", "left": "222px"})
res = json.loads(self._block.handle('do_attempt', make_request(data)).body)
assert_equals(res, {
"finished": False,
"correct": False,
"correct_location": True,
"feedback": None,
"final_feedback": None
})
expected = self.get_data_response()
expected["state"] = {
"items": {
"1": {"top": "22px", "left": "222px", "absolute": True, "correct_input": False}
},
"finished": False
}
get_data = json.loads(self._block.handle('get_data', Mock()).body)
assert_equals(expected, get_data)
data = json.dumps({"val": 1, "input": "250"})
res = json.loads(self._block.handle('do_attempt', make_request(data)).body)
assert_equals(res, {
"finished": False,
"correct": False,
"correct_location": True,
"feedback": self.FEEDBACK[1]['incorrect'],
"final_feedback": None
})
expected = self.get_data_response()
expected["state"] = {
"items": {
"1": {"top": "22px", "left": "222px", "absolute": True,
"input": "250", "correct_input": False}
},
"finished": False
}
get_data = json.loads(self._block.handle('get_data', Mock()).body)
assert_equals(expected, get_data)
data = json.dumps({"val": 1, "input": "103"})
res = json.loads(self._block.handle('do_attempt', make_request(data)).body)
assert_equals(res, {
"finished": False,
"correct": True,
"correct_location": True,
"feedback": self.FEEDBACK[1]['correct'],
"final_feedback": None
})
expected = self.get_data_response()
expected["state"] = {
"items": {
"1": {"top": "22px", "left": "222px", "absolute": True,
"input": "103", "correct_input": True}
},
"finished": False
}
get_data = json.loads(self._block.handle('get_data', Mock()).body)
assert_equals(expected, get_data)
def test_grading(self):
published_grades = []
def mock_publish(self, event, params):
if event == 'grade':
published_grades.append(params)
self._block.runtime.publish = mock_publish
data = json.dumps({"val": 0, "zone": self.ZONE_1, "top": "11px", "left": "111px"})
self._block.handle('do_attempt', make_request(data))
assert_equals(1, len(published_grades))
assert_equals({'value': 0.5, 'max_value': 1}, published_grades[-1])
data = json.dumps({"val": 1, "zone": self.ZONE_2, "top": "22px", "left": "222px"})
json.loads(self._block.handle('do_attempt', make_request(data)).body)
assert_equals(2, len(published_grades))
assert_equals({'value': 0.5, 'max_value': 1}, published_grades[-1])
data = json.dumps({"val": 1, "input": "99"})
json.loads(self._block.handle('do_attempt', make_request(data)).body)
assert_equals(3, len(published_grades))
assert_equals({'value': 1, 'max_value': 1}, published_grades[-1])
def test_do_attempt_final(self):
data = json.dumps({"val": 0, "zone": self.ZONE_1, "top": "11px", "left": "111px"})
self._block.handle('do_attempt', make_request(data))
expected = self.get_data_response()
expected["state"] = {
"items": {
"0": {"top": "11px", "left": "111px", "absolute": True,
"correct_input": True}
},
"finished": False
}
get_data = json.loads(self._block.handle('get_data', Mock()).body)
assert_equals(expected, get_data)
data = json.dumps({"val": 1, "zone": self.ZONE_2, "top": "22px", "left": "222px"})
res = json.loads(self._block.handle('do_attempt', make_request(data)).body)
data = json.dumps({"val": 1, "input": "99"})
res = json.loads(self._block.handle('do_attempt', make_request(data)).body)
assert_equals(res, {
"final_feedback": self.FINAL_FEEDBACK,
"finished": True,
"correct": True,
"correct_location": True,
"feedback": self.FEEDBACK[1]["correct"]
})
expected = self.get_data_response()
expected["state"] = {
"items": {
"0": {"top": "11px", "left": "111px", "absolute": True, "correct_input": True},
"1": {"top": "22px", "left": "222px", "absolute": True, "input": "99",
"correct_input": True}
},
"finished": True
}
expected["feedback"]["finish"] = self.FINAL_FEEDBACK
get_data = json.loads(self._block.handle('get_data', Mock()).body)
assert_equals(expected, get_data)
class TestDragAndDropHtmlData(BaseDragAndDropAjaxFixture, unittest.TestCase):
ZONE_1 = "Zone <i>1</i>"
ZONE_2 = "Zone <b>2</b>"
FEEDBACK = {
0: {"correct": "Yes <b>1</b>", "incorrect": "No <b>1</b>"},
1: {"correct": "Yes <i>2</i>", "incorrect": "No <i>2</i>"},
2: {"correct": "", "incorrect": ""}
}
FINAL_FEEDBACK = "Final <b>Feed</b>"
def initial_data(self):
return json.loads(load_resource('data/test_html_data.json'))
def get_data_response(self):
return json.loads(load_resource('data/test_get_html_data.json'))
class TestDragAndDropPlainData(BaseDragAndDropAjaxFixture, unittest.TestCase):
ZONE_1 = "Zone 1"
ZONE_2 = "Zone 2"
FEEDBACK = {
0: {"correct": "Yes 1", "incorrect": "No 1"},
1: {"correct": "Yes 2", "incorrect": "No 2"},
2: {"correct": "", "incorrect": ""}
}
FINAL_FEEDBACK = "Final Feed"
def initial_data(self):
return json.loads(load_resource('data/test_data.json'))
def get_data_response(self):
return json.loads(load_resource('data/test_get_data.json'))
def test_ajax_solve_and_reset():
block = make_block()
assert_false(block.completed)
assert_equals(block.item_state, {})
data = json.dumps({"val":0,"zone":"Zone 1","top":"11px","left":"111px"})
block.handle('do_attempt', make_request(data))
data = json.dumps({"val":1,"zone":"Zone 2","top":"22px","left":"222px"})
block.handle('do_attempt', make_request(data))
assert_true(block.completed)
assert_equals(block.item_state, {'0': {"top": "11px", "left": "111px", "absolute": True},
'1': {"top": "22px", "left": "222px", "absolute": True}})
block.handle('reset', make_request("{}"))
assert_true(block.completed)
assert_equals(block.item_state, {})
import json
import pkg_resources import pkg_resources
import re
from mock import patch
from webob import Request
from workbench.runtime import WorkbenchRuntime
from xblock.fields import ScopeIds
from xblock.runtime import KvsFieldData, DictKeyValueStore
import drag_and_drop_v2
DEFAULT_START_FEEDBACK = "Drag the items onto the image above."
DEFAULT_FINISH_FEEDBACK = "Good work! You have completed this drag and drop exercise."
def make_request(data, method='POST'):
""" Make a webob JSON Request """
request = Request.blank('/')
request.method = 'POST'
request.body = json.dumps(data).encode('utf-8') if data is not None else ""
request.method = method
return request
def make_block():
""" Instantiate a DragAndDropBlock XBlock inside a WorkbenchRuntime """
block_type = 'drag_and_drop_v2'
key_store = DictKeyValueStore()
field_data = KvsFieldData(key_store)
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 load_resource(resource_path): def load_resource(resource_path):
...@@ -7,3 +41,32 @@ def load_resource(resource_path): ...@@ -7,3 +41,32 @@ def load_resource(resource_path):
""" """
resource_content = pkg_resources.resource_string(__name__, resource_path) resource_content = pkg_resources.resource_string(__name__, resource_path)
return unicode(resource_content) return unicode(resource_content)
class TestCaseMixin(object):
""" Helpful mixins for unittest TestCase subclasses """
maxDiff = None
def patch_workbench(self):
self.apply_patch(
'workbench.runtime.WorkbenchRuntime.local_resource_url',
lambda _, _block, path: '/expanded/url/to/drag_and_drop_v2/' + path
)
self.apply_patch(
'workbench.runtime.WorkbenchRuntime.replace_urls',
lambda _, html: re.sub(r'"/static/([^"]*)"', r'"/course/test-course/assets/\1"', html),
create=True,
)
def apply_patch(self, *args, **kwargs):
new_patch = patch(*args, **kwargs)
mock = new_patch.start()
self.addCleanup(new_patch.stop)
return mock
def call_handler(self, handler_name, data=None, expect_json=True, method='POST'):
response = self.block.handle(handler_name, make_request(data, method=method))
if expect_json:
self.assertEqual(response.status_code, 200)
return json.loads(response.body)
return response
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