Commit 156b43ec by cahrens

Allow passing through title to iFrame.

Also changes section to div.

TNL-6044
parent d2b6c9eb
......@@ -592,8 +592,12 @@ class JSInput(InputTypeBase):
# set state
Attribute('width', "400"), # iframe width
Attribute('height', "300"), # iframe height
Attribute('sop', None) # SOP will be relaxed only if this
# attribute is set to false.
# Title for the iframe, which should be supplied by the author of the problem. Not translated
# because we are in a class method and therefore do not have access to capa_system.i18n.
# Note that the default "display name" for the problem is also not translated.
Attribute('title', "Problem Remote Content"),
# SOP will be relaxed only if this attribute is set to false.
Attribute('sop', None)
]
def _extra_context(self):
......
<%page expression_filter="h"/>
<%! from openedx.core.djangolib.markup import HTML %>
<section id="inputtype_${id}" class="jsinput"
<div id="inputtype_${id}" class="jsinput"
data="${gradefn}"
% if saved_state:
data-stored="${saved_state|x}"
data-stored="${saved_state}"
% endif
% if initial_state:
data-initial-state="${initial_state|x}"
data-initial-state="${initial_state}"
% endif
% if get_statefn:
data-getstate="${get_statefn}"
......@@ -33,10 +34,11 @@
src="${html_file}"
height="${height}"
width="${width}"
title="${title}"
/>
<input type="hidden" name="input_${id}" id="input_${id}"
waitfor=""
value="${value|h}"/>
value="${value}"/>
<br/>
<p id="answer_${id}" class="answer"></p>
......@@ -54,4 +56,4 @@
% if msg:
<span class="message" tabindex="-1">${HTML(msg)}</span>
% endif
</section>
</div>
......@@ -608,6 +608,18 @@ class ImageResponseXMLFactory(ResponseXMLFactory):
return input_element
class JSInputXMLFactory(CustomResponseXMLFactory):
"""
Factory for producing <jsinput> XML.
Note that this factory currently does not create a functioning problem.
It will only create an empty iframe.
"""
def create_input_element(self, **kwargs):
""" Create the <jsinput> element """
return etree.Element("jsinput")
class MultipleChoiceResponseXMLFactory(ResponseXMLFactory):
""" Factory for producing <multiplechoiceresponse> XML """
......
......@@ -162,6 +162,89 @@ class ChoiceGroupTest(unittest.TestCase):
self.check_group('checkboxgroup', 'checkbox', '[]')
class JSInputTest(unittest.TestCase):
"""
Test context variables passed into the jsinput template.
"""
def test_rendering_default_values(self):
"""
Tests the default values passed through to render.
"""
xml_str = '<jsinput id="prob_1_2"/>'
expected = {
'html_file': None,
'gradefn': "gradefn",
'get_statefn': None,
'set_statefn': None,
'initial_state': None,
'width': "400",
'height': "300",
'title': "Problem Remote Content",
'sop': None
}
self._render_context_test(xml_str, expected)
def test_rendering_provided_values(self):
"""
Tests that values provided by course authors are passed through to render.
"""
xml_str = """
<jsinput id="prob_1_2"
gradefn="WebGLDemo.getGrade" get_statefn="WebGLDemo.getState" set_statefn="WebGLDemo.setState"
initial_state='{"selectedObjects":{"cube":true,"cylinder":false}}'
width="1000" height="1200"
html_file="https://studio.edx.org/c4x/edX/DemoX/asset/webGLDemo.html"
sop="false" title="Awesome and fun!"
/>
"""
expected = {
'html_file': "https://studio.edx.org/c4x/edX/DemoX/asset/webGLDemo.html",
'gradefn': "WebGLDemo.getGrade",
'get_statefn': "WebGLDemo.getState",
'set_statefn': "WebGLDemo.setState",
'initial_state': '{"selectedObjects":{"cube":true,"cylinder":false}}',
'width': "1000",
'height': "1200",
'title': "Awesome and fun!",
'sop': 'false'
}
self._render_context_test(xml_str, expected)
def _render_context_test(self, xml_str, expected_context):
"""
Helper method for testing context based on the provided XML string.
"""
element = etree.fromstring(xml_str)
state = {
'value': 103,
'response_data': RESPONSE_DATA
}
the_input = lookup_tag('jsinput')(test_capa_system(), element, state)
context = the_input._get_render_context() # pylint: disable=protected-access
full_expected_context = {
'STATIC_URL': '/dummy-static/',
'id': 'prob_1_2',
'status': inputtypes.Status('unanswered'),
'describedby_html': DESCRIBEDBY.format(status_id='prob_1_2'),
'msg': "",
'params': None,
'jschannel_loader': '/dummy-static/js/capa/src/jschannel.js',
'jsinput_loader': '/dummy-static/js/capa/src/jsinput.js',
'saved_state': 103,
'response_data': RESPONSE_DATA,
'value': 103
}
full_expected_context.update(expected_context)
self.assertEqual(full_expected_context, context)
class TextLineTest(unittest.TestCase):
'''
Check that textline inputs work, with and without math.
......
......@@ -66,6 +66,7 @@ data: |
width="400"
height="400"
html_file="https://studio.edx.org/c4x/edX/DemoX/asset/webGLDemo.html"
title="Spinning Cone and Cube"
sop="false"/>
</customresponse>
</problem>
describe('JSInput', function() {
var sections;
var inputFields;
var $jsinputContainers;
var $inputFields;
beforeEach(function() {
loadFixtures('js/capa/fixtures/jsinput.html');
sections = $('section[id^="inputtype_"]');
inputFields = $('input[id^="input_"]');
$jsinputContainers = $('.jsinput');
$inputFields = $('input[id^="input_"]');
JSInput.walkDOM();
});
it('sets all data-processed attributes to true on first load', function() {
sections.each(function(index, item) {
$jsinputContainers.each(function(index, item) {
expect(item).toHaveData('processed', true);
});
});
it('sets the waitfor attribute to its update function', function() {
inputFields.each(function(index, item) {
$inputFields.each(function(index, item) {
expect(item).toHaveAttr('waitfor');
});
});
it('tests the correct number of sections', function() {
expect(sections.length).toEqual(2);
expect(sections.length).toEqual(inputFields.length);
it('tests the correct number of jsinput instances', function() {
expect($jsinputContainers.length).toEqual(2);
expect($jsinputContainers.length).toEqual($inputFields.length);
});
});
......@@ -39,26 +39,26 @@ var JSInput = (function($, undefined) {
/* Private methods */
var section = $(elem).parent().find('section[class="jsinput"]'),
sectionAttr = function(e) { return $(section).attr(e); },
var jsinputContainer = $(elem).parent().find('.jsinput'),
jsinputAttr = function(e) { return $(jsinputContainer).attr(e); },
iframe = $(elem).find('iframe[name^="iframe_"]').get(0),
cWindow = iframe.contentWindow,
path = iframe.src.substring(0, iframe.src.lastIndexOf('/') + 1),
// Get the hidden input field to pass to customresponse
inputField = $(elem).parent().find('input[id^="input_"]'),
// Get the grade function name
gradeFn = sectionAttr('data'),
gradeFn = jsinputAttr('data'),
// Get state getter
stateGetter = sectionAttr('data-getstate'),
stateGetter = jsinputAttr('data-getstate'),
// Get state setter
stateSetter = sectionAttr('data-setstate'),
stateSetter = jsinputAttr('data-setstate'),
// Get stored state
storedState = sectionAttr('data-stored'),
storedState = jsinputAttr('data-stored'),
// Get initial state
initialState = sectionAttr('data-initial-state'),
initialState = jsinputAttr('data-initial-state'),
// Bypass single-origin policy only if this attribute is "false"
// In that case, use JSChannel to do so.
sop = sectionAttr('data-sop'),
sop = jsinputAttr('data-sop'),
channel;
sop = (sop !== 'false');
......@@ -189,14 +189,14 @@ var JSInput = (function($, undefined) {
}
function walkDOM() {
var allSections = $('section.jsinput');
var $jsinputContainers = $('.jsinput');
// When a JSInput problem loads, its data-processed attribute is false,
// so the jsconstructor will be called for it.
// The constructor will not be called again on subsequent reruns of
// this file by other JSInput. Only if it is reloaded, either with the
// rest of the page or when it is submitted, will this constructor be
// called again.
allSections.each(function(index, value) {
$jsinputContainers.each(function(index, value) {
var dataProcessed = ($(value).attr('data-processed') === 'true');
if (!dataProcessed) {
jsinputConstructor(value);
......
......@@ -19,6 +19,7 @@ from capa.tests.response_xml_factory import (
CustomResponseXMLFactory,
FormulaResponseXMLFactory,
ImageResponseXMLFactory,
JSInputXMLFactory,
MultipleChoiceResponseXMLFactory,
NumericalResponseXMLFactory,
OptionResponseXMLFactory,
......@@ -132,7 +133,29 @@ class ProblemTypeTestBase(ProblemsTest, EventsTestMixin):
raise NotImplementedError()
class ProblemTypeTestMixin(object):
class ProblemTypeA11yTestMixin(object):
"""
Shared a11y tests for all problem types.
"""
@attr('a11y')
def test_problem_type_a11y(self):
"""
Run accessibility audit for the problem type.
"""
self.problem_page.wait_for(
lambda: self.problem_page.problem_name == self.problem_name,
"Make sure the correct problem is on the page"
)
# Set the scope to the problem container
self.problem_page.a11y_audit.config.set_scope(
include=['div#seq_content'])
# Run the accessibility audit.
self.problem_page.a11y_audit.check_for_accessibility_errors()
class ProblemTypeTestMixin(ProblemTypeA11yTestMixin):
"""
Test cases shared amongst problem types.
"""
......@@ -357,23 +380,6 @@ class ProblemTypeTestMixin(object):
self.problem_page.click_submit()
self.problem_page.wait_partial_notification()
@attr('a11y')
def test_problem_type_a11y(self):
"""
Run accessibility audit for the problem type.
"""
self.problem_page.wait_for(
lambda: self.problem_page.problem_name == self.problem_name,
"Make sure the correct problem is on the page"
)
# Set the scope to the problem container
self.problem_page.a11y_audit.config.set_scope(
include=['div#seq_content'])
# Run the accessibility audit.
self.problem_page.a11y_audit.check_for_accessibility_errors()
class AnnotationProblemTypeTest(ProblemTypeTestBase, ProblemTypeTestMixin):
"""
......@@ -801,6 +807,29 @@ class ScriptProblemTypeTest(ProblemTypeTestBase, ProblemTypeTestMixin):
self.problem_page.fill_answer(second_addend, input_num=1)
class JSInputTypeTest(ProblemTypeTestBase, ProblemTypeA11yTestMixin):
"""
TestCase Class for jsinput (custom JavaScript) problem type.
Right now the only test point that is executed is the a11y test.
This is because the factory simply creates an empty iframe.
"""
problem_name = 'JSINPUT PROBLEM'
problem_type = 'customresponse'
factory = JSInputXMLFactory()
factory_kwargs = {
'question_text': 'IFrame shows below (but has no content)'
}
def answer_problem(self, correctness):
"""
Problem is not set up to work (displays an empty iframe), but this method must
be extended because the parent class has marked it as abstract.
"""
raise NotImplementedError()
class CodeProblemTypeTest(ProblemTypeTestBase, ProblemTypeTestMixin):
"""
TestCase Class for Code Problem Type
......
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