Commit 8ac56fc2 by Jillian Vogel

Improves the accessibility of the Problem Builder, Step Builder, and their related XBlocks.

* Moves the choice-label <input> elements inside of their <label> tags.
* Wraps long-answer and slider question text in a <label> tag.
* Uses <th> instead of <td> elements inside <thead>, for the instructor tool and plot preview tables.
* Links the feedback, and choice-tip text with the choice labels via aria-describedby.
* Wraps the choice-result feedback icons inside the label elements, to match CAPA a11y design.
* Adds translated, aria-visible labels for the "correct answer/choice" checkmark and "incorrect answer/choice" X.
* Adds the 'aria-live="polite"' attribute to the divs whose content changes dynamically.
* Removes block-level children from <legend>, placing the h3.question-title outside the <fieldset>.
* Fixes tests broken by above commits
* Adds tests for new aria-label attributes..
parent efe6f231
...@@ -52,7 +52,9 @@ ...@@ -52,7 +52,9 @@
} }
.data-export-results thead { .data-export-results thead {
border-bottom: 2px solid #999; border-bottom: 2px solid #999;
white-space: nowrap;
} }
.data-export-results th,
.data-export-results td { .data-export-results td {
border-left: 1px solid #999; border-left: 1px solid #999;
padding: 5px; padding: 5px;
......
...@@ -9,12 +9,14 @@ ...@@ -9,12 +9,14 @@
border-bottom: 2px solid #999; border-bottom: 2px solid #999;
background-color: #ddd; background-color: #ddd;
font-weight: bold; font-weight: bold;
white-space: nowrap;
} }
.sb-plot tr:nth-child(even) { .sb-plot tr:nth-child(even) {
background-color: #eee; background-color: #eee;
} }
.sb-plot th,
.sb-plot td { .sb-plot td {
border-left: 1px solid #999; border-left: 1px solid #999;
padding: 5px; padding: 5px;
......
...@@ -41,7 +41,7 @@ ...@@ -41,7 +41,7 @@
margin-top: 10px; margin-top: 10px;
} }
.mentoring h3 { .xblock .mentoring h3 {
margin-top: 0px; margin-top: 0px;
margin-bottom: 7px; margin-bottom: 7px;
} }
......
...@@ -8,8 +8,8 @@ ...@@ -8,8 +8,8 @@
} }
.mentoring .questionnaire .choice-result { .mentoring .questionnaire .choice-result {
display: table-cell; display: inline-block;
width: 40px; width: 34px;
vertical-align: top; vertical-align: top;
cursor: pointer; cursor: pointer;
float: none; float: none;
...@@ -78,16 +78,12 @@ ...@@ -78,16 +78,12 @@
} }
.mentoring .choices-list .choice-selector { .mentoring .choices-list .choice-selector {
display: table-cell; display: inline-block;
vertical-align: top;
width: 28px;
padding-top: 3px;
padding-right: 5px;
} }
.mentoring .choice-tips-container,
.mentoring .choice-label { .mentoring .choice-label {
display: table-cell; display: table-cell;
vertical-align: top; vertical-align: top;
line-height: 1.3; width: 50%;
padding-top: 4px;
} }
...@@ -35,9 +35,11 @@ function AnswerBlock(runtime, element) { ...@@ -35,9 +35,11 @@ function AnswerBlock(runtime, element) {
if (result.status) { if (result.status) {
if (result.status === "correct") { if (result.status === "correct") {
checkmark.addClass('checkmark-correct icon-ok fa-check'); checkmark.addClass('checkmark-correct icon-ok fa-check');
checkmark.attr('aria-label', checkmark.data('label_correct'));
} }
else { else {
checkmark.addClass('checkmark-incorrect icon-exclamation fa-exclamation'); checkmark.addClass('checkmark-incorrect icon-exclamation fa-exclamation');
checkmark.attr('aria-label', checkmark.data('label_incorrect'));
} }
} }
}, },
......
...@@ -15,6 +15,7 @@ function MentoringAssessmentView(runtime, element, mentoring) { ...@@ -15,6 +15,7 @@ function MentoringAssessmentView(runtime, element, mentoring) {
checkmark.removeClass('checkmark-partially-correct icon-ok fa-check'); checkmark.removeClass('checkmark-partially-correct icon-ok fa-check');
checkmark.removeClass('checkmark-incorrect icon-exclamation fa-exclamation'); checkmark.removeClass('checkmark-incorrect icon-exclamation fa-exclamation');
checkmark.removeClass('checkmark-clickable'); checkmark.removeClass('checkmark-clickable');
checkmark.attr('aria-label', '');
checkmark.off('click'); checkmark.off('click');
// Clear all selections // Clear all selections
...@@ -266,10 +267,13 @@ function MentoringAssessmentView(runtime, element, mentoring) { ...@@ -266,10 +267,13 @@ function MentoringAssessmentView(runtime, element, mentoring) {
if (response.completed === 'partial') { if (response.completed === 'partial') {
checkmark.addClass('checkmark-partially-correct icon-ok fa-check'); checkmark.addClass('checkmark-partially-correct icon-ok fa-check');
checkmark.attr('aria-label', checkmark.data('label_partial'));
} else if (response.completed === 'correct') { } else if (response.completed === 'correct') {
checkmark.addClass('checkmark-correct icon-ok fa-check'); checkmark.addClass('checkmark-correct icon-ok fa-check');
checkmark.attr('aria-label', checkmark.data('label_correct'));
} else { } else {
checkmark.addClass('checkmark-incorrect icon-exclamation fa-exclamation'); checkmark.addClass('checkmark-incorrect icon-exclamation fa-exclamation');
checkmark.attr('aria-label', checkmark.data('label_incorrect'));
} }
submitDOM.attr('disabled', 'disabled'); submitDOM.attr('disabled', 'disabled');
......
...@@ -82,10 +82,13 @@ function MentoringWithStepsBlock(runtime, element) { ...@@ -82,10 +82,13 @@ function MentoringWithStepsBlock(runtime, element) {
function showFeedback(response) { function showFeedback(response) {
if (response.step_status === 'correct') { if (response.step_status === 'correct') {
checkmark.addClass('checkmark-correct icon-ok fa-check'); checkmark.addClass('checkmark-correct icon-ok fa-check');
checkmark.attr('aria-label', checkmark.data('label_correct'));
} else if (response.step_status === 'partial') { } else if (response.step_status === 'partial') {
checkmark.addClass('checkmark-partially-correct icon-ok fa-check'); checkmark.addClass('checkmark-partially-correct icon-ok fa-check');
checkmark.attr('aria-label', checkmark.data('label_partial'));
} else { } else {
checkmark.addClass('checkmark-incorrect icon-exclamation fa-exclamation'); checkmark.addClass('checkmark-incorrect icon-exclamation fa-exclamation');
checkmark.attr('aria-label', checkmark.data('label_incorrect'));
} }
var step = getActiveStep(); var step = getActiveStep();
if (typeof step.showFeedback == 'function') { if (typeof step.showFeedback == 'function') {
...@@ -166,6 +169,7 @@ function MentoringWithStepsBlock(runtime, element) { ...@@ -166,6 +169,7 @@ function MentoringWithStepsBlock(runtime, element) {
checkmark.removeClass('checkmark-correct icon-ok fa-check'); checkmark.removeClass('checkmark-correct icon-ok fa-check');
checkmark.removeClass('checkmark-partially-correct icon-ok fa-check'); checkmark.removeClass('checkmark-partially-correct icon-ok fa-check');
checkmark.removeClass('checkmark-incorrect icon-exclamation fa-exclamation'); checkmark.removeClass('checkmark-incorrect icon-exclamation fa-exclamation');
checkmark.attr('aria-label', '');
hideAllSteps(); hideAllSteps();
hideReviewStep(); hideReviewStep();
attemptsDOM.html(''); attemptsDOM.html('');
......
...@@ -88,6 +88,7 @@ function MessageView(element, mentoring) { ...@@ -88,6 +88,7 @@ function MessageView(element, mentoring) {
this.allResultsDOM.removeClass( this.allResultsDOM.removeClass(
'checkmark-incorrect icon-exclamation fa-exclamation checkmark-correct icon-ok fa-check' 'checkmark-incorrect icon-exclamation fa-exclamation checkmark-correct icon-ok fa-check'
); );
this.allResultsDOM.attr('aria-label', '');
} }
}; };
} }
...@@ -136,9 +137,11 @@ function MCQBlock(runtime, element) { ...@@ -136,9 +137,11 @@ function MCQBlock(runtime, element) {
if (result.status === "correct") { if (result.status === "correct") {
choiceInputDOM.addClass('correct'); choiceInputDOM.addClass('correct');
choiceResultDOM.addClass('checkmark-correct icon-ok fa-check'); choiceResultDOM.addClass('checkmark-correct icon-ok fa-check');
choiceResultDOM.attr('aria-label', choiceResultDOM.data('label_correct'));
} else { } else {
choiceDOM.addClass('incorrect'); choiceDOM.addClass('incorrect');
choiceResultDOM.addClass('checkmark-incorrect icon-exclamation fa-exclamation'); choiceResultDOM.addClass('checkmark-incorrect icon-exclamation fa-exclamation');
choiceResultDOM.attr('aria-label', choiceResultDOM.data('label_incorrect'));
} }
choiceResultDOM.off('click').on('click', function() { choiceResultDOM.off('click').on('click', function() {
if (choiceTipsDOM.html() !== '') { if (choiceTipsDOM.html() !== '') {
...@@ -230,9 +233,11 @@ function MRQBlock(runtime, element) { ...@@ -230,9 +233,11 @@ function MRQBlock(runtime, element) {
if (choice.completed) { if (choice.completed) {
choiceDOM.addClass('correct'); choiceDOM.addClass('correct');
choiceResultDOM.addClass('checkmark-correct icon-ok fa-check'); choiceResultDOM.addClass('checkmark-correct icon-ok fa-check');
choiceResultDOM.attr('aria-label', choiceResultDOM.data('label_correct'));
} else if (!choice.completed) { } else if (!choice.completed) {
choiceDOM.addClass('incorrect'); choiceDOM.addClass('incorrect');
choiceResultDOM.addClass('checkmark-incorrect icon-exclamation fa-exclamation'); choiceResultDOM.addClass('checkmark-incorrect icon-exclamation fa-exclamation');
choiceResultDOM.attr('aria-label', choiceResultDOM.data('label_incorrect'));
} }
mentoring.setContent(choiceTipsDOM, choice.tips); mentoring.setContent(choiceTipsDOM, choice.tips);
......
.themed-xblock.mentoring .questionnaire .choice-result {
display: table-cell;
}
.themed-xblock.mentoring .choice-result::before { .themed-xblock.mentoring .choice-result::before {
content: ""; content: "";
display: block; display: block;
...@@ -40,8 +36,6 @@ div.course-wrapper section.course-content .themed-xblock.mentoring p:empty { ...@@ -40,8 +36,6 @@ div.course-wrapper section.course-content .themed-xblock.mentoring p:empty {
.themed-xblock.mentoring .choice-label { .themed-xblock.mentoring .choice-label {
display: table-cell; display: table-cell;
vertical-align: middle; vertical-align: middle;
width: 100%;
padding-bottom: 10px;
} }
.themed-xblock.mentoring .choice-label span.low { .themed-xblock.mentoring .choice-label span.low {
......
{% load i18n %}
<div class="xblock-answer" data-completed="{{ self.completed }}"> <div class="xblock-answer" data-completed="{{ self.completed }}">
{% if not hide_header %}<h3 class="question-title">{{ self.display_name_with_default }}</h3>{% endif %} {% if not hide_header %}<h3 class="question-title">{{ self.display_name_with_default }}</h3>{% endif %}
<p>{{ self.question|safe }}</p> <label><p>{{ self.question|safe }}</p>
<textarea <textarea
class="answer editable" cols="50" rows="10" name="input" class="answer editable" cols="50" rows="10" name="input"
data-min_characters="{{ self.min_characters }}" data-min_characters="{{ self.min_characters }}"
>{{ self.student_input }}</textarea> >{{ self.student_input }}</textarea>
</label>
<div style="display: none;" class="orig-student-answer">{{ self.student_input }}</div> <!-- To detect edits --> <div style="display: none;" class="orig-student-answer">{{ self.student_input }}</div> <!-- To detect edits -->
<span class="answer-checkmark fa icon-2x"></span> <span class="answer-checkmark fa icon-2x" aria-label=""
data-label_correct="{% trans "Correct" %}" data-label_incorrect="{% trans "Incorrect" %}"></span>
</div> </div>
...@@ -59,35 +59,37 @@ ...@@ -59,35 +59,37 @@
</div> </div>
</div> </div>
<div id="results" class="data-export-results"> <div id="results-wrapper" aria-live="polite">
<table> <div id="results" class="data-export-results">
<thead> <table>
<tr> <thead>
<td>{% trans "Section" %}</td> <tr>
<td>{% trans "Subsection" %}</td> <th>{% trans "Section" %}</th>
<td>{% trans "Unit" %}</td> <th>{% trans "Subsection" %}</th>
<td>{% trans "Type" %}</td> <th>{% trans "Unit" %}</th>
<td>{% trans "Question" %}</td> <th>{% trans "Type" %}</th>
<td>{% trans "Answer" %}</td> <th>{% trans "Question" %}</th>
<td>{% trans "Username" %}</td> <th>{% trans "Answer" %}</th>
</tr> <th>{% trans "Username" %}</th>
</thead> </tr>
<tbody></tbody> </thead>
</table> <tbody></tbody>
<div class="data-export-info"></div> </table>
<div class="data-export-result-actions"> <div class="data-export-info"></div>
<button id="first-page">First</button> <div class="data-export-result-actions">
<button id="prev-page">Prev</button> <button id="first-page">First</button>
<span id="current-page"></span>/<span id="total-pages"></span> <button id="prev-page">Prev</button>
<button id="next-page">Next</button> <span id="current-page"></span>/<span id="total-pages"></span>
<button id="last-page">Last</button> <button id="next-page">Next</button>
<button id="last-page">Last</button>
</div>
</div> </div>
</div>
<div class="data-export-status"></div> <div class="data-export-status"></div>
<div class="data-export-actions"> <div class="data-export-actions">
<button class="data-export-download">{% trans "Download as CSV" %}</button> <button class="data-export-download">{% trans "Download as CSV" %}</button>
<button class="data-export-cancel">{% trans "Cancel search" %}</button> <button class="data-export-cancel">{% trans "Cancel search" %}</button>
<button class="data-export-delete">{% trans "Delete results" %}</button> <button class="data-export-delete">{% trans "Delete results" %}</button>
</div>
</div> </div>
<fieldset class="choices questionnaire"> {% load i18n %}
<legend class="question"> {% if not hide_header %}
{% if not hide_header %}<h3 class="question-title">{{ self.display_name_with_default }}</h3>{% endif %} <h3 class="question-title" id="heading_{{ self.html_id }}">{{ self.display_name_with_default }}</h3>
<p>{{ self.question|safe }}</p> {% endif %}
</legend> <fieldset class="choices questionnaire" id="{{ self.html_id }}">
<legend class="question field-group-hd">{{ self.question|safe }}</legend>
<div class="choices-list"> <div class="choices-list">
{% for choice in custom_choices %} {% for choice in custom_choices %}
<div class="choice"> <div class="choice" aria-live="polite" aria-atomic="true">
<div class="choice-result fa icon-2x"></div> <label class="choice-label"
<div class="choice-selector"> aria-describedby="feedback_{{ self.html_id }} choice_tips_{{ self.html_id }}-{{ forloop.counter }}">
<input id="choice-{{ self.html_id }}-{{ forloop.counter }}" type="radio" <div class="choice-result fa icon-2x" aria-label=""
name="{{ self.name }}" value="{{ choice.value }}" data-label_correct="{% trans "Correct" %}" data-label_incorrect="{% trans "Incorrect" %}"></div>
{% if self.student_choice == choice.value and not hide_prev_answer %} checked{% endif %} <div class="choice-selector">
/> <input type="radio" name="{{ self.name }}" value="{{ choice.value }}"
</div> {% if self.student_choice == choice.value and not hide_prev_answer %} checked{% endif %}
<label class="choice-label" for="choice-{{ self.html_id }}-{{ forloop.counter }}"> />
{{ choice.content|safe }} </div>
{{ choice.content|safe }}
</label> </label>
<div class="choice-tips-container"> <div class="choice-tips-container">
<div class="choice-tips"></div> <div class="choice-tips" id="choice_tips_{{ self.html_id }}-{{ forloop.counter }}"></div>
</div> </div>
</div> </div>
{% endfor %} {% endfor %}
<div class="feedback"></div> <div class="feedback" id="feedback_{{ self.html_id }}"></div>
</div> </div>
</fieldset> </fieldset>
...@@ -14,7 +14,9 @@ ...@@ -14,7 +14,9 @@
{% endfor %} {% endfor %}
<div class="submit"> <div class="submit">
<span class="step-overall-checkmark fa icon-2x fa-fw"></span> <span class="step-overall-checkmark fa icon-2x fa-fw" aria-label=""
data-label_correct="{% trans "Correct" %}" data-label_incorrect="{% trans "Incorrect" %}"
data-label_partial="{% trans "Partially correct" %}"></span>
<input type="button" class="input-main" value="Submit" disabled="disabled" /> <input type="button" class="input-main" value="Submit" disabled="disabled" />
<input type="button" class="input-next" value="Next Step" disabled="disabled" /> <input type="button" class="input-next" value="Next Step" disabled="disabled" />
<input type="button" class="input-review" value="Review grade" disabled="disabled" /> <input type="button" class="input-review" value="Review grade" disabled="disabled" />
......
<fieldset class="choices questionnaire" data-hide_results="{{self.hide_results}}" data-hide_prev_answer="{{hide_prev_answer}}"> {% load i18n %}
<legend class="question"> {% if not hide_header %}
{% if not hide_header %}<h3 class="question-title">{{ self.display_name_with_default }}</h3>{% endif %} <h3 class="question-title" id="heading_{{ self.html_id }}">{{ self.display_name_with_default }}</h3>
<p>{{ self.question|safe }}</p> {% endif %}
</legend> <fieldset class="choices questionnaire" id="{{ self.html_id }}"
data-hide_results="{{ self.hide_results }}" data-hide_prev_answer="{{ hide_prev_answer }}">
<legend class="question field-group-hd">{{ self.question|safe }}</legend>
<div class="choices-list"> <div class="choices-list">
{% for choice in custom_choices %} {% for choice in custom_choices %}
<div class="choice"> <div class="choice" aria-live="polite" aria-atomic="true">
<div class="choice-result fa icon-2x"></div> <div class="choice-result fa icon-2x" id="result_{{ self.html_id }}-{{ forloop.counter }}" aria-label=""
<div class="choice-selector"> data-label_correct="{% trans "Correct" %}" data-label_incorrect="{% trans "Incorrect" %}"></div>
<input id="choice-{{ self.html_id }}-{{ forloop.counter }}" type="checkbox" <label class="choice-label" aria-describedby="feedback_{{ self.html_id }}
name="{{ self.name }}" value="{{ choice.value }}" result_{{ self.html_id }}-{{ forloop.counter }}
{% if choice.value in self.student_choices and not hide_prev_answer %} checked{% endif %} choice_tips_{{ self.html_id }}-{{ forloop.counter }}">
/> <div class="choice-selector">
</div> <input type="checkbox" name="{{ self.name }}" value="{{ choice.value }}"
<label class="choice-label" for="choice-{{ self.html_id }}-{{ forloop.counter }}"> {% if choice.value in self.student_choices and not hide_prev_answer %} checked{% endif %}
/>
</div>
{{ choice.content|safe }} {{ choice.content|safe }}
</label> </label>
<div class="choice-tips-container"> <div class="choice-tips-container">
<div class="choice-tips"></div> <div class="choice-tips" id="choice_tips_{{ self.html_id }}-{{ forloop.counter }}"></div>
</div> </div>
</div> </div>
{% endfor %} {% endfor %}
<div class="feedback"></div> <div class="feedback" id="feedback_{{ self.html_id }}"></div>
</div> </div>
</fieldset> </fieldset>
...@@ -6,9 +6,9 @@ ...@@ -6,9 +6,9 @@
<table> <table>
<thead> <thead>
<tr> <tr>
<td>{% trans "Claim" %}</td> <th>{% trans "Claim" %}</th>
<td>{% trans "Question 1" %}</td> <th>{% trans "Question 1" %}</th>
<td>{% trans "Question 2" %}</td> <th>{% trans "Question 2" %}</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
......
<fieldset class="rating questionnaire"> {% load i18n %}
<legend class="question"> {% if not hide_header %}
{% if not hide_header %}<h3 class="question-title">{{ self.display_name_with_default }}</h3>{% endif %} <h3 class="question-title" id="heading_{{ self.html_id }}">{{ self.display_name_with_default }}</h3>
<p>{{ self.question|safe }}</p> {% endif %}
</legend> <fieldset class="rating questionnaire" id="{{ self.html_id }}">
<legend class="question field-group-hd">{{ self.question|safe }}</legend>
<div class="choices-list"> <div class="choices-list">
{% for i in '12345' %} {% for i in '12345' %}
<div class="choice"> <div class="choice" aria-live="polite" aria-atomic="true">
<div class="choice-result fa icon-2x"></div> <label class="choice-label"
aria-describedby="feedback_{{ self.html_id }} choice_tips_{{ self.html_id }}-{{ i }}">
<div class="choice-result fa icon-2x" aria-label=""
data-label_correct="{% trans "Correct" %}" data-label_incorrect="{% trans "Incorrect" %}"></div>
<div class="choice-selector"> <div class="choice-selector">
<input id="choice-{{ self.html_id }}-{{i}}" type="radio" <input type="radio" name="{{ self.name }}" value="{{i}}"
name="{{ self.name }}" value="{{i}}"
{% if self.student_choice == i and not hide_prev_answer %} checked{%else%} data-student-choice='{{self.student_choice}}'{% endif %} {% if self.student_choice == i and not hide_prev_answer %} checked{%else%} data-student-choice='{{self.student_choice}}'{% endif %}
/> />
</div>
<label class="choice-label" for="choice-{{ self.html_id }}-{{i}}">
{{i}} {{i}}
{% if i == '1' %} - {{ self.low|safe }}{% endif %} {% if i == '1' %} - {{ self.low|safe }}{% endif %}
{% if i == '5' %} - {{ self.high|safe }}{% endif %} {% if i == '5' %} - {{ self.high|safe }}{% endif %}
</label>
<div class="choice-tips-container">
<div class="choice-tips"></div>
</div> </div>
</label>
<div class="choice-tips-container">
<div class="choice-tips" id="choice_tips_{{ self.html_id }}-{{ i }}"></div>
</div> </div>
</div>
{% endfor %} {% endfor %}
{% for choice in custom_choices %} {% for choice in custom_choices %}
<div class="choice"> <div class="choice" aria-live="polite" aria-atomic="true">
<div class="choice-result fa icon-2x"></div> <label class="choice-label"
<div class="choice-selector"> aria-describedby="feedback_{{ self.html_id }} choice_tips_{{ self.html_id }}-{{ forloop.counter }}">
<input id="choice-{{ self.html_id }}-custom{{ forloop.counter }}" type="radio" <div class="choice-result fa icon-2x" aria-label=""
name="{{ self.name }}" value="{{ choice.value }}" data-label_correct="{% trans "Correct" %}" data-label_incorrect="{% trans "Incorrect" %}"></div>
{% if self.student_choice == choice.value and not hide_prev_answer %} checked{%else%} data-student-choice='{{self.student_choice}}'{% endif %} <div class="choice-selector">
/> <input type="radio" name="{{ self.name }}" value="{{ choice.value }}"
</div> {% if self.student_choice == choice.value and not hide_prev_answer %} checked{%else%} data-student-choice='{{self.student_choice}}'{% endif %}
<label class="choice-label" for="choice-{{ self.html_id }}-custom{{ forloop.counter }}"> />
{{ choice.content|safe }} </div>
{{ choice.content|safe }}
</label> </label>
<div class="choice-tips-container"> <div class="choice-tips-container">
<div class="choice-tips"></div> <div class="choice-tips" id="choice_tips_{{ self.html_id }}-{{ forloop.counter }}"></div>
</div> </div>
</div> </div>
{% endfor %} {% endfor %}
<div class="feedback"></div> <div class="feedback" id="feedback_{{ self.html_id }}"></div>
</div> </div>
</fieldset> </fieldset>
{% load i18n %}
<div class="xblock-pb-slider"> <div class="xblock-pb-slider">
{% if not hide_header %}<h3 class="question-title">{{ title }}</h3>{% endif %} {% if not hide_header %}<h3 class="question-title">{{ title }}</h3>{% endif %}
{% if question %}
<p><label for="{{slider_id}}">{{ question|safe }} <span class="sr">({{instructions_string}})</span></label></p>
{% endif %}
<div class="pb-slider-box clearfix"> <div class="pb-slider-box clearfix">
<input type="range" <p><label>{{ question|safe }} <span class="sr">({{instructions_string}})</span>
id="{{slider_id}}" class="pb-slider-range" min="0" max="100" step="1" value="{{initial_value}}" <input type="range" id="{{ slider_id }}" class="pb-slider-range"
{% if not question %}aria-label="{{instructions_string}}"{% endif %} min="0" max="100" step="1" value="{{initial_value}}"
> />
</label></p>
<div class="pb-slider-min-label" aria-hidden="true">{{ min_label }}</div> <div class="pb-slider-min-label" aria-hidden="true">{{ min_label }}</div>
<div class="pb-slider-max-label" aria-hidden="true">{{ max_label }}</div> <div class="pb-slider-max-label" aria-hidden="true">{{ max_label }}</div>
</div> </div>
<div class="clearfix"> <div class="clearfix">
<span class="submit-result fa icon-2x checkmark-correct icon-ok fa-check" style="visibility: hidden;"></span> <span class="submit-result fa icon-2x checkmark-correct icon-ok fa-check"
style="visibility: hidden;" aria-label="{% trans "Complete" %}"></span>
</div> </div>
</div> </div>
<div class="sb-step" data-next-button-label="{{ self.next_button_label }}" {% if self.has_question %} data-has-question="true" {% endif %}> <div class="sb-step" aria-live="polite"
data-next-button-label="{{ self.next_button_label }}" {% if self.has_question %} data-has-question="true" {% endif %}>
{% if show_title %} {% if show_title %}
<div class="title"> <div class="title">
<h3> <h3>
......
...@@ -84,7 +84,7 @@ class ProblemBuilderQuestionnaireBlockTest(ProblemBuilderBaseTest): ...@@ -84,7 +84,7 @@ class ProblemBuilderQuestionnaireBlockTest(ProblemBuilderBaseTest):
return questionnaire.find_elements_by_css_selector(".choices-list .choice")[choice_index] return questionnaire.find_elements_by_css_selector(".choices-list .choice")[choice_index]
def _get_answer_checkmark(self, answer): def _get_answer_checkmark(self, answer):
return answer.find_element_by_xpath("parent::*").find_element_by_css_selector(".answer-checkmark") return answer.find_element_by_xpath("ancestor::node()[3]").find_element_by_css_selector(".answer-checkmark")
def _get_messages_element(self, mentoring): def _get_messages_element(self, mentoring):
return mentoring.find_element_by_css_selector('.messages') return mentoring.find_element_by_css_selector('.messages')
...@@ -103,15 +103,25 @@ class ProblemBuilderQuestionnaireBlockTest(ProblemBuilderBaseTest): ...@@ -103,15 +103,25 @@ class ProblemBuilderQuestionnaireBlockTest(ProblemBuilderBaseTest):
def _assert_answer(self, answer, results_shown=True): def _assert_answer(self, answer, results_shown=True):
self.assertEqual(answer.get_attribute('value'), 'This is the answer') self.assertEqual(answer.get_attribute('value'), 'This is the answer')
answer_checkmark = self._get_answer_checkmark(answer) answer_checkmark = self._get_answer_checkmark(answer)
self._assert_checkmark(answer_checkmark, shown=results_shown, checkmark_class='checkmark-correct') self._assert_checkmark(answer_checkmark, shown=results_shown)
def _assert_checkmark(self, checkmark, shown=True, checkmark_class=None): def _assert_checkmark(self, checkmark, correct=True, shown=True):
result_classes = checkmark.get_attribute('class').split() result_classes = checkmark.get_attribute('class').split()
result_label = checkmark.get_attribute('aria-label').strip()
if shown: if shown:
if correct:
checkmark_class = 'checkmark-correct'
checkmark_label = 'Correct'
else:
checkmark_class = 'checkmark-incorrect'
checkmark_label = 'Incorrect'
self.assertTrue(checkmark.is_displayed()) self.assertTrue(checkmark.is_displayed())
self.assertIn(checkmark_class, result_classes) self.assertIn(checkmark_class, result_classes)
self.assertEquals(checkmark_label, result_label)
else: else:
self.assertFalse(checkmark.is_displayed()) self.assertFalse(checkmark.is_displayed())
self.assertEquals('', result_label)
def _assert_mcq(self, mcq, previous_answer_shown=True): def _assert_mcq(self, mcq, previous_answer_shown=True):
if previous_answer_shown: if previous_answer_shown:
...@@ -143,8 +153,7 @@ class ProblemBuilderQuestionnaireBlockTest(ProblemBuilderBaseTest): ...@@ -143,8 +153,7 @@ class ProblemBuilderQuestionnaireBlockTest(ProblemBuilderBaseTest):
choice_result.click() choice_result.click()
feedback_popup = choice.find_element_by_css_selector(".choice-tips") feedback_popup = choice.find_element_by_css_selector(".choice-tips")
checkmark_class = 'checkmark-correct' if success else 'checkmark-incorrect' self._assert_checkmark(choice_result, correct=success)
self._assert_checkmark(choice_result, checkmark_class=checkmark_class)
self.assertTrue(feedback_popup.is_displayed()) self.assertTrue(feedback_popup.is_displayed())
self.assertEqual(feedback_popup.text, expected_text) self.assertEqual(feedback_popup.text, expected_text)
......
...@@ -52,15 +52,19 @@ class QuestionnaireBlockTest(MentoringBaseTest): ...@@ -52,15 +52,19 @@ class QuestionnaireBlockTest(MentoringBaseTest):
# Initial MCQ status # Initial MCQ status
mentoring = self.go_to_page('Mcq 1') mentoring = self.go_to_page('Mcq 1')
mcq1 = mentoring.find_element_by_css_selector('fieldset.choices') mcq1 = mentoring.find_element_by_css_selector('fieldset.choices')
mcq1_heading = mentoring.find_element_by_id('heading_' + mcq1.get_attribute('id'))
mcq2 = mentoring.find_element_by_css_selector('fieldset.rating') mcq2 = mentoring.find_element_by_css_selector('fieldset.rating')
mcq2_heading = mentoring.find_element_by_id('heading_' + mcq2.get_attribute('id'))
messages = mentoring.find_element_by_css_selector('.messages') messages = mentoring.find_element_by_css_selector('.messages')
submit = mentoring.find_element_by_css_selector('.submit input.input-main') submit = mentoring.find_element_by_css_selector('.submit input.input-main')
self.assert_messages_empty(messages) self.assert_messages_empty(messages)
self.assertFalse(submit.is_enabled()) self.assertFalse(submit.is_enabled())
self.assertEqual(mcq1.find_element_by_css_selector('legend').text, 'Question 1\nDo you like this MCQ?') self.assertEqual(mcq1_heading.text, 'Question 1')
self.assertEqual(mcq2.find_element_by_css_selector('legend').text, 'Question 2\nHow do you rate this MCQ?') self.assertEqual(mcq1.find_element_by_css_selector('legend').text, 'Do you like this MCQ?')
self.assertEqual(mcq2_heading.text, 'Question 2')
self.assertEqual(mcq2.find_element_by_css_selector('legend').text, 'How do you rate this MCQ?')
mcq1_choices = mcq1.find_elements_by_css_selector('.choices .choice') mcq1_choices = mcq1.find_elements_by_css_selector('.choices .choice')
mcq2_choices = mcq2.find_elements_by_css_selector('.rating .choice') mcq2_choices = mcq2.find_elements_by_css_selector('.rating .choice')
...@@ -141,8 +145,8 @@ class QuestionnaireBlockTest(MentoringBaseTest): ...@@ -141,8 +145,8 @@ class QuestionnaireBlockTest(MentoringBaseTest):
# Clicking outside the tips should hide the tips and clear the with-tips class. # Clicking outside the tips should hide the tips and clear the with-tips class.
mcq1_tips = mcq1.find_element_by_css_selector(".choice-tips .tip p") mcq1_tips = mcq1.find_element_by_css_selector(".choice-tips .tip p")
mcq2_tips = mcq2.find_element_by_css_selector(".choice-tips .tip p") mcq2_tips = mcq2.find_element_by_css_selector(".choice-tips .tip p")
mcq1.find_element_by_css_selector('.mentoring .question-title').click() mcq1_heading.click()
mcq2.find_element_by_css_selector('.mentoring .question-title').click() mcq2_heading.click()
mcq1_tip_containers = mcq1.find_elements_by_css_selector('.choice-tips-container.with-tips') mcq1_tip_containers = mcq1.find_elements_by_css_selector('.choice-tips-container.with-tips')
mcq2_tip_containers = mcq2.find_elements_by_css_selector('.choice-tips-container.with-tips') mcq2_tip_containers = mcq2.find_elements_by_css_selector('.choice-tips-container.with-tips')
self.assertEqual(len(mcq1_tip_containers), 0) self.assertEqual(len(mcq1_tip_containers), 0)
...@@ -161,7 +165,10 @@ class QuestionnaireBlockTest(MentoringBaseTest): ...@@ -161,7 +165,10 @@ class QuestionnaireBlockTest(MentoringBaseTest):
self.assertFalse(submit.is_enabled()) self.assertFalse(submit.is_enabled())
mcq_legend = mcq.find_element_by_css_selector('legend') mcq_legend = mcq.find_element_by_css_selector('legend')
self.assertEqual(mcq_legend.text, 'Question\nWhat do you like in this MRQ?') self.assertEqual(mcq_legend.text, 'What do you like in this MRQ?')
mcq_heading = mentoring.find_element_by_id('heading_' + mcq.get_attribute('id'))
self.assertEqual(mcq_heading.text, 'Question')
mcq_choices = mcq.find_elements_by_css_selector('.choices .choice') mcq_choices = mcq.find_elements_by_css_selector('.choices .choice')
...@@ -193,8 +200,7 @@ class QuestionnaireBlockTest(MentoringBaseTest): ...@@ -193,8 +200,7 @@ class QuestionnaireBlockTest(MentoringBaseTest):
# this could be a list comprehension, but a bit complicated one - hence explicit loop # this could be a list comprehension, but a bit complicated one - hence explicit loop
for choice_wrapper in questionnaire.find_elements_by_css_selector(".choice"): for choice_wrapper in questionnaire.find_elements_by_css_selector(".choice"):
choice_label = choice_wrapper.find_element_by_css_selector("label") choice_label = choice_wrapper.find_element_by_css_selector("label")
result.append(choice_label.get_attribute('innerHTML').strip()) result.append(choice_label)
return result return result
@ddt.data( @ddt.data(
...@@ -202,9 +208,10 @@ class QuestionnaireBlockTest(MentoringBaseTest): ...@@ -202,9 +208,10 @@ class QuestionnaireBlockTest(MentoringBaseTest):
'Mcq With Html Choices' 'Mcq With Html Choices'
) )
def test_questionnaire_html_choices(self, page): def test_questionnaire_html_choices(self, page):
mentoring = self.go_to_page(page) mentoring = self.go_to_page(page)
question = mentoring.find_element_by_css_selector('legend p') question = mentoring.find_element_by_css_selector('fieldset legend')
self.assertIn( self.assertIn(
'What do <strong>you</strong> like in this ', 'What do <strong>you</strong> like in this ',
question.get_attribute('innerHTML').strip() question.get_attribute('innerHTML').strip()
...@@ -220,8 +227,12 @@ class QuestionnaireBlockTest(MentoringBaseTest): ...@@ -220,8 +227,12 @@ class QuestionnaireBlockTest(MentoringBaseTest):
'<span style="font-color:red">Its bugs</span>' '<span style="font-color:red">Its bugs</span>'
] ]
options = self._get_questionnaire_options(choices_list) # Ensure each questionnaire label contains the input item, and the expected option.
self.assertEqual(expected_options, options) labels = self._get_questionnaire_options(choices_list)
self.assertEquals(len(labels), len(expected_options))
for idx, label in enumerate(labels):
self.assertEquals(len(label.find_elements_by_tag_name('input')), 1)
self.assertIn(expected_options[idx], label.get_attribute('innerHTML').strip())
self.assert_messages_empty(messages) self.assert_messages_empty(messages)
......
...@@ -544,16 +544,24 @@ class StepBuilderTest(MentoringAssessmentBaseTest, MultipleSliderBlocksTestMixin ...@@ -544,16 +544,24 @@ class StepBuilderTest(MentoringAssessmentBaseTest, MultipleSliderBlocksTestMixin
choice = mcq.find_elements_by_css_selector(".choices-list .choice")[choice_index] choice = mcq.find_elements_by_css_selector(".choices-list .choice")[choice_index]
choice_result = choice.find_element_by_css_selector('.choice-result') choice_result = choice.find_element_by_css_selector('.choice-result')
feedback_popup = choice.find_element_by_css_selector(".choice-tips") feedback_popup = choice.find_element_by_css_selector(".choice-tips")
checkmark_class = 'checkmark-correct' if correct else 'checkmark-incorrect'
self.wait_until_visible(feedback_popup) self.wait_until_visible(feedback_popup)
self.assertEqual(feedback_popup.text, expected_text) self.assertEqual(feedback_popup.text, expected_text)
self.assert_choice_result(choice_result, checkmark_class=checkmark_class) self.assert_choice_result(choice_result, correct)
def assert_choice_result(self, choice_result, correct):
if correct:
checkmark_class = 'checkmark-correct'
checkmark_label = 'Correct'
else:
checkmark_class = 'checkmark-incorrect'
checkmark_label = 'Incorrect'
def assert_choice_result(self, choice_result, checkmark_class):
result_classes = choice_result.get_attribute('class').split() result_classes = choice_result.get_attribute('class').split()
result_label = choice_result.get_attribute('aria-label').strip()
self.wait_until_visible(choice_result) self.wait_until_visible(choice_result)
self.assertIn(checkmark_class, result_classes) self.assertIn(checkmark_class, result_classes)
self.assertEquals(checkmark_label, result_label)
def test_review_tips(self): def test_review_tips(self):
params = { params = {
......
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