Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,5 @@ script:
- python run_tests.py --with-coverage --cover-package=problem_builder
notifications:
email: false
addons:
firefox: "36.0"
6 changes: 6 additions & 0 deletions problem_builder/dashboard.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
import json
import logging
import operator as op
from django.template.defaultfilters import floatformat

from .dashboard_visual import DashboardVisualData
from .mcq import MCQBlock
Expand Down Expand Up @@ -402,6 +403,7 @@ def student_view(self, context=None): # pylint: disable=unused-argument
block['mcqs'].append({
"display_name": mcq_block.display_name_with_default,
"value": value,
"accessible_value": _("Score: {score}").format(score=value) if value else _("No value yet"),
"color": self.color_for_value(value) if value is not None else None,
})
# If the values are numeric, display an average:
Expand All @@ -412,6 +414,10 @@ def student_view(self, context=None): # pylint: disable=unused-argument
if numeric_values:
average_value = sum(numeric_values) / len(numeric_values)
block['average'] = average_value
# average block is shown only if average value exists, so accessible text for no data is not required
block['accessible_average'] = _("Score: {score}").format(
score=floatformat(average_value)
)
block['average_label'] = self.average_labels.get(mentoring_block.url_name, _("Average"))
block['has_average'] = True
block['average_color'] = self.color_for_value(average_value)
Expand Down
8 changes: 7 additions & 1 deletion problem_builder/mentoring.py
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,12 @@ class MentoringBlock(XBlock, StepParentMixin, StudioEditableXBlockMixin, StudioC
default=_("Mentoring Questions"),
scope=Scope.settings
)
feedback_label = String(
display_name=_("Feedback Header"),
help=_("Header for feedback messages"),
default=_("Feedback"),
scope=Scope.content
)

