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
156b43ec
Commit
156b43ec
authored
Nov 29, 2016
by
cahrens
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Allow passing through title to iFrame.
Also changes section to div. TNL-6044
parent
d2b6c9eb
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 @
156b43ec
...
@@ -592,8 +592,12 @@ class JSInput(InputTypeBase):
...
@@ -592,8 +592,12 @@ class JSInput(InputTypeBase):
# set state
# set state
Attribute
(
'width'
,
"400"
),
# iframe width
Attribute
(
'width'
,
"400"
),
# iframe width
Attribute
(
'height'
,
"300"
),
# iframe height
Attribute
(
'height'
,
"300"
),
# iframe height
Attribute
(
'sop'
,
None
)
# SOP will be relaxed only if this
# Title for the iframe, which should be supplied by the author of the problem. Not translated
# attribute is set to false.
# 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
):
def
_extra_context
(
self
):
...
...
common/lib/capa/capa/templates/jsinput.html
View file @
156b43ec
<
%
page
expression_filter=
"h"
/>
<
%!
from
openedx
.
core
.
djangolib
.
markup
import
HTML
%
>
<
%!
from
openedx
.
core
.
djangolib
.
markup
import
HTML
%
>
<
section
id=
"inputtype_${id}"
class=
"jsinput"
<
div
id=
"inputtype_${id}"
class=
"jsinput"
data=
"${gradefn}"
data=
"${gradefn}"
%
if
saved_state:
%
if
saved_state:
data-stored=
"${saved_state
|x
}"
data-stored=
"${saved_state}"
%
endif
%
endif
%
if
initial_state:
%
if
initial_state:
data-initial-state=
"${initial_state
|x
}"
data-initial-state=
"${initial_state}"
%
endif
%
endif
%
if
get_statefn:
%
if
get_statefn:
data-getstate=
"${get_statefn}"
data-getstate=
"${get_statefn}"
...
@@ -33,10 +34,11 @@
...
@@ -33,10 +34,11 @@
src=
"${html_file}"
src=
"${html_file}"
height=
"${height}"
height=
"${height}"
width=
"${width}"
width=
"${width}"
title=
"${title}"
/>
/>
<input
type=
"hidden"
name=
"input_${id}"
id=
"input_${id}"
<input
type=
"hidden"
name=
"input_${id}"
id=
"input_${id}"
waitfor=
""
waitfor=
""
value=
"${value
|h
}"
/>
value=
"${value}"
/>
<br/>
<br/>
<p
id=
"answer_${id}"
class=
"answer"
></p>
<p
id=
"answer_${id}"
class=
"answer"
></p>
...
@@ -54,4 +56,4 @@
...
@@ -54,4 +56,4 @@
% if msg:
% if msg:
<span
class=
"message"
tabindex=
"-1"
>
${HTML(msg)}
</span>
<span
class=
"message"
tabindex=
"-1"
>
${HTML(msg)}
</span>
% endif
% endif
</
section
>
</
div
>
common/lib/capa/capa/tests/response_xml_factory.py
View file @
156b43ec
...
@@ -608,6 +608,18 @@ class ImageResponseXMLFactory(ResponseXMLFactory):
...
@@ -608,6 +608,18 @@ class ImageResponseXMLFactory(ResponseXMLFactory):
return
input_element
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
):
class
MultipleChoiceResponseXMLFactory
(
ResponseXMLFactory
):
""" Factory for producing <multiplechoiceresponse> XML """
""" Factory for producing <multiplechoiceresponse> XML """
...
...
common/lib/capa/capa/tests/test_inputtypes.py
View file @
156b43ec
...
@@ -162,6 +162,89 @@ class ChoiceGroupTest(unittest.TestCase):
...
@@ -162,6 +162,89 @@ class ChoiceGroupTest(unittest.TestCase):
self
.
check_group
(
'checkboxgroup'
,
'checkbox'
,
'[]'
)
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
):
class
TextLineTest
(
unittest
.
TestCase
):
'''
'''
Check that textline inputs work, with and without math.
Check that textline inputs work, with and without math.
...
...
common/lib/xmodule/xmodule/templates/problem/jsinput_response.yaml
View file @
156b43ec
...
@@ -66,6 +66,7 @@ data: |
...
@@ -66,6 +66,7 @@ data: |
width="400"
width="400"
height="400"
height="400"
html_file="https://studio.edx.org/c4x/edX/DemoX/asset/webGLDemo.html"
html_file="https://studio.edx.org/c4x/edX/DemoX/asset/webGLDemo.html"
title="Spinning Cone and Cube"
sop="false"/>
sop="false"/>
</customresponse>
</customresponse>
</problem>
</problem>
common/static/js/capa/spec/jsinput_spec.js
View file @
156b43ec
describe
(
'JSInput'
,
function
()
{
describe
(
'JSInput'
,
function
()
{
var
section
s
;
var
$jsinputContainer
s
;
var
inputFields
;
var
$
inputFields
;
beforeEach
(
function
()
{
beforeEach
(
function
()
{
loadFixtures
(
'js/capa/fixtures/jsinput.html'
);
loadFixtures
(
'js/capa/fixtures/jsinput.html'
);
sections
=
$
(
'section[id^="inputtype_"]
'
);
$jsinputContainers
=
$
(
'.jsinput
'
);
inputFields
=
$
(
'input[id^="input_"]'
);
$
inputFields
=
$
(
'input[id^="input_"]'
);
JSInput
.
walkDOM
();
JSInput
.
walkDOM
();
});
});
it
(
'sets all data-processed attributes to true on first load'
,
function
()
{
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
);
expect
(
item
).
toHaveData
(
'processed'
,
true
);
});
});
});
});
it
(
'sets the waitfor attribute to its update function'
,
function
()
{
it
(
'sets the waitfor attribute to its update function'
,
function
()
{
inputFields
.
each
(
function
(
index
,
item
)
{
$
inputFields
.
each
(
function
(
index
,
item
)
{
expect
(
item
).
toHaveAttr
(
'waitfor'
);
expect
(
item
).
toHaveAttr
(
'waitfor'
);
});
});
});
});
it
(
'tests the correct number of
section
s'
,
function
()
{
it
(
'tests the correct number of
jsinput instance
s'
,
function
()
{
expect
(
section
s
.
length
).
toEqual
(
2
);
expect
(
$jsinputContainer
s
.
length
).
toEqual
(
2
);
expect
(
sections
.
length
).
toEqual
(
inputFields
.
length
);
expect
(
$jsinputContainers
.
length
).
toEqual
(
$
inputFields
.
length
);
});
});
});
});
common/static/js/capa/src/jsinput.js
View file @
156b43ec
...
@@ -39,26 +39,26 @@ var JSInput = (function($, undefined) {
...
@@ -39,26 +39,26 @@ var JSInput = (function($, undefined) {
/* Private methods */
/* Private methods */
var
section
=
$
(
elem
).
parent
().
find
(
'section[class="jsinput"]
'
),
var
jsinputContainer
=
$
(
elem
).
parent
().
find
(
'.jsinput
'
),
sectionAttr
=
function
(
e
)
{
return
$
(
section
).
attr
(
e
);
},
jsinputAttr
=
function
(
e
)
{
return
$
(
jsinputContainer
).
attr
(
e
);
},
iframe
=
$
(
elem
).
find
(
'iframe[name^="iframe_"]'
).
get
(
0
),
iframe
=
$
(
elem
).
find
(
'iframe[name^="iframe_"]'
).
get
(
0
),
cWindow
=
iframe
.
contentWindow
,
cWindow
=
iframe
.
contentWindow
,
path
=
iframe
.
src
.
substring
(
0
,
iframe
.
src
.
lastIndexOf
(
'/'
)
+
1
),
path
=
iframe
.
src
.
substring
(
0
,
iframe
.
src
.
lastIndexOf
(
'/'
)
+
1
),
// Get the hidden input field to pass to customresponse
// Get the hidden input field to pass to customresponse
inputField
=
$
(
elem
).
parent
().
find
(
'input[id^="input_"]'
),
inputField
=
$
(
elem
).
parent
().
find
(
'input[id^="input_"]'
),
// Get the grade function name
// Get the grade function name
gradeFn
=
section
Attr
(
'data'
),
gradeFn
=
jsinput
Attr
(
'data'
),
// Get state getter
// Get state getter
stateGetter
=
section
Attr
(
'data-getstate'
),
stateGetter
=
jsinput
Attr
(
'data-getstate'
),
// Get state setter
// Get state setter
stateSetter
=
section
Attr
(
'data-setstate'
),
stateSetter
=
jsinput
Attr
(
'data-setstate'
),
// Get stored state
// Get stored state
storedState
=
section
Attr
(
'data-stored'
),
storedState
=
jsinput
Attr
(
'data-stored'
),
// Get initial state
// 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"
// Bypass single-origin policy only if this attribute is "false"
// In that case, use JSChannel to do so.
// In that case, use JSChannel to do so.
sop
=
section
Attr
(
'data-sop'
),
sop
=
jsinput
Attr
(
'data-sop'
),
channel
;
channel
;
sop
=
(
sop
!==
'false'
);
sop
=
(
sop
!==
'false'
);
...
@@ -189,14 +189,14 @@ var JSInput = (function($, undefined) {
...
@@ -189,14 +189,14 @@ var JSInput = (function($, undefined) {
}
}
function
walkDOM
()
{
function
walkDOM
()
{
var
allSections
=
$
(
'section
.jsinput'
);
var
$jsinputContainers
=
$
(
'
.jsinput'
);
// When a JSInput problem loads, its data-processed attribute is false,
// When a JSInput problem loads, its data-processed attribute is false,
// so the jsconstructor will be called for it.
// so the jsconstructor will be called for it.
// The constructor will not be called again on subsequent reruns of
// The constructor will not be called again on subsequent reruns of
// this file by other JSInput. Only if it is reloaded, either with the
// 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
// rest of the page or when it is submitted, will this constructor be
// called again.
// called again.
allSection
s
.
each
(
function
(
index
,
value
)
{
$jsinputContainer
s
.
each
(
function
(
index
,
value
)
{
var
dataProcessed
=
(
$
(
value
).
attr
(
'data-processed'
)
===
'true'
);
var
dataProcessed
=
(
$
(
value
).
attr
(
'data-processed'
)
===
'true'
);
if
(
!
dataProcessed
)
{
if
(
!
dataProcessed
)
{
jsinputConstructor
(
value
);
jsinputConstructor
(
value
);
...
...
common/test/acceptance/tests/lms/test_problem_types.py
View file @
156b43ec
...
@@ -19,6 +19,7 @@ from capa.tests.response_xml_factory import (
...
@@ -19,6 +19,7 @@ from capa.tests.response_xml_factory import (
CustomResponseXMLFactory
,
CustomResponseXMLFactory
,
FormulaResponseXMLFactory
,
FormulaResponseXMLFactory
,
ImageResponseXMLFactory
,
ImageResponseXMLFactory
,
JSInputXMLFactory
,
MultipleChoiceResponseXMLFactory
,
MultipleChoiceResponseXMLFactory
,
NumericalResponseXMLFactory
,
NumericalResponseXMLFactory
,
OptionResponseXMLFactory
,
OptionResponseXMLFactory
,
...
@@ -132,7 +133,29 @@ class ProblemTypeTestBase(ProblemsTest, EventsTestMixin):
...
@@ -132,7 +133,29 @@ class ProblemTypeTestBase(ProblemsTest, EventsTestMixin):
raise
NotImplementedError
()
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.
Test cases shared amongst problem types.
"""
"""
...
@@ -357,23 +380,6 @@ class ProblemTypeTestMixin(object):
...
@@ -357,23 +380,6 @@ class ProblemTypeTestMixin(object):
self
.
problem_page
.
click_submit
()
self
.
problem_page
.
click_submit
()
self
.
problem_page
.
wait_partial_notification
()
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
):
class
AnnotationProblemTypeTest
(
ProblemTypeTestBase
,
ProblemTypeTestMixin
):
"""
"""
...
@@ -801,6 +807,29 @@ class ScriptProblemTypeTest(ProblemTypeTestBase, ProblemTypeTestMixin):
...
@@ -801,6 +807,29 @@ class ScriptProblemTypeTest(ProblemTypeTestBase, ProblemTypeTestMixin):
self
.
problem_page
.
fill_answer
(
second_addend
,
input_num
=
1
)
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
):
class
CodeProblemTypeTest
(
ProblemTypeTestBase
,
ProblemTypeTestMixin
):
"""
"""
TestCase Class for Code Problem Type
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