Commit 6fe209b0 by Jay Zoldak

Merge pull request #1392 from MITx/tests/diana/update-oe-unit-tests

New tests for OpenEndedGrading
parents bc1452f2 32524727
...@@ -14,7 +14,6 @@ ...@@ -14,7 +14,6 @@
<script type="text/javascript" src="<%= common_js_root %>/vendor/jasmine-jquery.js"></script> <script type="text/javascript" src="<%= common_js_root %>/vendor/jasmine-jquery.js"></script>
<script type="text/javascript" src="<%= common_js_root %>/vendor/jquery.cookie.js"></script> <script type="text/javascript" src="<%= common_js_root %>/vendor/jquery.cookie.js"></script>
<script type="text/javascript" src="<%= common_js_root %>/vendor/CodeMirror/codemirror.js"></script> <script type="text/javascript" src="<%= common_js_root %>/vendor/CodeMirror/codemirror.js"></script>
<script type="text/javascript" src="<%= common_js_root %>/vendor/mathjax-MathJax-c9db6ac/MathJax.js"></script>
<script type="text/javascript" src="<%= common_js_root %>/vendor/tiny_mce/jquery.tinymce.js"></script> <script type="text/javascript" src="<%= common_js_root %>/vendor/tiny_mce/jquery.tinymce.js"></script>
<script type="text/javascript" src="<%= common_js_root %>/vendor/tiny_mce/tiny_mce.js"></script> <script type="text/javascript" src="<%= common_js_root %>/vendor/tiny_mce/tiny_mce.js"></script>
<script type="text/javascript" src="<%= common_js_root %>/vendor/RequireJS.js"></script> <script type="text/javascript" src="<%= common_js_root %>/vendor/RequireJS.js"></script>
......
<section class="course-content">
<section class="xmodule_display xmodule_CombinedOpenEndedModule" data-type="CombinedOpenEnded">
<section id="combined-open-ended" class="combined-open-ended" data-ajax-url="/courses/MITx/6.002x/2012_Fall/modx/i4x://MITx/6.002x/combinedopenended/CombinedOE" data-allow_reset="False" data-state="assessing" data-task-count="2" data-task-number="1">
<h2>Problem 1</h2>
<div class="status-container">
<h4>Status</h4>
<div class="status-elements">
<section id="combined-open-ended-status" class="combined-open-ended-status">
<div class="statusitem" data-status-number="0">
Step 1 (Problem complete) : 1 / 1
<span class="correct" id="status"></span>
</div>
<div class="statusitem statusitem-current" data-status-number="1">
Step 2 (Being scored) : None / 1
<span class="grading" id="status"></span>
</div>
</section>
</div>
</div>
<div class="item-container">
<h4>Problem</h4>
<div class="problem-container">
<div class="item"><section id="openended_open_ended" class="open-ended-child" data-state="assessing" data-child-type="openended"><div class="error"></div>
<div class="prompt">
Some prompt.
</div>
<textarea rows="30" cols="80" name="answer" class="answer short-form-response" id="input_open_ended" disabled="disabled">Test submission. Yaaaaaaay!</textarea><div class="message-wrapper"></div>
<div class="grader-status">
<span class="grading" id="status_open_ended">Submitted for grading.</span>
</div>
<input type="button" value="Submit assessment" class="submit-button" name="show" style="display: none;"><input name="skip" class="skip-button" type="button" value="Skip Post-Assessment" style="display: none;"><div class="open-ended-action"></div>
<span id="answer_open_ended"></span>
</section></div>
</div>
<input type="button" value="Reset" class="reset-button" name="reset" style="display: none;">
<input type="button" value="Next Step" class="next-step-button" name="reset" style="display: none;">
</div>
<a name="results">
<div class="result-container">
</div>
</a></section><a name="results">
</a></section><a name="results">
</a><div><a name="results">
</a><a href="https://github.com/MITx/content-mit-6002x/tree/master/combinedopenended/CombinedOE.xml">Edit</a> /
<a href="#i4x_MITx_6_002x_combinedopenended_CombinedOE_xqa-modal" onclick="javascript:getlog('i4x_MITx_6_002x_combinedopenended_CombinedOE', {
'location': 'i4x://MITx/6.002x/combinedopenended/CombinedOE',
'xqa_key': 'KUBrWtK3RAaBALLbccHrXeD3RHOpmZ2A',
'category': 'CombinedOpenEndedModule',
'user': 'blah'
})" id="i4x_MITx_6_002x_combinedopenended_CombinedOE_xqa_log">QA</a>
</div>
<div><a href="#i4x_MITx_6_002x_combinedopenended_CombinedOE_debug" id="i4x_MITx_6_002x_combinedopenended_CombinedOE_trig">Staff Debug Info</a></div>
<section id="i4x_MITx_6_002x_combinedopenended_CombinedOE_xqa-modal" class="modal xqa-modal" style="width:80%; left:20%; height:80%; overflow:auto">
<div class="inner-wrapper">
<header>
<h2>edX Content Quality Assessment</h2>
</header>
<form id="i4x_MITx_6_002x_combinedopenended_CombinedOE_xqa_form" class="xqa_form">
<label>Comment</label>
<input id="i4x_MITx_6_002x_combinedopenended_CombinedOE_xqa_entry" type="text" placeholder="comment">
<label>Tag</label>
<span style="color:black;vertical-align: -10pt">Optional tag (eg "done" or "broken"):&nbsp; </span>
<input id="i4x_MITx_6_002x_combinedopenended_CombinedOE_xqa_tag" type="text" placeholder="tag" style="width:80px;display:inline">
<div class="submit">
<button name="submit" type="submit">Add comment</button>
</div>
<hr>
<div id="i4x_MITx_6_002x_combinedopenended_CombinedOE_xqa_log_data"></div>
</form>
</div>
</section>
<section class="modal staff-modal" id="i4x_MITx_6_002x_combinedopenended_CombinedOE_debug" style="width:80%; left:20%; height:80%; overflow:auto;">
<div class="inner-wrapper" style="color:black">
<header>
<h2>Staff Debug</h2>
</header>
<div class="staff_info" style="display:block">
is_released = <font color="red">Yes!</font>
location = i4x://MITx/6.002x/combinedopenended/CombinedOE
github = <a href="https://github.com/MITx/content-mit-6002x/tree/master/combinedopenended/CombinedOE.xml">https://github.com/MITx/content-mit-6002x/tree/master/combinedopenended/CombinedOE.xml</a>
definition = <pre>None</pre>
metadata = {
"showanswer": "attempted",
"display_name": "Problem 1",
"graceperiod": "1 day 12 hours 59 minutes 59 seconds",
"xqa_key": "KUBrWtK3RAaBALLbccHrXeD3RHOpmZ2A",
"rerandomize": "never",
"start": "2012-09-05T12:00",
"attempts": "10000",
"data_dir": "content-mit-6002x",
"max_score": "1"
}
category = CombinedOpenEndedModule
</div>
</div>
</section>
<div id="i4x_MITx_6_002x_combinedopenended_CombinedOE_setup"></div>
</section>
describe 'CombinedOpenEnded', ->
beforeEach ->
spyOn Logger, 'log'
# load up some fixtures
loadFixtures 'combined-open-ended.html'
jasmine.Clock.useMock()
@element = $('.course-content')
describe 'constructor', ->
beforeEach ->
spyOn(Collapsible, 'setCollapsibles')
@combined = new CombinedOpenEnded @element
it 'set the element', ->
expect(@combined.element).toEqual @element
it 'get the correct values from data fields', ->
expect(@combined.ajax_url).toEqual '/courses/MITx/6.002x/2012_Fall/modx/i4x://MITx/6.002x/combinedopenended/CombinedOE'
expect(@combined.state).toEqual 'assessing'
expect(@combined.task_count).toEqual 2
expect(@combined.task_number).toEqual 1
it 'subelements are made collapsible', ->
expect(Collapsible.setCollapsibles).toHaveBeenCalled()
describe 'poll', ->
beforeEach =>
# setup the spies
@combined = new CombinedOpenEnded @element
spyOn(@combined, 'reload').andCallFake -> return 0
window.setTimeout = jasmine.createSpy().andCallFake (callback, timeout) -> return 5
it 'polls at the correct intervals', =>
fakeResponseContinue = state: 'not done'
spyOn($, 'postWithPrefix').andCallFake (url, callback) -> callback(fakeResponseContinue)
@combined.poll()
expect(window.setTimeout).toHaveBeenCalledWith(@combined.poll, 10000)
expect(window.queuePollerID).toBe(5)
it 'polling stops properly', =>
fakeResponseDone = state: "done"
spyOn($, 'postWithPrefix').andCallFake (url, callback) -> callback(fakeResponseDone)
@combined.poll()
expect(window.queuePollerID).toBeUndefined()
expect(window.setTimeout).not.toHaveBeenCalled()
expect(@combined.reload).toHaveBeenCalled()
describe 'rebind', ->
beforeEach ->
@combined = new CombinedOpenEnded @element
spyOn(@combined, 'queueing').andCallFake -> return 0
spyOn(@combined, 'skip_post_assessment').andCallFake -> return 0
window.setTimeout = jasmine.createSpy().andCallFake (callback, timeout) -> return 5
it 'when our child is in an assessing state', ->
@combined.child_state = 'assessing'
@combined.rebind()
expect(@combined.answer_area.attr("disabled")).toBe("disabled")
expect(@combined.submit_button.val()).toBe("Submit assessment")
expect(@combined.queueing).toHaveBeenCalled()
it 'when our child state is initial', ->
@combined.child_state = 'initial'
@combined.rebind()
expect(@combined.answer_area.attr("disabled")).toBeUndefined()
expect(@combined.submit_button.val()).toBe("Submit")
it 'when our child state is post_assessment', ->
@combined.child_state = 'post_assessment'
@combined.rebind()
expect(@combined.answer_area.attr("disabled")).toBe("disabled")
expect(@combined.submit_button.val()).toBe("Submit post-assessment")
it 'when our child state is done', ->
spyOn(@combined, 'next_problem').andCallFake ->
@combined.child_state = 'done'
@combined.rebind()
expect(@combined.answer_area.attr("disabled")).toBe("disabled")
expect(@combined.next_problem).toHaveBeenCalled()
describe 'next_problem', ->
beforeEach ->
@combined = new CombinedOpenEnded @element
@combined.child_state = 'done'
it 'handling a successful call', ->
fakeResponse =
success: true
html: "dummy html"
allow_reset: false
spyOn($, 'postWithPrefix').andCallFake (url, val, callback) -> callback(fakeResponse)
spyOn(@combined, 'reinitialize')
spyOn(@combined, 'rebind')
@combined.next_problem()
expect($.postWithPrefix).toHaveBeenCalled()
expect(@combined.reinitialize).toHaveBeenCalledWith(@combined.element)
expect(@combined.rebind).toHaveBeenCalled()
expect(@combined.answer_area.val()).toBe('')
expect(@combined.child_state).toBe('initial')
it 'handling an unsuccessful call', ->
fakeResponse =
success: false
error: 'This is an error'
spyOn($, 'postWithPrefix').andCallFake (url, val, callback) -> callback(fakeResponse)
@combined.next_problem()
expect(@combined.errors_area.html()).toBe(fakeResponse.error)
...@@ -64,7 +64,6 @@ jasmine.stubVideoPlayer = (context, enableParts, createPlayer=true) -> ...@@ -64,7 +64,6 @@ jasmine.stubVideoPlayer = (context, enableParts, createPlayer=true) ->
if createPlayer if createPlayer
return new VideoPlayer(video: context.video) return new VideoPlayer(video: context.video)
spyOn(window, 'onunload')
# Stub jQuery.cookie # Stub jQuery.cookie
$.cookie = jasmine.createSpy('jQuery.cookie').andReturn '1.0' $.cookie = jasmine.createSpy('jQuery.cookie').andReturn '1.0'
......
...@@ -7,7 +7,6 @@ class @CombinedOpenEnded ...@@ -7,7 +7,6 @@ class @CombinedOpenEnded
@wrapper=$(element).find('section.xmodule_CombinedOpenEndedModule') @wrapper=$(element).find('section.xmodule_CombinedOpenEndedModule')
@el = $(element).find('section.combined-open-ended') @el = $(element).find('section.combined-open-ended')
@combined_open_ended=$(element).find('section.combined-open-ended') @combined_open_ended=$(element).find('section.combined-open-ended')
@id = @el.data('id')
@ajax_url = @el.data('ajax-url') @ajax_url = @el.data('ajax-url')
@state = @el.data('state') @state = @el.data('state')
@task_count = @el.data('task-count') @task_count = @el.data('task-count')
...@@ -282,6 +281,10 @@ class @CombinedOpenEnded ...@@ -282,6 +281,10 @@ class @CombinedOpenEnded
$.postWithPrefix "#{@ajax_url}/check_for_score", (response) => $.postWithPrefix "#{@ajax_url}/check_for_score", (response) =>
if response.state == "done" or response.state=="post_assessment" if response.state == "done" or response.state=="post_assessment"
delete window.queuePollerID delete window.queuePollerID
location.reload() @reload()
else else
window.queuePollerID = window.setTimeout(@poll, 10000) window.queuePollerID = window.setTimeout(@poll, 10000)
# wrap this so that it can be mocked
reload: ->
location.reload()
...@@ -258,7 +258,7 @@ class OpenEndedModule(openendedchild.OpenEndedChild): ...@@ -258,7 +258,7 @@ class OpenEndedModule(openendedchild.OpenEndedChild):
""" """
new_score_msg = self._parse_score_msg(score_msg, system) new_score_msg = self._parse_score_msg(score_msg, system)
if not new_score_msg['valid']: if not new_score_msg['valid']:
score_msg['feedback'] = 'Invalid grader reply. Please contact the course staff.' new_score_msg['feedback'] = 'Invalid grader reply. Please contact the course staff.'
self.record_latest_score(new_score_msg['score']) self.record_latest_score(new_score_msg['score'])
self.record_latest_post_assessment(score_msg) self.record_latest_post_assessment(score_msg)
...@@ -406,6 +406,13 @@ class OpenEndedModule(openendedchild.OpenEndedChild): ...@@ -406,6 +406,13 @@ class OpenEndedModule(openendedchild.OpenEndedChild):
'score': Numeric value (floating point is okay) to assign to answer 'score': Numeric value (floating point is okay) to assign to answer
'msg': grader_msg 'msg': grader_msg
'feedback' : feedback from grader 'feedback' : feedback from grader
'grader_type': what type of grader resulted in this score
'grader_id': id of the grader
'submission_id' : id of the submission
'success': whether or not this submission was successful
'rubric_scores': a list of rubric scores
'rubric_scores_complete': boolean if rubric scores are complete
'rubric_xml': the xml of the rubric in string format
} }
Returns (valid_score_msg, correct, score, msg): Returns (valid_score_msg, correct, score, msg):
......
...@@ -113,7 +113,7 @@ class OpenEndedChild(object): ...@@ -113,7 +113,7 @@ class OpenEndedChild(object):
pass pass
def latest_answer(self): def latest_answer(self):
"""None if not available""" """Empty string if not available"""
if not self.history: if not self.history:
return "" return ""
return self.history[-1].get('answer', "") return self.history[-1].get('answer', "")
...@@ -125,7 +125,7 @@ class OpenEndedChild(object): ...@@ -125,7 +125,7 @@ class OpenEndedChild(object):
return self.history[-1].get('score') return self.history[-1].get('score')
def latest_post_assessment(self, system): def latest_post_assessment(self, system):
"""None if not available""" """Empty string if not available"""
if not self.history: if not self.history:
return "" return ""
return self.history[-1].get('post_assessment', "") return self.history[-1].get('post_assessment', "")
......
...@@ -10,8 +10,16 @@ from . import test_system ...@@ -10,8 +10,16 @@ from . import test_system
class SelfAssessmentTest(unittest.TestCase): class SelfAssessmentTest(unittest.TestCase):
definition = {'rubric': 'A rubric', rubric = '''<rubric><rubric>
'prompt': 'Who?', <category>
<description>Response Quality</description>
<option>The response is not a satisfactory answer to the question. It either fails to address the question or does so in a limited way, with no evidence of higher-order thinking.</option>
</category>
</rubric></rubric>'''
prompt = etree.XML("<prompt>This is sample prompt text.</prompt>")
definition = {'rubric': rubric,
'prompt': prompt,
'submitmessage': 'Shall we submit now?', 'submitmessage': 'Shall we submit now?',
'hintprompt': 'Consider this...', 'hintprompt': 'Consider this...',
} }
...@@ -23,47 +31,46 @@ class SelfAssessmentTest(unittest.TestCase): ...@@ -23,47 +31,46 @@ class SelfAssessmentTest(unittest.TestCase):
descriptor = Mock() descriptor = Mock()
def test_import(self): def setUp(self):
state = json.dumps({'student_answers': ["Answer 1", "answer 2", "answer 3"], state = json.dumps({'student_answers': ["Answer 1", "answer 2", "answer 3"],
'scores': [0, 1], 'scores': [0, 1],
'hints': ['o hai'], 'hints': ['o hai'],
'state': SelfAssessmentModule.INITIAL, 'state': SelfAssessmentModule.INITIAL,
'attempts': 2}) 'attempts': 2})
rubric = '''<rubric><rubric>
<category>
<description>Response Quality</description>
<option>The response is not a satisfactory answer to the question. It either fails to address the question or does so in a limited way, with no evidence of higher-order thinking.</option>
</category>
</rubric></rubric>'''
prompt = etree.XML("<prompt>Text</prompt>")
static_data = { static_data = {
'max_attempts': 10, 'max_attempts': 10,
'rubric': etree.XML(rubric), 'rubric': etree.XML(self.rubric),
'prompt': prompt, 'prompt': self.prompt,
'max_score': 1, 'max_score': 1,
'display_name': "Name" 'display_name': "Name"
} }
module = SelfAssessmentModule(test_system, self.location, self.module = SelfAssessmentModule(test_system, self.location,
self.definition, self.descriptor, self.definition, self.descriptor,
static_data, state, metadata=self.metadata) static_data, state, metadata=self.metadata)
self.assertEqual(module.get_score()['score'], 0) def test_get_html(self):
html = self.module.get_html(test_system)
self.assertTrue("This is sample prompt text" in html)
def test_self_assessment_flow(self):
self.assertEqual(self.module.get_score()['score'], 0)
module.save_answer({'student_answer': "I am an answer"}, test_system) self.module.save_answer({'student_answer': "I am an answer"}, test_system)
self.assertEqual(module.state, module.ASSESSING) self.assertEqual(self.module.state, self.module.ASSESSING)
module.save_assessment({'assessment': '0'}, test_system) self.module.save_assessment({'assessment': '0'}, test_system)
self.assertEqual(module.state, module.DONE) self.assertEqual(self.module.state, self.module.DONE)
d = module.reset({})
d = self.module.reset({})
self.assertTrue(d['success']) self.assertTrue(d['success'])
self.assertEqual(module.state, module.INITIAL) self.assertEqual(self.module.state, self.module.INITIAL)
# if we now assess as right, skip the REQUEST_HINT state # if we now assess as right, skip the REQUEST_HINT state
module.save_answer({'student_answer': 'answer 4'}, test_system) self.module.save_answer({'student_answer': 'answer 4'}, test_system)
module.save_assessment({'assessment': '1'}, test_system) self.module.save_assessment({'assessment': '1'}, test_system)
self.assertEqual(module.state, module.DONE) self.assertEqual(self.module.state, self.module.DONE)
...@@ -31,6 +31,15 @@ This is a mock peer grading service that can be used for unit tests ...@@ -31,6 +31,15 @@ This is a mock peer grading service that can be used for unit tests
without making actual service calls to the grading controller without making actual service calls to the grading controller
""" """
class MockPeerGradingService(object): class MockPeerGradingService(object):
# TODO: get this rubric parsed and working
rubric = """<rubric>
<category>
<description>Description</description>
<option>First option</option>
<option>Second option</option>
</category>
</rubric>"""
def get_next_submission(self, problem_location, grader_id): def get_next_submission(self, problem_location, grader_id):
return json.dumps({'success': True, return json.dumps({'success': True,
'submission_id':1, 'submission_id':1,
...@@ -41,7 +50,7 @@ class MockPeerGradingService(object): ...@@ -41,7 +50,7 @@ class MockPeerGradingService(object):
'max_score': 4}) 'max_score': 4})
def save_grade(self, location, grader_id, submission_id, def save_grade(self, location, grader_id, submission_id,
score, feedback, submission_key): score, feedback, submission_key, rubric_scores):
return json.dumps({'success': True}) return json.dumps({'success': True})
def is_student_calibrated(self, problem_location, grader_id): def is_student_calibrated(self, problem_location, grader_id):
...@@ -57,16 +66,16 @@ class MockPeerGradingService(object): ...@@ -57,16 +66,16 @@ class MockPeerGradingService(object):
'max_score': 4}) 'max_score': 4})
def save_calibration_essay(self, problem_location, grader_id, def save_calibration_essay(self, problem_location, grader_id,
calibration_essay_id, submission_key, score, feedback): calibration_essay_id, submission_key, score, feedback, rubric_scores):
return {'success': True, 'actual_score': 2} return json.dumps({'success': True, 'actual_score': 2})
def get_problem_list(self, course_id, grader_id): def get_problem_list(self, course_id, grader_id):
return json.dumps({'success': True, return json.dumps({'success': True,
'problem_list': [ 'problem_list': [
json.dumps({'location': 'i4x://MITx/3.091x/problem/open_ended_demo1', json.dumps({'location': 'i4x://MITx/3.091x/problem/open_ended_demo1',
'problem_name': "Problem 1", 'num_graded': 3, 'num_pending': 5}), 'problem_name': "Problem 1", 'num_graded': 3, 'num_pending': 5, 'num_required': 7}),
json.dumps({'location': 'i4x://MITx/3.091x/problem/open_ended_demo2', json.dumps({'location': 'i4x://MITx/3.091x/problem/open_ended_demo2',
'problem_name': "Problem 2", 'num_graded': 1, 'num_pending': 5}) 'problem_name': "Problem 2", 'num_graded': 1, 'num_pending': 5, 'num_required': 8})
]}) ]})
class PeerGradingService(GradingService): class PeerGradingService(GradingService):
......
...@@ -6,6 +6,7 @@ django-admin.py test --settings=lms.envs.test --pythonpath=. lms/djangoapps/open ...@@ -6,6 +6,7 @@ django-admin.py test --settings=lms.envs.test --pythonpath=. lms/djangoapps/open
from django.test import TestCase from django.test import TestCase
from open_ended_grading import staff_grading_service from open_ended_grading import staff_grading_service
from open_ended_grading import peer_grading_service
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from django.contrib.auth.models import Group from django.contrib.auth.models import Group
...@@ -17,9 +18,10 @@ from nose import SkipTest ...@@ -17,9 +18,10 @@ from nose import SkipTest
from mock import patch, Mock from mock import patch, Mock
import json import json
import logging
log = logging.getLogger(__name__)
from override_settings import override_settings from override_settings import override_settings
_mock_service = staff_grading_service.MockStaffGradingService()
@override_settings(MODULESTORE=ct.TEST_DATA_XML_MODULESTORE) @override_settings(MODULESTORE=ct.TEST_DATA_XML_MODULESTORE)
class TestStaffGradingService(ct.PageLoader): class TestStaffGradingService(ct.PageLoader):
...@@ -111,3 +113,144 @@ class TestStaffGradingService(ct.PageLoader): ...@@ -111,3 +113,144 @@ class TestStaffGradingService(ct.PageLoader):
d = json.loads(r.content) d = json.loads(r.content)
self.assertTrue(d['success'], str(d)) self.assertTrue(d['success'], str(d))
self.assertIsNotNone(d['problem_list']) self.assertIsNotNone(d['problem_list'])
@override_settings(MODULESTORE=ct.TEST_DATA_XML_MODULESTORE)
class TestPeerGradingService(ct.PageLoader):
'''
Check that staff grading service proxy works. Basically just checking the
access control and error handling logic -- all the actual work is on the
backend.
'''
def setUp(self):
xmodule.modulestore.django._MODULESTORES = {}
self.student = 'view@test.com'
self.instructor = 'view2@test.com'
self.password = 'foo'
self.location = 'TestLocation'
self.create_account('u1', self.student, self.password)
self.create_account('u2', self.instructor, self.password)
self.activate_user(self.student)
self.activate_user(self.instructor)
self.course_id = "edX/toy/2012_Fall"
self.toy = modulestore().get_course(self.course_id)
self.mock_service = peer_grading_service.peer_grading_service()
self.logout()
def test_get_next_submission_success(self):
self.login(self.student, self.password)
url = reverse('peer_grading_get_next_submission', kwargs={'course_id': self.course_id})
data = {'location': self.location}
r = self.check_for_post_code(200, url, data)
d = json.loads(r.content)
self.assertTrue(d['success'])
self.assertIsNotNone(d['submission_id'])
self.assertIsNotNone(d['prompt'])
self.assertIsNotNone(d['submission_key'])
self.assertIsNotNone(d['max_score'])
def test_get_next_submission_missing_location(self):
self.login(self.student, self.password)
url = reverse('peer_grading_get_next_submission', kwargs={'course_id': self.course_id})
data = {}
r = self.check_for_post_code(200, url, data)
d = json.loads(r.content)
self.assertFalse(d['success'])
self.assertEqual(d['error'], "Missing required keys: location")
def test_save_grade_success(self):
self.login(self.student, self.password)
url = reverse('peer_grading_save_grade', kwargs={'course_id': self.course_id})
data = {'location': self.location,
'submission_id': '1',
'submission_key': 'fake key',
'score': '2',
'feedback': 'This is feedback',
'rubric_scores[]': [1, 2]}
r = self.check_for_post_code(200, url, data)
d = json.loads(r.content)
self.assertTrue(d['success'])
def test_save_grade_missing_keys(self):
self.login(self.student, self.password)
url = reverse('peer_grading_save_grade', kwargs={'course_id': self.course_id})
data = {}
r = self.check_for_post_code(200, url, data)
d = json.loads(r.content)
self.assertFalse(d['success'])
self.assertTrue(d['error'].find('Missing required keys:') > -1)
def test_is_calibrated_success(self):
self.login(self.student, self.password)
url = reverse('peer_grading_is_student_calibrated', kwargs={'course_id': self.course_id})
data = {'location': self.location}
r = self.check_for_post_code(200, url, data)
d = json.loads(r.content)
self.assertTrue(d['success'])
self.assertTrue('calibrated' in d)
def test_is_calibrated_failure(self):
self.login(self.student, self.password)
url = reverse('peer_grading_is_student_calibrated', kwargs={'course_id': self.course_id})
data = {}
r = self.check_for_post_code(200, url, data)
d = json.loads(r.content)
self.assertFalse(d['success'])
self.assertFalse('calibrated' in d)
def test_show_calibration_essay_success(self):
self.login(self.student, self.password)
url = reverse('peer_grading_show_calibration_essay', kwargs={'course_id': self.course_id})
data = {'location': self.location}
r = self.check_for_post_code(200, url, data)
d = json.loads(r.content)
self.assertTrue(d['success'])
self.assertIsNotNone(d['submission_id'])
self.assertIsNotNone(d['prompt'])
self.assertIsNotNone(d['submission_key'])
self.assertIsNotNone(d['max_score'])
def test_show_calibration_essay_missing_key(self):
self.login(self.student, self.password)
url = reverse('peer_grading_show_calibration_essay', kwargs={'course_id': self.course_id})
data = {}
r = self.check_for_post_code(200, url, data)
d = json.loads(r.content)
self.assertFalse(d['success'])
self.assertEqual(d['error'], "Missing required keys: location")
def test_save_calibration_essay_success(self):
self.login(self.student, self.password)
url = reverse('peer_grading_save_calibration_essay', kwargs={'course_id': self.course_id})
data = {'location': self.location,
'submission_id': '1',
'submission_key': 'fake key',
'score': '2',
'feedback': 'This is feedback',
'rubric_scores[]': [1, 2]}
r = self.check_for_post_code(200, url, data)
d = json.loads(r.content)
self.assertTrue(d['success'])
self.assertTrue('actual_score' in d)
def test_save_calibration_essay_missing_keys(self):
self.login(self.student, self.password)
url = reverse('peer_grading_save_calibration_essay', kwargs={'course_id': self.course_id})
data = {}
r = self.check_for_post_code(200, url, data)
d = json.loads(r.content)
self.assertFalse(d['success'])
self.assertTrue(d['error'].find('Missing required keys:') > -1)
self.assertFalse('actual_score' in d)
<section class="container">
<div class="staff-grading" data-ajax_url="${ajax_url}">
<h1>Staff grading</h1>
<div class="breadcrumbs">
</div>
<div class="error-container">
</div>
<div class="message-container">
</div>
<! -- Problem List View -->
<section class="problem-list-container">
<h2>Instructions</h2>
<div class="instructions">
<p>This is the list of problems that current need to be graded in order to train the machine learning models. Each problem needs to be trained separately, and we have indicated the number of student submissions that need to be graded in order for a model to be generated. You can grade more than the minimum required number of submissions--this will improve the accuracy of machine learning, though with diminishing returns. You can see the current accuracy of machine learning while grading.</p>
</div>
<h2>Problem List</h2>
<table class="problem-list">
</table>
</section>
<!-- Grading View -->
<section class="prompt-wrapper">
<h2 class="prompt-name"></h2>
<div class="meta-info-wrapper">
<h3>Problem Information</h3>
<div class="problem-meta-info-container">
</div>
<h3>Maching Learning Information</h3>
<div class="ml-error-info-container">
</div>
</div>
<div class="prompt-information-container">
<h3>Question</h3>
<div class="prompt-container">
</div>
</div>
</section>
<div class="action-button">
<input type=button value="Submit" class="action-button" name="show" />
</div>
<section class="grading-wrapper">
<h2>Grading</h2>
<div class="grading-container">
<div class="submission-wrapper">
<h3>Student Submission</h3>
<div class="submission-container">
</div>
</div>
<div class="evaluation">
<p class="score-selection-container">
</p>
<p class="grade-selection-container">
</p>
<textarea name="feedback" placeholder="Feedback for student (optional)"
class="feedback-area" cols="70" ></textarea>
</div>
<div class="submission">
<input type="button" value="Submit" class="submit-button" name="show"/>
<input type="button" value="Skip" class="skip-button" name="skip"/>
</div>
</div>
</div>
</section>
...@@ -10,19 +10,6 @@ describe 'Courseware', -> ...@@ -10,19 +10,6 @@ describe 'Courseware', ->
Courseware.start() Courseware.start()
expect(Logger.bind).toHaveBeenCalled() expect(Logger.bind).toHaveBeenCalled()
describe 'bind', ->
beforeEach ->
@courseware = new Courseware
setFixtures """
<div class="course-content">
<div class="sequence"></div>
</div>
"""
it 'binds the content change event', ->
@courseware.bind()
expect($('.course-content .sequence')).toHandleWith 'contentChanged', @courseware.render
describe 'render', -> describe 'render', ->
beforeEach -> beforeEach ->
jasmine.stubRequests() jasmine.stubRequests()
...@@ -30,6 +17,7 @@ describe 'Courseware', -> ...@@ -30,6 +17,7 @@ describe 'Courseware', ->
spyOn(window, 'Histogram') spyOn(window, 'Histogram')
spyOn(window, 'Problem') spyOn(window, 'Problem')
spyOn(window, 'Video') spyOn(window, 'Video')
spyOn(XModule, 'loadModules')
setFixtures """ setFixtures """
<div class="course-content"> <div class="course-content">
<div id="video_1" class="video" data-streams="1.0:abc1234"></div> <div id="video_1" class="video" data-streams="1.0:abc1234"></div>
...@@ -41,12 +29,8 @@ describe 'Courseware', -> ...@@ -41,12 +29,8 @@ describe 'Courseware', ->
""" """
@courseware.render() @courseware.render()
it 'detect the video elements and convert them', -> it 'ensure that the XModules have been loaded', ->
expect(window.Video).toHaveBeenCalledWith('1', '1.0:abc1234') expect(XModule.loadModules).toHaveBeenCalled()
expect(window.Video).toHaveBeenCalledWith('2', '1.0:def5678')
it 'detect the problem element and convert it', ->
expect(window.Problem).toHaveBeenCalledWith(3, 'problem_3', '/example/url/')
it 'detect the histrogram element and convert it', -> it 'detect the histrogram element and convert it', ->
expect(window.Histogram).toHaveBeenCalledWith('3', [[0, 1]]) expect(window.Histogram).toHaveBeenCalledWith('3', [[0, 1]])
...@@ -16,6 +16,7 @@ describe 'Navigation', -> ...@@ -16,6 +16,7 @@ describe 'Navigation', ->
active: 1 active: 1
header: 'h3' header: 'h3'
autoHeight: false autoHeight: false
heightStyle: 'content'
describe 'when there is no active section', -> describe 'when there is no active section', ->
beforeEach -> beforeEach ->
...@@ -23,11 +24,12 @@ describe 'Navigation', -> ...@@ -23,11 +24,12 @@ describe 'Navigation', ->
$('#accordion').append('<ul><li></li></ul><ul><li></li></ul>') $('#accordion').append('<ul><li></li></ul><ul><li></li></ul>')
new Navigation new Navigation
it 'activate the accordian with section 1 as active', -> it 'activate the accordian with no section as active', ->
expect($('#accordion').accordion).toHaveBeenCalledWith expect($('#accordion').accordion).toHaveBeenCalledWith
active: 1 active: 0
header: 'h3' header: 'h3'
autoHeight: false autoHeight: false
heightStyle: 'content'
it 'binds the accordionchange event', -> it 'binds the accordionchange event', ->
Navigation.bind() Navigation.bind()
......
describe 'StaffGrading', ->
beforeEach ->
spyOn Logger, 'log'
@mockBackend = new StaffGradingBackend('url', true)
describe 'constructor', ->
beforeEach ->
@staff_grading = new StaffGrading(@mockBackend)
it 'we are originally in the list view', ->
expect(@staff_grading.list_view).toBe(true)
...@@ -9,9 +9,13 @@ state_graded = "graded" ...@@ -9,9 +9,13 @@ state_graded = "graded"
state_no_data = "no_data" state_no_data = "no_data"
state_error = "error" state_error = "error"
class StaffGradingBackend class @StaffGradingBackend
constructor: (ajax_url, mock_backend) -> constructor: (ajax_url, mock_backend) ->
@ajax_url = ajax_url @ajax_url = ajax_url
# prevent this from trying to make requests when we don't have
# a proper url
if !ajax_url
mock_backend = true
@mock_backend = mock_backend @mock_backend = mock_backend
if @mock_backend if @mock_backend
@mock_cnt = 0 @mock_cnt = 0
...@@ -142,7 +146,7 @@ The standard chunk of Lorem Ipsum used since the 1500s is reproduced below for t ...@@ -142,7 +146,7 @@ The standard chunk of Lorem Ipsum used since the 1500s is reproduced below for t
.error => callback({success: false, error: "Error occured while performing this operation"}) .error => callback({success: false, error: "Error occured while performing this operation"})
class StaffGrading class @StaffGrading
constructor: (backend) -> constructor: (backend) ->
@backend = backend @backend = backend
......
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