# User state
attempted = Boolean(
Expand Down Expand Up @@ -170,7 +176,7 @@ class MentoringBlock(XBlock, StepParentMixin, StudioEditableXBlockMixin, StudioC

editable_fields = (
'display_name', 'mode', 'followed_by', 'max_attempts', 'enforce_dependency',
'display_submit', 'weight',
'display_submit', 'feedback_label', 'weight',
)
icon_class = 'problem'
has_score = True
Expand Down
10 changes: 9 additions & 1 deletion problem_builder/public/css/dashboard.css
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
.pb-dashboard table {
max-width: 800px;
width: 700px;
table-layout: auto;
border-collapse: collapse;
margin-left: auto;
margin-right: auto;
margin-bottom: 15px;
}

Expand All @@ -9,6 +13,10 @@
font-weight: bold;
}

.pb-dashboard .avg-row .desc {
font-weight: 600;
}

.pb-dashboard table td, .pb-dashboard table tbody th {
border-top: 1px solid #ddd;
border-bottom: 1px solid #ddd;
Expand All @@ -24,7 +32,7 @@
min-width: 4em;
text-align: right;
padding-right: 5px;
border-right: 0.6em solid transparent;
border-right: 2em solid transparent;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You might want to just consider defining some baseline variables then having sizes be relative to that (eg baseline/4, baseline/10 or whatever). That way you don't have to do fussy math all the time and worry that numbers like 1.875 are correct.

Perhaps a future improvement to think about.

}

.pb-dashboard table .avg-row td.desc {
Expand Down
3 changes: 2 additions & 1 deletion problem_builder/public/js/mentoring.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ function MentoringBlock(runtime, element) {
hideAllSteps: hideAllSteps,
step: step,
steps: steps,
publish_event: publish_event
publish_event: publish_event,
data: data
};

function publish_event(data) {
Expand Down
2 changes: 1 addition & 1 deletion problem_builder/public/js/mentoring_standard_view.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ function MentoringStandardView(runtime, element, mentoring) {
// Messages should only be displayed upon hitting 'submit', not on page reload
mentoring.setContent(messagesDOM, results.message);
if (messagesDOM.html().trim()) {
messagesDOM.prepend('<div class="title1">' + gettext('Feedback') + '</div>');
messagesDOM.prepend('<div class="title1">' + mentoring.data.feedback_label + '</div>');
messagesDOM.show();
}

Expand Down
20 changes: 6 additions & 14 deletions problem_builder/templates/html/dashboard.html
Original file line number Diff line number Diff line change
Expand Up @@ -42,30 +42,22 @@ <h2>{{display_name}}</h2>
{% for mcq in block.mcqs %}
<tr>
<th class="desc">{{ mcq.display_name }}</th>
<td class="value"
{% if mcq.color %} style="border-right-color: {{mcq.color}};"{% endif %}
{% if not show_numbers %}
{% if mcq.value %} aria-label="Score: {{mcq.value}}" {% else %} aria-label="{% trans 'No value yet' %}" {%endif%}
{% endif %}
>
<td class="value" {% if mcq.color %} style="border-right-color: {{mcq.color}};"{% endif %}>
{% if mcq.value and show_numbers %}
{{ mcq.value }}
<span aria-hidden="true">{{ mcq.value }}</span>
{% endif %}
<span class="sr">{{ mcq.accessible_value }}</span>
</td>
</tr>
{% endfor %}
{% if block.has_average %}
<tr class="avg-row">
<th class="desc">{{ block.average_label }}</th>
<td class="value"
{% if block.average_color %} style="border-right-color: {{block.average_color}};"{% endif %}
{% if not show_numbers %}
{% if block.average %} aria-label="Score: {{block.average|floatformat}}" {% else %} aria-label="{% trans 'No value yet' %}" {%endif%}
{% endif %}
>
<td class="value" {% if block.average_color %} style="border-right-color: {{block.average_color}};"{% endif %}>
{% if show_numbers %}
{{ block.average|floatformat }}
<span aria-hidden="true">{{ block.average|floatformat }}</span>
{% endif %}
<span class="sr">{{ block.accessible_average }}</span>
</td>
</tr>
{% endif %}
Expand Down
14 changes: 14 additions & 0 deletions problem_builder/templates/html/dashboard_report.html
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,20 @@
body {
font-family: 'Open Sans', 'Helvetica Neue', Helvetica, Arial, sans-serif;
}
.pb-dashboard table {
text-align: left;
}
/* screen reader class from edx-platform */
.sr {
border: 0;
clip: rect(1px 1px 1px 1px);
height: 1px;
margin: -1px;
overflow: hidden;
padding: 0;
position: absolute;
width: 1px;
}
{{css}}
</style>
</head>
Expand Down
2 changes: 1 addition & 1 deletion problem_builder/templates/html/mentoring.html
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{% load i18n %}
<div class="mentoring themed-xblock" data-mode="{{ self.mode }}" data-step="{{ self.step }}">
<div class="mentoring themed-xblock" data-mode="{{ self.mode }}" data-step="{{ self.step }}" data-feedback_label="{{ self.feedback_label}}">
<div class="missing-dependency warning" data-missing="{{ self.has_missing_dependency }}">
{% with url=missing_dependency_url|safe %}
{% blocktrans with link_start="<a href='"|add:url|add:"'>" link_end="</a>" %}
Expand Down
63 changes: 49 additions & 14 deletions problem_builder/tests/integration/test_dashboard.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,9 @@
# "AGPLv3". If not, see <http://www.gnu.org/licenses/>.
#
from textwrap import dedent
from django.template.defaultfilters import floatformat
from mock import Mock, patch
from selenium.common.exceptions import NoSuchElementException
from xblockutils.base_test import SeleniumXBlockTest
from xblockutils.resources import ResourceLoader

Expand Down Expand Up @@ -112,6 +114,22 @@ def _install_fixture(self, dashboard_xml):
self.go_to_view("student_view")
self.vertical = self.load_root_xblock()

def _get_cell_contents(self, cell):
try:
visible_text = cell.find_element_by_css_selector('span:not(.sr)').text
except NoSuchElementException:
visible_text = ""
screen_reader_text = cell.find_element_by_css_selector('span.sr')
return visible_text, screen_reader_text.text

def _assert_cell_contents(self, cell, expected_visible_text, expected_screen_reader_text):
visible_text, screen_reader_text = self._get_cell_contents(cell)
self.assertEqual(visible_text, expected_visible_text)
self.assertEqual(screen_reader_text, expected_screen_reader_text)

def _format_sr_text(self, visible_text):
return "Score: {value}".format(value=visible_text)

def test_empty_dashboard(self):
"""
Test that when the student has not submitted any question answers, we still see
Expand All @@ -129,8 +147,8 @@ def test_empty_dashboard(self):
mcq_rows = step.find_elements_by_css_selector('tr')
self.assertTrue(2 <= len(mcq_rows) <= 3)
for mcq in mcq_rows:
value = mcq.find_element_by_css_selector('td:last-child')
self.assertEqual(value.text, '')
cell = mcq.find_element_by_css_selector('td:last-child')
self._assert_cell_contents(cell, '', 'No value yet')

def _set_mentoring_values(self):
pbs = self.browser.find_elements_by_css_selector('.mentoring')
Expand All @@ -155,20 +173,23 @@ def test_dashboard(self):
dashboard = self.browser.find_element_by_css_selector('.pb-dashboard')
steps = dashboard.find_elements_by_css_selector('tbody')
self.assertEqual(len(steps), 3)
expected_values = ('1', '2', '3', '4', 'B')

for step_num, step in enumerate(steps):
mcq_rows = step.find_elements_by_css_selector('tr:not(.avg-row)')
self.assertTrue(2 <= len(mcq_rows) <= 3)
for mcq in mcq_rows:
value = mcq.find_element_by_css_selector('td.value')
self.assertIn(value.text, ('1', '2', '3', '4', 'B'))
cell = mcq.find_element_by_css_selector('td.value')
visible_text, screen_reader_text = self._get_cell_contents(cell)
self.assertIn(visible_text, expected_values)
self.assertIn(screen_reader_text, map(self._format_sr_text, expected_values))
# Check the average:
avg_row = step.find_element_by_css_selector('tr.avg-row')
left_col = avg_row.find_element_by_css_selector('.desc')
self.assertEqual(left_col.text, "Average")
right_col = avg_row.find_element_by_css_selector('.value')
expected_average = {0: "2", 1: "3", 2: "1"}[step_num]
self.assertEqual(right_col.text, expected_average)
self._assert_cell_contents(right_col, expected_average, self._format_sr_text(expected_average))

def test_dashboard_alternative(self):
"""
Expand All @@ -189,18 +210,25 @@ def test_dashboard_alternative(self):

average_labels = ["Avg.", "Mean", "Second Quartile"]

expected_values = ('1', '2', '3', '4', 'B')

for step_num, step in enumerate(steps):
mcq_rows = step.find_elements_by_css_selector('tr:not(.avg-row)')
self.assertTrue(2 <= len(mcq_rows) <= 3)
for mcq in mcq_rows:
value = mcq.find_element_by_css_selector('td.value')
self.assertEqual(value.text, '')
cell = mcq.find_element_by_css_selector('td.value')
visible_text, screen_reader_text = self._get_cell_contents(cell)
# this dashboard configured to not show numbers
self.assertEqual(visible_text, '')
# but screen reader content still added
self.assertIn(screen_reader_text, map(self._format_sr_text, expected_values))
# Check the average:
avg_row = step.find_element_by_css_selector('tr.avg-row')
left_col = avg_row.find_element_by_css_selector('.desc')
self.assertEqual(left_col.text, average_labels[step_num])
right_col = avg_row.find_element_by_css_selector('.value')
self.assertEqual(right_col.text, "")
expected_average = {0: "2", 1: "3", 2: "1"}[step_num]
self._assert_cell_contents(right_col, '', self._format_sr_text(expected_average))

def test_dashboard_exclude_questions(self):
"""
Expand All @@ -214,22 +242,25 @@ def test_dashboard_exclude_questions(self):
dashboard = self.browser.find_element_by_css_selector('.pb-dashboard')
steps = dashboard.find_elements_by_css_selector('tbody')
self.assertEqual(len(steps), 3)
expected_values = ('1', '2', '3', '4')

lengths = [1, 2, 1]

for step_num, step in enumerate(steps):
mcq_rows = step.find_elements_by_css_selector('tr:not(.avg-row)')
self.assertEqual(len(mcq_rows), lengths[step_num])
for mcq in mcq_rows:
value = mcq.find_element_by_css_selector('td.value')
self.assertIn(value.text, ('1', '2', '3', '4'))
cell = mcq.find_element_by_css_selector('td.value')
visible_text, screen_reader_text = self._get_cell_contents(cell)
self.assertIn(visible_text, expected_values)
self.assertIn(screen_reader_text, map(self._format_sr_text, expected_values))
# Check the average:
avg_row = step.find_element_by_css_selector('tr.avg-row')
left_col = avg_row.find_element_by_css_selector('.desc')
self.assertEqual(left_col.text, "Average")
right_col = avg_row.find_element_by_css_selector('.value')
expected_average = {0: "1", 1: "3", 2: "1"}[step_num]
self.assertEqual(right_col.text, expected_average)
self._assert_cell_contents(right_col, expected_average, self._format_sr_text(expected_average))

def test_dashboard_malformed_exclude_questions(self):
"""
Expand All @@ -244,18 +275,22 @@ def test_dashboard_malformed_exclude_questions(self):
steps = dashboard.find_elements_by_css_selector('tbody')
self.assertEqual(len(steps), 3)

expected_values = ('1', '2', '3', '4')

lengths = [3, 2, 1]

for step_num, step in enumerate(steps):
mcq_rows = step.find_elements_by_css_selector('tr:not(.avg-row)')
self.assertEqual(len(mcq_rows), lengths[step_num])
for mcq in mcq_rows:
value = mcq.find_element_by_css_selector('td.value')
self.assertIn(value.text, ('1', '2', '3', '4'))
cell = mcq.find_element_by_css_selector('td.value')
visible_text, screen_reader_text = self._get_cell_contents(cell)
self.assertIn(visible_text, expected_values)
self.assertIn(screen_reader_text, map(self._format_sr_text, expected_values))
# Check the average:
avg_row = step.find_element_by_css_selector('tr.avg-row')
left_col = avg_row.find_element_by_css_selector('.desc')
self.assertEqual(left_col.text, "Average")
right_col = avg_row.find_element_by_css_selector('.value')
expected_average = {0: "2", 1: "3", 2: "1"}[step_num]
self.assertEqual(right_col.text, expected_average)
self._assert_cell_contents(right_col, expected_average, self._format_sr_text(expected_average))
4 changes: 4 additions & 0 deletions run_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
import os
import sys

import logging

if __name__ == "__main__":
# Use the workbench settings file:
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "workbench.settings")
Expand All @@ -19,6 +21,8 @@
from django.conf import settings
settings.INSTALLED_APPS += ("problem_builder", )

logging.disable(logging.CRITICAL)

from django.core.management import execute_from_command_line
args = sys.argv[1:]
paths = [arg for arg in args if arg[0] != '-']
Expand Down