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
73c30993
Commit
73c30993
authored
Feb 18, 2015
by
Sarina Canelake
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #6679 from open-craft/problem-tooltips
Support for inline explanatory popups in problem XML
parents
3b883d17
d12e173c
Hide whitespace changes
Inline
Side-by-side
Showing
9 changed files
with
208 additions
and
45 deletions
+208
-45
common/lib/capa/capa/customrender.py
+32
-2
common/lib/capa/capa/templates/clarification.html
+5
-0
common/lib/xmodule/xmodule/css/capa/display.scss
+6
-0
common/lib/xmodule/xmodule/js/src/capa/display.coffee
+6
-0
common/static/js/spec/tooltip_manager_spec.js
+11
-0
common/static/js/src/tooltip_manager.js
+31
-5
common/test/acceptance/pages/lms/problem.py
+16
-0
common/test/acceptance/tests/lms/test_lms_matlab_problem.py
+13
-38
common/test/acceptance/tests/lms/test_lms_problems.py
+88
-0
No files found.
common/lib/capa/capa/customrender.py
View file @
73c30993
...
...
@@ -6,8 +6,6 @@ These tags do not have state, so they just get passed the system (for access to
and the xml element.
"""
from
.registry
import
TagRegistry
import
logging
import
re
...
...
@@ -137,3 +135,35 @@ class TargetedFeedbackRenderer(object):
return
xhtml
registry
.
register
(
TargetedFeedbackRenderer
)
#-----------------------------------------------------------------------------
class
ClarificationRenderer
(
object
):
"""
A clarification appears as an inline icon which reveals more information when the user
hovers over it.
e.g. <p>Enter the ROA <clarification>Return on Assets</clarification> for 2015:</p>
"""
tags
=
[
'clarification'
]
def
__init__
(
self
,
system
,
xml
):
self
.
system
=
system
# Get any text content found inside this tag prior to the first child tag. It may be a string or None type.
initial_text
=
xml
.
text
if
xml
.
text
else
''
self
.
inner_html
=
initial_text
+
''
.
join
(
etree
.
tostring
(
element
)
for
element
in
xml
)
# pylint: disable=no-member
self
.
tail
=
xml
.
tail
def
get_html
(
self
):
"""
Return the contents of this tag, rendered to html, as an etree element.
"""
context
=
{
'clarification'
:
self
.
inner_html
}
html
=
self
.
system
.
render_template
(
"clarification.html"
,
context
)
xml
=
etree
.
XML
(
html
)
# pylint: disable=no-member
# We must include any text that was following our original <clarification>...</clarification> XML node.:
xml
.
tail
=
self
.
tail
return
xml
registry
.
register
(
ClarificationRenderer
)
common/lib/capa/capa/templates/clarification.html
0 → 100644
View file @
73c30993
<span
class=
"clarification"
tabindex=
"0"
role=
"note"
aria-label=
"Clarification"
>
<i
data-tooltip=
"${clarification | h}"
data-tooltip-show-on-click=
"true"
class=
"fa fa-info-circle"
aria-hidden=
"true"
></i>
<span
class=
"sr"
>
(${clarification})
</span>
</span>
common/lib/xmodule/xmodule/css/capa/display.scss
View file @
73c30993
...
...
@@ -162,6 +162,12 @@ div.problem {
white-space
:
nowrap
;
overflow
:
hidden
;
}
span
.clarification
i
{
font-style
:
normal
;
&
:hover
{
color
:
$blue
;
}
}
}
&
.unanswered
{
...
...
common/lib/xmodule/xmodule/js/src/capa/display.coffee
View file @
73c30993
...
...
@@ -34,6 +34,12 @@ class @Problem
@
$
(
'div.action input.reset'
).
click
@
reset
@
$
(
'div.action button.show'
).
click
@
show
@
$
(
'div.action input.save'
).
click
@
save
# Accessibility helper for sighted keyboard users to show <clarification> tooltips on focus:
@
$
(
'.clarification'
).
focus
(
ev
)
=>
icon
=
$
(
ev
.
target
).
children
"i"
window
.
globalTooltipManager
.
openTooltip
icon
@
$
(
'.clarification'
).
blur
(
ev
)
=>
window
.
globalTooltipManager
.
hide
()
@
bindResetCorrectness
()
...
...
common/static/js/spec/tooltip_manager_spec.js
View file @
73c30993
...
...
@@ -70,6 +70,17 @@ describe('TooltipManager', function () {
expect
(
$
(
'.tooltip'
)).
toBeHidden
();
});
it
(
'can be configured to show when user clicks on the element'
,
function
()
{
this
.
element
.
attr
(
'data-tooltip-show-on-click'
,
true
);
this
.
element
.
trigger
(
$
.
Event
(
"click"
));
expect
(
$
(
'.tooltip'
)).
toBeVisible
();
});
it
(
'can be be triggered manually'
,
function
()
{
this
.
tooltip
.
openTooltip
(
this
.
element
);
expect
(
$
(
'.tooltip'
)).
toBeVisible
();
});
it
(
'should moves correctly'
,
function
()
{
showTooltip
(
this
.
element
);
expect
(
$
(
'.tooltip'
)).
toBeVisible
();
...
...
common/static/js/src/tooltip_manager.js
View file @
73c30993
...
...
@@ -26,7 +26,7 @@
'mouseover.TooltipManager'
:
this
.
showTooltip
,
'mousemove.TooltipManager'
:
this
.
moveTooltip
,
'mouseout.TooltipManager'
:
this
.
hideTooltip
,
'click.TooltipManager'
:
this
.
hideTooltip
'click.TooltipManager'
:
this
.
click
},
this
.
SELECTOR
);
},
...
...
@@ -46,15 +46,29 @@
},
showTooltip
:
function
(
event
)
{
var
tooltipText
=
$
(
event
.
currentTarget
).
attr
(
'data-tooltip'
);
this
.
prepareTooltip
(
event
.
currentTarget
,
event
.
pageX
,
event
.
pageY
);
if
(
this
.
tooltipTimer
)
{
clearTimeout
(
this
.
tooltipTimer
);
}
this
.
tooltipTimer
=
setTimeout
(
this
.
show
,
500
);
},
prepareTooltip
:
function
(
element
,
pageX
,
pageY
)
{
pageX
=
typeof
pageX
!==
'undefined'
?
pageX
:
element
.
offset
().
left
+
element
.
width
()
/
2
;
pageY
=
typeof
pageY
!==
'undefined'
?
pageY
:
element
.
offset
().
top
+
element
.
height
()
/
2
;
var
tooltipText
=
$
(
element
).
attr
(
'data-tooltip'
);
this
.
tooltip
.
html
(
tooltipText
)
.
css
(
this
.
getCoords
(
event
.
pageX
,
event
.
pageY
));
.
css
(
this
.
getCoords
(
pageX
,
pageY
));
},
// To manually trigger a tooltip to reveal, other than through user mouse movement:
openTooltip
:
function
(
element
)
{
this
.
prepareTooltip
(
element
);
this
.
show
();
if
(
this
.
tooltipTimer
)
{
clearTimeout
(
this
.
tooltipTimer
);
}
this
.
tooltipTimer
=
setTimeout
(
this
.
show
,
500
);
},
moveTooltip
:
function
(
event
)
{
...
...
@@ -68,6 +82,18 @@
this
.
tooltipTimer
=
setTimeout
(
this
.
hide
,
50
);
},
click
:
function
(
event
)
{
var
showOnClick
=
!!
$
(
event
.
currentTarget
).
data
(
'tooltip-show-on-click'
);
// Default is false
if
(
showOnClick
)
{
this
.
show
();
if
(
this
.
tooltipTimer
)
{
clearTimeout
(
this
.
tooltipTimer
);
}
}
else
{
this
.
hideTooltip
(
event
);
}
},
destroy
:
function
()
{
this
.
tooltip
.
remove
();
// Unbind all delegated event handlers in the ".TooltipManager"
...
...
@@ -78,6 +104,6 @@
window
.
TooltipManager
=
TooltipManager
;
$
(
document
).
ready
(
function
()
{
new
TooltipManager
(
document
.
body
);
window
.
globalTooltipManager
=
new
TooltipManager
(
document
.
body
);
});
}());
common/test/acceptance/pages/lms/problem.py
View file @
73c30993
...
...
@@ -46,3 +46,19 @@ class ProblemPage(PageObject):
Is there a "correct" status showing?
"""
return
self
.
q
(
css
=
"div.problem div.capa_inputtype.textline div.correct p.status"
)
.
is_present
()
def
click_clarification
(
self
,
index
=
0
):
"""
Click on an inline icon that can be included in problem text using an HTML <clarification> element:
Problem <clarification>clarification text hidden by an icon in rendering</clarification> Text
"""
self
.
q
(
css
=
'div.problem .clarification:nth-child({index}) i[data-tooltip]'
.
format
(
index
=
index
+
1
))
.
click
()
@property
def
visible_tooltip_text
(
self
):
"""
Get the text seen in any tooltip currently visible on the page.
"""
self
.
wait_for_element_visibility
(
'body > .tooltip'
,
'A tooltip is visible.'
)
return
self
.
q
(
css
=
'body > .tooltip'
)
.
text
[
0
]
common/test/acceptance/tests/lms/test_lms_matlab_problem.py
View file @
73c30993
# -*- coding: utf-8 -*-
"""
End-to-end tests for the LMS.
Test for matlab problems
"""
import
time
from
..helpers
import
UniqueCourseTest
from
...pages.studio.auto_auth
import
AutoAuthPage
from
...pages.lms.courseware
import
CoursewarePage
from
...pages.lms.matlab_problem
import
MatlabProblemPage
from
...fixtures.course
import
CourseFixture
,
XBlockFixtureDesc
from
...fixtures.course
import
XBlockFixtureDesc
from
...fixtures.xqueue
import
XQueueResponseFixture
from
.test_lms_problems
import
ProblemsTest
from
textwrap
import
dedent
class
MatlabProblemTest
(
UniqueCourse
Test
):
class
MatlabProblemTest
(
Problems
Test
):
"""
Tests that verify matlab problem "Run Code".
"""
USERNAME
=
"STAFF_TESTER"
EMAIL
=
"johndoe@example.com"
def
setUp
(
self
):
super
(
MatlabProblemTest
,
self
)
.
setUp
()
self
.
XQUEUE_GRADE_RESPONSE
=
None
self
.
courseware_page
=
CoursewarePage
(
self
.
browser
,
self
.
course_id
)
# Install a course with sections/problems, tabs, updates, and handouts
course_fix
=
CourseFixture
(
self
.
course_info
[
'org'
],
self
.
course_info
[
'number'
],
self
.
course_info
[
'run'
],
self
.
course_info
[
'display_name'
]
)
def
get_problem
(
self
):
"""
Create a matlab problem for the test.
"""
problem_data
=
dedent
(
"""
<problem markdown="null">
<text>
...
...
@@ -62,18 +48,7 @@ class MatlabProblemTest(UniqueCourseTest):
</text>
</problem>
"""
)
course_fix
.
add_children
(
XBlockFixtureDesc
(
'chapter'
,
'Test Section'
)
.
add_children
(
XBlockFixtureDesc
(
'sequential'
,
'Test Subsection'
)
.
add_children
(
XBlockFixtureDesc
(
'problem'
,
'Test Matlab Problem'
,
data
=
problem_data
)
)
)
)
.
install
()
# Auto-auth register for the course.
AutoAuthPage
(
self
.
browser
,
username
=
self
.
USERNAME
,
email
=
self
.
EMAIL
,
course_id
=
self
.
course_id
,
staff
=
False
)
.
visit
()
return
XBlockFixtureDesc
(
'problem'
,
'Test Matlab Problem'
,
data
=
problem_data
)
def
_goto_matlab_problem_page
(
self
):
"""
...
...
@@ -92,13 +67,13 @@ class MatlabProblemTest(UniqueCourseTest):
# Enter a submission, which will trigger a pre-defined response from the XQueue stub.
self
.
submission
=
"a=1"
+
self
.
unique_id
[
0
:
5
]
self
.
XQUEUE_GRADE_RESPONSE
=
{
'msg'
:
self
.
submission
}
self
.
xqueue_grade_response
=
{
'msg'
:
self
.
submission
}
matlab_problem_page
=
self
.
_goto_matlab_problem_page
()
# Configure the XQueue stub's response for the text we will submit
if
self
.
XQUEUE_GRADE_RESPONSE
is
not
None
:
XQueueResponseFixture
(
self
.
submission
,
self
.
XQUEUE_GRADE_RESPONSE
)
.
install
()
if
self
.
xqueue_grade_response
is
not
None
:
XQueueResponseFixture
(
self
.
submission
,
self
.
xqueue_grade_response
)
.
install
()
matlab_problem_page
.
set_response
(
self
.
submission
)
matlab_problem_page
.
click_run_code
()
...
...
@@ -113,6 +88,6 @@ class MatlabProblemTest(UniqueCourseTest):
self
.
assertEqual
(
u''
,
matlab_problem_page
.
get_grader_msg
(
".external-grader-message"
)[
0
])
self
.
assertEqual
(
self
.
XQUEUE_GRADE_RESPONSE
.
get
(
"msg"
),
self
.
xqueue_grade_response
.
get
(
"msg"
),
matlab_problem_page
.
get_grader_msg
(
".ungraded-matlab-result"
)[
0
]
)
common/test/acceptance/tests/lms/test_lms_problems.py
0 → 100644
View file @
73c30993
# -*- coding: utf-8 -*-
"""
Bok choy acceptance tests for problems in the LMS
See also old lettuce tests in lms/djangoapps/courseware/features/problems.feature
"""
from
..helpers
import
UniqueCourseTest
from
...pages.studio.auto_auth
import
AutoAuthPage
from
...pages.lms.courseware
import
CoursewarePage
from
...pages.lms.problem
import
ProblemPage
from
...fixtures.course
import
CourseFixture
,
XBlockFixtureDesc
from
textwrap
import
dedent
class
ProblemsTest
(
UniqueCourseTest
):
"""
Base class for tests of problems in the LMS.
"""
USERNAME
=
"joe_student"
EMAIL
=
"joe@example.com"
def
setUp
(
self
):
super
(
ProblemsTest
,
self
)
.
setUp
()
self
.
xqueue_grade_response
=
None
self
.
courseware_page
=
CoursewarePage
(
self
.
browser
,
self
.
course_id
)
# Install a course with a hierarchy and problems
course_fixture
=
CourseFixture
(
self
.
course_info
[
'org'
],
self
.
course_info
[
'number'
],
self
.
course_info
[
'run'
],
self
.
course_info
[
'display_name'
]
)
problem
=
self
.
get_problem
()
course_fixture
.
add_children
(
XBlockFixtureDesc
(
'chapter'
,
'Test Section'
)
.
add_children
(
XBlockFixtureDesc
(
'sequential'
,
'Test Subsection'
)
.
add_children
(
problem
)
)
)
.
install
()
# Auto-auth register for the course.
AutoAuthPage
(
self
.
browser
,
username
=
self
.
USERNAME
,
email
=
self
.
EMAIL
,
course_id
=
self
.
course_id
,
staff
=
False
)
.
visit
()
def
get_problem
(
self
):
""" Subclasses should override this to complete the fixture """
raise
NotImplementedError
()
class
ProblemClarificationTest
(
ProblemsTest
):
"""
Tests the <clarification> element that can be used in problem XML.
"""
def
get_problem
(
self
):
"""
Create a problem with a <clarification>
"""
xml
=
dedent
(
"""
<problem markdown="null">
<text>
<p>
Given the data in Table 7 <clarification>Table 7: "Example PV Installation Costs",
Page 171 of Roberts textbook</clarification>, compute the ROI
<clarification>Return on Investment <strong>(per year)</strong></clarification> over 20 years.
</p>
<numericalresponse answer="6.5">
<textline label="Enter the annual ROI" trailing_text="
%
" />
</numericalresponse>
</text>
</problem>
"""
)
return
XBlockFixtureDesc
(
'problem'
,
'TOOLTIP TEST PROBLEM'
,
data
=
xml
)
def
test_clarification
(
self
):
"""
Test that we can see the <clarification> tooltips.
"""
self
.
courseware_page
.
visit
()
problem_page
=
ProblemPage
(
self
.
browser
)
self
.
assertEqual
(
problem_page
.
problem_name
,
'TOOLTIP TEST PROBLEM'
)
problem_page
.
click_clarification
(
0
)
self
.
assertIn
(
'"Example PV Installation Costs"'
,
problem_page
.
visible_tooltip_text
)
problem_page
.
click_clarification
(
1
)
tooltip_text
=
problem_page
.
visible_tooltip_text
self
.
assertIn
(
'Return on Investment'
,
tooltip_text
)
self
.
assertIn
(
'per year'
,
tooltip_text
)
self
.
assertNotIn
(
'strong'
,
tooltip_text
)
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