Commit 6c3c75a3 by Xavier Antoviaque

Merge pull request #2 from FiloSottile/refactor

Iteration and wide refactor
parents 0290f4ff 260d00f3
......@@ -51,3 +51,5 @@ docs/_build/
# PyBuilder
target/
workbench.db
# Drag and Drop XBlock v2
This XBlock implements a friendly drag-and-drop style question, where the student has to drag items on zones on a target image.
The editor is fully guided. Features include:
* custom target image
* free target zone positioning and sizing
* custom size items
* image items
* decoy items that don't have a zone
* feedback popups for both correct and incorrect attempts
* introductory and final feedback
It supports progressive grading and keeps progress across refreshes.
All checking and record keeping is done at server side.
## Installing
Just run
```
$ pip install -e .
```
from the XBlock folder and add `drag-and-drop-v2` to your Advanced Module List.
## Testing
1. In a virtualenv, run
```bash
$ (cd .../xblock-sdk/; pip install -r requirements.txt)
$ (cd .../xblock-drag-and-drop-v2/; pip install -r tests/requirements.txt)
```
2. In the xblock-sdk repository, create the following configuration file in `workbench/settings_drag_and_drop_v2.py`
```python
from settings import *
INSTALLED_APPS += ('drag_and_drop_v2',)
DATABASES['default']['NAME'] = 'workbench.db'
```
3. Run this to sync the database before starting the workbench (answering no to the superuser question is ok):
```bash
$ .../xblock-sdk/manage.py syncdb --settings=workbench.settings_drag_and_drop_v2
```
4. To run the tests, from the xblock-drag-and-drop-v2 repository root:
```bash
$ DJANGO_SETTINGS_MODULE="workbench.settings_drag_and_drop_v2" nosetests --rednose --verbose --with-cover --cover-package=drag_and_drop_v2
```
......@@ -4,12 +4,13 @@
# Imports ###########################################################
import logging
import textwrap
import json
import webob
import copy
import urllib
from xblock.core import XBlock
from xblock.fields import Scope, String
from xblock.fields import Scope, String, Dict, Float
from xblock.fragment import Fragment
from .utils import render_template, load_resource
......@@ -36,40 +37,68 @@ class DragAndDropBlock(XBlock):
question_text = String(
display_name="Question text",
help="The question text that is displayed to the user",
scope=Scope.settings
scope=Scope.settings,
default=""
)
weight = Float(
display_name="Weight",
help="This is the maximum score that the user receives when he/she successfully completes the problem",
scope=Scope.settings,
default=1
)
data = String(
data = Dict(
display_name="Drag and Drop",
help="JSON spec as generated by the builder",
scope=Scope.content,
default='{"feedback":{"start":"Intro","finish":"Final"},"items":[{"displayName":"A","zone":"Uno","id":0,"feedback":{"correct":"Si","incorrect":"No"},"size":{"width":"190px","height":"auto"},"backgroundImage":""},{"displayName":"B","zone":"none","id":1,"feedback":{"correct":"","incorrect":""},"size":{"width":"190px","height":"auto"},"backgroundImage":""}],"zones":[{"title":"Uno","id":"zone-1","active":true,"index":1,"width":200,"height":100,"x":0,"y":0},{"title":"Due","id":"zone-2","active":true,"index":2,"width":200,"height":100,"x":"300","y":"210"}],"targetImg":"https://i.imgur.com/PoI27ox.png"}'
# default=textwrap.dedent("""
# {
# feedback: {},
# items: [],
# zones: [],
# targetImg: 'img/triangle.png'
# }
# """)
default={
'feedback': {
'start': '',
'finish': ''
},
'items': [],
'zones': [],
'targetImg': None
}
)
item_state = Dict(
help="How the student has interacted with the problem",
scope=Scope.user_state,
default={}
)
has_score = True
def student_view(self, context):
"""
Player view, displayed to the student
"""
max_score_string = '({0} Point{1} Possible)'.format(int(self.weight),
's' if self.weight > 1 else '') if self.weight else ''
js_templates = load_resource('/templates/html/js_templates.html')
context = {
'js_templates': js_templates,
'title': self.display_name,
'question_text': self.question_text
'question_text': self.question_text,
'max_score_string': max_score_string
}
fragment = Fragment()
fragment.add_content(render_template('/templates/html/drag_and_drop.html', context))
fragment.add_css(load_resource('public/css/drag_and_drop.css'))
fragment.add_javascript(load_resource('public/js/vendor/jquery.html5-placeholder-shim.js'))
fragment.add_javascript(load_resource('public/js/vendor/underscore1.6.0.js'))
fragment.add_javascript(load_resource('public/js/drag_and_drop.js'))
fragment.add_css_url(self.runtime.local_resource_url(self,
'public/css/vendor/jquery-ui-1.10.4.custom.min.css'))
fragment.add_css_url(self.runtime.local_resource_url(self,
'public/css/drag_and_drop.css'))
fragment.add_javascript_url(self.runtime.local_resource_url(self,
'public/js/vendor/jquery.html5-placeholder-shim.js'))
fragment.add_javascript_url(self.runtime.local_resource_url(self,
'public/js/vendor/handlebars-v1.1.2.js'))
fragment.add_javascript_url(self.runtime.local_resource_url(self,
'public/js/drag_and_drop.js'))
fragment.initialize_js('DragAndDropBlock')
......@@ -80,14 +109,25 @@ class DragAndDropBlock(XBlock):
Editing view in Studio
"""
context = {}
js_templates = load_resource('/templates/html/js_templates.html')
context = {
'js_templates': js_templates,
'self': self,
'data': urllib.quote(json.dumps(self.data)),
}
fragment = Fragment()
fragment.add_content(render_template('/templates/html/drag_and_drop_edit.html', context))
fragment.add_css(load_resource('public/css/drag_and_drop_edit.css'))
fragment.add_javascript(load_resource('public/js/vendor/jquery.html5-placeholder-shim.js'))
fragment.add_javascript(load_resource('public/js/vendor/underscore1.6.0.js'))
fragment.add_javascript(load_resource('public/js/drag_and_drop_edit.js'))
fragment.add_css_url(self.runtime.local_resource_url(self,
'public/css/vendor/jquery-ui-1.10.4.custom.min.css'))
fragment.add_css_url(self.runtime.local_resource_url(self,
'public/css/drag_and_drop_edit.css'))
fragment.add_javascript_url(self.runtime.local_resource_url(self,
'public/js/vendor/jquery.html5-placeholder-shim.js'))
fragment.add_javascript_url(self.runtime.local_resource_url(self,
'public/js/vendor/handlebars-v1.1.2.js'))
fragment.add_javascript_url(self.runtime.local_resource_url(self,
'public/js/drag_and_drop_edit.js'))
fragment.initialize_js('DragAndDropEditBlock')
......@@ -97,16 +137,8 @@ class DragAndDropBlock(XBlock):
def studio_submit(self, submissions, suffix=''):
self.display_name = submissions['display_name']
self.question_text = submissions['question_text']
data = submissions['data']
try:
json.loads(data)
self.data = data
except ValueError as e:
return {
'result': 'error',
'message': e.message
}
self.weight = float(submissions['weight'])
self.data = submissions['data']
return {
'result': 'success',
......@@ -114,4 +146,57 @@ class DragAndDropBlock(XBlock):
@XBlock.handler
def get_data(self, request, suffix=''):
return webob.response.Response(body=self.data)
data = copy.deepcopy(self.data)
for item in data['items']:
# Strip answers
del item['feedback']
del item['zone']
tot_items = sum(1 for i in self.data['items'] if i['zone'] != 'none')
if len(self.item_state) != tot_items:
del data['feedback']['finish']
data['state'] = {
'items': self.item_state,
'finished': len(self.item_state) == tot_items
}
return webob.response.Response(body=json.dumps(data))
@XBlock.json_handler
def do_attempt(self, attempt, suffix=''):
item = next(i for i in self.data['items'] if i['id'] == attempt['val'])
tot_items = sum(1 for i in self.data['items'] if i['zone'] != 'none')
if item['zone'] == attempt['zone']:
self.item_state[item['id']] = (attempt['top'], attempt['left'])
if len(self.item_state) == tot_items:
final_feedback = self.data['feedback']['finish']
else:
final_feedback = None
try:
self.runtime.publish(self, 'grade', {
'value': len(self.item_state) / float(tot_items) * self.weight,
'max_value': self.weight,
})
except NotImplementedError:
# Note, this publish method is unimplemented in Studio runtimes,
# so we have to figure that we're running in Studio for now
pass
return {
'correct': True,
'finished': len(self.item_state) == tot_items,
'final_feedback': final_feedback,
'feedback': item['feedback']['correct']
}
else:
return {
'correct': False,
'finished': len(self.item_state) == tot_items,
'final_feedback': None,
'feedback': item['feedback']['incorrect']
}
......@@ -52,12 +52,16 @@
position: relative;
float: left;
display: inline;
z-index: 100;
/* Some versions of the drag and drop library try to fiddle with this */
z-index: 10 !important;
margin-bottom: 5px;
padding: 10px;
}
.xblock--drag-and-drop .option.hover { background: #ccc; }
.xblock--drag-and-drop .drag-container .items .option img {
max-width: 100%;
}
.xblock--drag-and-drop .option.fade { opacity: 0.6; }
......@@ -74,19 +78,19 @@
}
.xblock--drag-and-drop .target-img {
background: url('../img/triangle.png') no-repeat;
width: 100%;
height: 100%;
}
.xblock--drag-and-drop .zone {
/*border: 1px solid #000;*/
position: absolute;
display: -webkit-box;
display: -moz-box;
display: -ms-flexbox;
display: -webkit-flex;
display: flex;
display: -moz-box;
display: -ms-flexbox;
display: -webkit-flex;
display: flex;
/* Internet Explorer 10 */
-ms-flex-pack:center;
......@@ -117,25 +121,6 @@
margin-bottom: auto;
}
.xblock--drag-and-drop .zone.one {
height: 75px;
width: 115px;
top: 130px;
left: 200px;
}
.xblock--drag-and-drop .zone.two {
height: 120px;
width: 200px;
top: 220px;
left: 157px;
}
.xblock--drag-and-drop .zone.three {
height: 120px;
width: 200px;
bottom: 30px;
left: 157px;
}
/*** IE9 alignment fix ***/
.lt-ie10 .xblock--drag-and-drop .zone {
display: table;
......@@ -155,5 +140,5 @@
}
.no-close .ui-dialog-titlebar-close {
display: none;
display: none;
}
......@@ -6,45 +6,12 @@
background: #fff;
}
.xblock--drag-and-drop h1,
.xblock--drag-and-drop h2,
.xblock--drag-and-drop h3,
.xblock--drag-and-drop h4,
.xblock--drag-and-drop h5,
.xblock--drag-and-drop h6,
.xblock--drag-and-drop p,
.xblock--drag-and-drop li,
.xblock--drag-and-drop a {
font-family: Arial;
}
.xblock--drag-and-drop h1 {
color: #adadad;
}
.xblock--drag-and-drop h2 {
color: #333;
margin: 0;
text-transform: uppercase;
.modal-window .editor-with-buttons.xblock--drag-and-drop {
/* Fix Studio edito height */
margin-bottom: 0;
height: 380px;
}
.xblock--drag-and-drop header p,
.xblock--drag-and-drop footer p {
color: #adadad;
line-height: 1.5em;
}
.xblock--drag-and-drop .small {
font-size: 0.6em;
}
.xblock--drag-and-drop .drag-container {
width: 760px;
background: #ebf0f2;
position: relative;
}
/** Draggable Items **/
.xblock--drag-and-drop .items {
width: 210px;
......@@ -57,21 +24,6 @@
list-style-type: none;
}
.xblock--drag-and-drop .items .option {
width: 190px;
background: #2e83cd;
color: #fff;
position: relative;
float: left;
display: inline;
z-index: 100;
margin-bottom: 5px;
padding: 10px;
}
.xblock--drag-and-drop .option.hover { background: #ccc; }
.xblock--drag-and-drop .option.fade { opacity: 0.6; }
/*** Drop Target ***/
.xblock--drag-and-drop .target {
......@@ -86,20 +38,19 @@
}
.xblock--drag-and-drop .target-img {
background: url(../img/triangle.png) no-repeat;
background: url('../img/triangle.png') no-repeat;
width: 100%;
height: 100%;
}
.xblock--drag-and-drop .zone {
/*border: 1px solid #000;*/
position: absolute;
display: -webkit-box;
display: -moz-box;
display: -ms-flexbox;
display: -webkit-flex;
display: flex;
display: -moz-box;
display: -ms-flexbox;
display: -webkit-flex;
display: flex;
/* Internet Explorer 10 */
-ms-flex-pack:center;
......@@ -130,25 +81,6 @@
margin-bottom: auto;
}
.xblock--drag-and-drop .zone.one {
height: 75px;
width: 115px;
top: 130px;
left: 200px;
}
.xblock--drag-and-drop .zone.two {
height: 120px;
width: 200px;
top: 220px;
left: 157px;
}
.xblock--drag-and-drop .zone.three {
height: 120px;
width: 200px;
bottom: 30px;
left: 157px;
}
/*** IE9 alignment fix ***/
.lt-ie10 .xblock--drag-and-drop .zone {
display: table;
......@@ -160,26 +92,13 @@
text-align: center;
}
/*** FEEDBACK ***/
.xblock--drag-and-drop .feedback {
width: 740px;
border-top: #ccc 1px solid;
margin: 20px 10px;
padding-top: 10px;
}
.xblock--drag-and-drop .feedback .message {
margin: 5px 0 0;
}
/** Builder **/
.xblock--drag-and-drop .hidden {
display: none !important;
}
.xblock--drag-and-drop .drag-builder {
/* TODO */
height: 375px;
height: 100%;
overflow: scroll;
}
......@@ -216,12 +135,6 @@
float: left;
}
.xblock--drag-and-drop .drag-builder .continue {
position: absolute;
right: 0;
top: -5px;
}
.xblock--drag-and-drop .drag-builder .items {
width: calc(100% - 515px);
margin: 10px 0 0 0;
......@@ -231,6 +144,10 @@
margin-left: 0;
}
.xblock--drag-and-drop .drag-builder .target-image-form input {
width: 50%;
}
.xblock--drag-and-drop .zones-form .zone-row label {
display: inline-block;
width: 18%;
......@@ -257,8 +174,6 @@
}
.xblock--drag-and-drop .drag-builder .zone {
width: 200px;
height: 100px;
border: 1px dotted #666;
}
......@@ -389,8 +304,8 @@
top: 2px;
left: 6px;
-webkit-transform: rotate(45deg);
-ms-transform: rotate(45deg);
transform: rotate(45deg);
-ms-transform: rotate(45deg);
transform: rotate(45deg);
}
.xblock--drag-and-drop .icon.remove:after {
......@@ -404,8 +319,8 @@
top: 6px;
left: 0;
-webkit-transform: rotate(45deg);
-ms-transform: rotate(45deg);
transform: rotate(45deg);
-ms-transform: rotate(45deg);
transform: rotate(45deg);
}
.xblock--drag-and-drop .remove-item .icon.remove {
......
<section class="xblock--drag-and-drop">
<link rel="stylesheet" href="https://code.jquery.com/ui/1.10.4/themes/smoothness/jquery-ui.css">
{{ js_templates|safe }}
<h2 class="problem-header">
{{ title }}
</h2>
<div class="problem-progress">(1 point possible)</div>
<div class="problem-progress">{{ max_score_string }}</div>
<section class="problem" role="application">
<p>
......
{% load i18n %}
<div class="xblock--drag-and-drop editor-with-buttons">
<link rel="stylesheet" href="https://code.jquery.com/ui/1.10.4/themes/smoothness/jquery-ui.css">
{{ js_templates|safe }}
<section class="drag-builder">
<script type="text/javascript">
var DragAndDropV2BlockPreviousData = JSON.parse(decodeURIComponent('{{ data|safe }}'));
</script>
<section class="drag-builder">
<div class="tab feedback-tab">
<p class="tab-content">
Note: don't edit the question if students already answered it! Delete it and create a new one.
</p>
<section class="tab-content">
<form class="feedback-form">
<h3>Question title</h3>
<input class="display-name" />
<input class="display-name" value="{{ self.display_name }}" />
<h3>Maximum score</h3>
<input class="weight" value="1" value="{{ self.weight }}"/>
<h3>Question text</h3>
<textarea class="question-text"></textarea>
<textarea class="question-text">{{ self.question_text }}</textarea>
<h3>Introduction Feedback</h3>
<textarea class="intro-feedback"></textarea>
<textarea class="intro-feedback">{{ self.data.feedback.start }}</textarea>
<h3>Final Feedback</h3>
<textarea class="final-feedback"></textarea>
<textarea class="final-feedback">{{ self.data.feedback.finish }}</textarea>
</form>
</section>
<!-- <footer class="tab-footer">
<button class="btn continue goto-zones">Continue</button>
</footer> -->
</div>
<div class="tab zones-tab hidden">
......@@ -31,6 +38,11 @@
<h3>Zone Positions</h3>
</header>
<section class="tab-content">
<section class="tab-content target-image-form">
<label>New background URL:</label>
<input type="text">
<button class="btn">Change background</button>
</section>
<div class="items">
<form class="zones-form"></form>
<a href="#" class="add-zone add-element"><div class="icon add"></div>Add a zone</a>
......@@ -39,9 +51,6 @@
<div class="target-img"></div>
</div>
</section>
<!-- <footer class="tab-footer">
<button class="btn continue goto-items">Continue</button>
</footer> -->
</div>
<div class="tab items-tab hidden">
......@@ -53,7 +62,6 @@
</section>
<footer class="tab-footer">
<a href="#" class="add-item add-element"><div class="icon add"></div>Add an item</a>
<!-- <button class="btn continue goto-exercise">Finish</button> -->
</footer>
</div>
......
<script id="item-tpl" type="text/html">
<li class="option" data-value="{{ id }}"
style="width: {{ size.width }}; height: {{ size.height }}">
{{ displayName }}
</li>
</script>
<script id="image-item-tpl" type="text/html">
<li class="option" data-value="{{ id }}"
style="width: {{ size.width }}; height: {{ size.height }}">
<img src="{{ backgroundImage }}" />
</li>
</script>
<script id="zone-element-tpl" type="text/html">
<div id="{{ id }}" class="zone" data-zone="{{ title }}" style="
top:{{ y }}px;
left:{{ x }}px;
width:{{ width }}px;
height:{{ height }}px;">
<p>{{ title }}</p>
</div>
</script>
<script id="zone-input-tpl" type="text/html">
<div class="zone-row {{ id }}">
<label>Text</label>
<input type="text" class="title" value="{{ title }}" />
<a href="#" class="remove-zone hidden">
<div class="icon remove"></div>
</a>
<div class="layout">
<label>width</label>
<input type="text" class="size width" value="{{ width }}" />
<label>height</label>
<input type="text" class="size height" value="{{ height }}" />
<br />
<label>x</label>
<input type="text" class="coord x" value="{{ x }}" />
<label>y</label>
<input type="text" class="coord y" value="{{ y }}" />
</div>
</div>
</script>
<script id="zone-dropdown-tpl" type="text/html">
<option value="{{ value }}" {{ selected }}>{{ value }}</option>
</script>
<script id="item-input-tpl" type="text/html">
<div class="item">
<div class="row">
<label>Text</label>
<input type="text" class="item-text" value="{{ displayName }}"/>
<label>Zone</label>
<select class="zone-select">{{ dropdown }}</select>
<a href="#" class="remove-item hidden">
<div class="icon remove"></div>
</a>
</div>
<div class="row">
<label>Background image URL (alternative to the text)</label>
<textarea class="background-image">{{ backgroundImage }}</textarea>
</div>
<div class="row">
<label>Success Feedback</label>
<textarea class="success-feedback">{{ feedback.correct }}</textarea>
</div>
<div class="row">
<label>Error Feedback</label>
<textarea class="error-feedback">{{ feedback.incorrect }}</textarea>
</div>
<div class="row">
<label>Width (px - 0 for auto)</label>
<input type="text" class="item-width" value="{{ width }}"></input>
<label>Height (px - 0 for auto)</label>
<input type="text" class="item-height" value="{{ height }}"></input>
</div>
</div>
</script>
mock
nose
coverage
rednose
-e .
{
"zones": [
{
"index": 1,
"width": 200,
"title": "Zone A",
"height": 100,
"y": "200",
"x": "100",
"id": "zone-1"
},
{
"index": 2,
"width": 200,
"title": "Zone B",
"height": 100,
"y": 0,
"x": 0,
"id": "zone-2"
}
],
"items": [
{
"displayName": "A",
"feedback": {
"incorrect": "No A",
"correct": "Yes A"
},
"zone": "Zone A",
"backgroundImage": "",
"id": 0,
"size": {
"width": "190px",
"height": "auto"
}
},
{
"displayName": "B",
"feedback": {
"incorrect": "No B",
"correct": "Yes B"
},
"zone": "Zone B",
"backgroundImage": "",
"id": 1,
"size": {
"width": "190px",
"height": "auto"
}
},
{
"displayName": "X",
"feedback": {
"incorrect": "",
"correct": ""
},
"zone": "none",
"backgroundImage": "",
"id": 2,
"size": {
"width": "100px",
"height": "100px"
}
},
{
"displayName": "",
"feedback": {
"incorrect": "",
"correct": ""
},
"zone": "none",
"backgroundImage": "http://i1.kym-cdn.com/entries/icons/square/000/006/151/tumblr_lltzgnHi5F1qzib3wo1_400.jpg",
"id": 3,
"size": {
"width": "190px",
"height": "auto"
}
}
],
"state": {
"items": {},
"finished": true
},
"feedback": {
"start": "Intro Feed",
"finish": "Final Feed"
},
"targetImg": "http://i0.kym-cdn.com/photos/images/newsfeed/000/030/404/1260585284155.png"
}
import logging
import json
import re
import datetime
import time
import json
from webob import Request
from mock import Mock, patch
from workbench.runtime import WorkbenchRuntime
from xblock.runtime import KvsFieldData, DictKeyValueStore
from nose.tools import (
assert_equals, assert_true, assert_in,
assert_regexp_matches
)
import drag_and_drop_v2
# Silence too verbose Django logging
logging.disable(logging.DEBUG)
def make_request(body):
request = Request.blank('/')
request.body = body.encode('utf-8')
return request
def make_block():
runtime = WorkbenchRuntime()
key_store = DictKeyValueStore()
db_model = KvsFieldData(key_store)
return drag_and_drop_v2.DragAndDropBlock(runtime, db_model, Mock())
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.render('student_view', Mock())
assert_in('<section class="xblock--drag-and-drop">',
student_fragment.content)
assert_in('{{ value }}', student_fragment.content)
assert_in("Test Drag &amp; Drop", student_fragment.content)
assert_in("Question Drag &amp; Drop", student_fragment.content)
assert_in("(5 Points Possible)", 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():
block = make_block()
body = json.dumps({
'display_name': "Test Drag & Drop",
'question_text': "Question Drag & Drop",
'weight': '5',
'data': {
'foo': 1
}
})
res = block.handle('studio_submit', make_request(body))
assert_equals(json.loads(res.body), {'result': 'success'})
assert_equals(block.display_name, "Test Drag & Drop")
assert_equals(block.question_text, "Question Drag & Drop")
assert_equals(block.weight, 5)
assert_equals(block.data, {'foo': 1})
def test_ajax():
assert_equals.__self__.maxDiff = None
block = make_block()
with open('tests/test_data.json') as f:
block.data = json.load(f)
with open('tests/test_get_data.json') as f:
get_data = json.loads(block.handle('get_data', Mock()).body)
assert_equals(json.load(f), get_data)
# Wrong with feedback
data = json.dumps({"val":0,"zone":"Zone B","top":"31px","left":"216px"})
res = json.loads(block.handle('do_attempt', make_request(data)).body)
assert_equals(res, {
"final_feedback": None,
"finished": False,
"correct": False,
"feedback": "No A"
})
with open('tests/test_get_data.json') as f:
get_data = json.loads(block.handle('get_data', Mock()).body)
assert_equals(json.load(f), get_data)
# Wrong without feedback
data = json.dumps({"val":2,"zone":"Zone B","top":"42px","left":"100px"})
res = json.loads(block.handle('do_attempt', make_request(data)).body)
assert_equals(res, {
"final_feedback": None,
"finished": False,
"correct": False,
"feedback": ""
})
with open('tests/test_get_data.json') as f:
get_data = json.loads(block.handle('get_data', Mock()).body)
assert_equals(json.load(f), get_data)
# Correct
data = json.dumps({"val":0,"zone":"Zone A","top":"11px","left":"111px"})
res = json.loads(block.handle('do_attempt', make_request(data)).body)
assert_equals(res, {
"final_feedback": None,
"finished": False,
"correct": True,
"feedback": "Yes A"
})
with open('tests/test_get_data.json') as f:
expected = json.load(f)
expected["state"] = {
"items": {
"0": ["11px", "111px"]
},
"finished": False
}
get_data = json.loads(block.handle('get_data', Mock()).body)
assert_equals(expected, get_data)
# Final
data = json.dumps({"val":1,"zone":"Zone B","top":"22px","left":"222px"})
res = json.loads(block.handle('do_attempt', make_request(data)).body)
assert_equals(res, {
"final_feedback": "Final Feed",
"finished": True,
"correct": True,
"feedback": "Yes B"
})
with open('tests/test_get_data.json') as f:
expected = json.load(f)
expected["state"] = {
"items": {
"0": ["11px", "111px"],
"1": ["22px", "222px"]
},
"finished": True
}
expected["feedback"]["finish"] = "Final Feed"
get_data = json.loads(block.handle('get_data', Mock()).body)
assert_equals(expected, get_data)
{
"zones": [
{
"index": 1,
"title": "Zone A",
"id": "zone-1",
"height": 100,
"y": "200",
"x": "100",
"width": 200
},
{
"index": 2,
"title": "Zone B",
"id": "zone-2",
"height": 100,
"y": 0,
"x": 0,
"width": 200
}
],
"items": [
{
"displayName": "A",
"backgroundImage": "",
"id": 0,
"size": {
"width": "190px",
"height": "auto"
}
},
{
"displayName": "B",
"backgroundImage": "",
"id": 1,
"size": {
"width": "190px",
"height": "auto"
}
},
{
"displayName": "X",
"backgroundImage": "",
"id": 2,
"size": {
"width": "100px",
"height": "100px"
}
},
{
"displayName": "",
"backgroundImage": "http://i1.kym-cdn.com/entries/icons/square/000/006/151/tumblr_lltzgnHi5F1qzib3wo1_400.jpg",
"id": 3,
"size": {
"width": "190px",
"height": "auto"
}
}
],
"state": {
"items": {},
"finished": false
},
"feedback": {
"start": "Intro Feed"
},
"targetImg": "http://i0.kym-cdn.com/photos/images/newsfeed/000/030/404/1260585284155.png"
}
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