Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
E
edx-platform
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
edx
edx-platform
Commits
7b2239b5
Commit
7b2239b5
authored
Dec 05, 2016
by
Christina Roberts
Committed by
GitHub
Dec 05, 2016
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #14072 from edx/christina/jsinput_title
Allow passing through title to iFrame.
parents
3994615d
156b43ec
Hide whitespace changes
Inline
Side-by-side
Showing
8 changed files
with
175 additions
and
44 deletions
+175
-44
common/lib/capa/capa/inputtypes.py
+6
-2
common/lib/capa/capa/templates/jsinput.html
+7
-5
common/lib/capa/capa/tests/response_xml_factory.py
+12
-0
common/lib/capa/capa/tests/test_inputtypes.py
+83
-0
common/lib/xmodule/xmodule/templates/problem/jsinput_response.yaml
+1
-0
common/static/js/capa/spec/jsinput_spec.js
+9
-9
common/static/js/capa/src/jsinput.js
+10
-10
common/test/acceptance/tests/lms/test_problem_types.py
+47
-18
No files found.
common/lib/capa/capa/inputtypes.py
View file @
7b2239b5
...
...
@@ -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
):
...
...
common/lib/capa/capa/templates/jsinput.html
View file @
7b2239b5
<
%
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
>
common/lib/capa/capa/tests/response_xml_factory.py
View file @
7b2239b5
...
...
@@ -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 """
...
...
common/lib/capa/capa/tests/test_inputtypes.py
View file @
7b2239b5
...
...
@@ -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.
...
...
common/lib/xmodule/xmodule/templates/problem/jsinput_response.yaml
View file @
7b2239b5
...
...
@@ -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>
common/static/js/capa/spec/jsinput_spec.js
View file @
7b2239b5
describe
(
'JSInput'
,
function
()
{
var
section
s
;
var
inputFields
;
var
$jsinputContainer
s
;
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
()
{
section
s
.
each
(
function
(
index
,
item
)
{
$jsinputContainer
s
.
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
section
s'
,
function
()
{
expect
(
section
s
.
length
).
toEqual
(
2
);
expect
(
sections
.
length
).
toEqual
(
inputFields
.
length
);
it
(
'tests the correct number of
jsinput instance
s'
,
function
()
{
expect
(
$jsinputContainer
s
.
length
).
toEqual
(
2
);
expect
(
$jsinputContainers
.
length
).
toEqual
(
$
inputFields
.
length
);
});
});
common/static/js/capa/src/jsinput.js
View file @
7b2239b5
...
...
@@ -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
=
section
Attr
(
'data'
),
gradeFn
=
jsinput
Attr
(
'data'
),
// Get state getter
stateGetter
=
section
Attr
(
'data-getstate'
),
stateGetter
=
jsinput
Attr
(
'data-getstate'
),
// Get state setter
stateSetter
=
section
Attr
(
'data-setstate'
),
stateSetter
=
jsinput
Attr
(
'data-setstate'
),
// Get stored state
storedState
=
section
Attr
(
'data-stored'
),
storedState
=
jsinput
Attr
(
'data-stored'
),
// Get initial state
initialState
=
section
Attr
(
'data-initial-state'
),
initialState
=
jsinput
Attr
(
'data-initial-state'
),
// Bypass single-origin policy only if this attribute is "false"
// In that case, use JSChannel to do so.
sop
=
section
Attr
(
'data-sop'
),
sop
=
jsinput
Attr
(
'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.
allSection
s
.
each
(
function
(
index
,
value
)
{
$jsinputContainer
s
.
each
(
function
(
index
,
value
)
{
var
dataProcessed
=
(
$
(
value
).
attr
(
'data-processed'
)
===
'true'
);
if
(
!
dataProcessed
)
{
jsinputConstructor
(
value
);
...
...
common/test/acceptance/tests/lms/test_problem_types.py
View file @
7b2239b5
...
...
@@ -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
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment