MDL-81608 quiz: Adjust size and spacing for the quiz edit page buttons

* In order to meet WCAG 2.2 criterion for minimum target size and make
it easier for users who have mobility limitations to use the quiz
editing page, we need to use the Bootstrap icon button styles and
increase the size and the spacing for the following buttons:
  * Add/remove page break button
  * Lock/unlock question dependency button
* This commit also makes the icons within the buttons as decorative and
moves the accessible name to the button elements themselves via the
aria-label attribute.
* Adjust Behat tests for checking the dependency buttons.
* Add accessibility Behat coverage for the quiz edit page.
This commit is contained in:
Jun Pataleta
2026-03-10 17:55:03 +08:00
parent 166bd34db8
commit 2f460d01ea
11 changed files with 101 additions and 77 deletions
@@ -1001,9 +1001,21 @@ class edit_renderer extends \plugin_renderer_base {
if (!$structure->can_be_edited()) {
$disabled = 'disabled';
}
return html_writer::span($this->action_link($url, $image, null, ['title' => $title,
'class' => 'page_split_join cm-edit-action', 'disabled' => $disabled, 'data-action' => $action]),
'page_split_join_wrapper');
return html_writer::span(
$this->action_link(
$url,
$image,
null,
[
'title' => $title,
'aria-label' => $title,
'class' => 'page_split_join cm-edit-action btn btn-sm icon-no-margin',
'disabled' => $disabled,
'data-action' => $action,
]
),
'page_split_join_wrapper'
);
}
/**
@@ -1021,13 +1033,11 @@ class edit_renderer extends \plugin_renderer_base {
];
if ($structure->is_question_dependent_on_previous_slot($slot)) {
$title = get_string('questiondependencyremove', 'quiz', $a);
$image = $this->pix_icon('t/locked', get_string('questiondependsonprevious', 'quiz'),
'moodle', ['title' => '']);
$image = $this->pix_icon('t/locked', '');
$action = 'removedependency';
} else {
$title = get_string('questiondependencyadd', 'quiz', $a);
$image = $this->pix_icon('t/unlocked', get_string('questiondependencyfree', 'quiz'),
'moodle', ['title' => '']);
$image = $this->pix_icon('t/unlocked', '');
$action = 'adddependency';
}
@@ -1040,9 +1050,22 @@ class edit_renderer extends \plugin_renderer_base {
if (!$structure->can_question_depend_on_previous_slot($slot)) {
$extraclass = ' question_dependency_cannot_depend';
}
return html_writer::span($this->action_link('#', $image, null, ['title' => $title,
'class' => 'cm-edit-action', 'disabled' => $disabled, 'data-action' => $action]),
'question_dependency_wrapper' . $extraclass);
return html_writer::span(
$this->action_link(
'#',
$image,
null,
[
'title' => $title,
'aria-label' => $title,
'class' => 'cm-edit-action btn btn-link btn-sm icon-no-margin',
'disabled' => $disabled,
'data-action' => $action,
'role' => 'button',
]
),
'question_dependency_wrapper' . $extraclass
);
}
/**
+8 -5
View File
@@ -615,7 +615,7 @@ table.quizreviewsummary td.cell {
#page-mod-quiz-edit ul.section {
margin: 0;
padding: 0 20px;
padding: 0 38px;
}
#page-mod-quiz-edit ul.slots {
@@ -688,8 +688,11 @@ table.quizreviewsummary td.cell {
#page-mod-quiz-edit ul.slots li.section li.activity .page_split_join_wrapper {
position: absolute;
left: -20px;
bottom: -11px;
left: -38px;
bottom: -18px;
.icon {
width: auto;
}
}
#page-mod-quiz-edit ul.slots li.section li.activity .instancemaxmark.decimalplaces_0 {
@@ -742,8 +745,8 @@ table.quizreviewsummary td.cell {
#page-mod-quiz-edit ul.slots li.section li.activity .question_dependency_wrapper {
position: absolute;
top: -1em;
right: -20px;
top: -1.25em;
right: -38px;
}
#page-mod-quiz-edit ul.slots li.section li.activity .question_dependency_wrapper.question_dependency_cannot_depend {
@@ -51,7 +51,7 @@ Feature: Edit quizzes where some questions require the previous one to have been
| TF1 | 1 | 0 |
| TF2 | 1 | 1 |
And I am on the "Quiz 1" "mod_quiz > Edit" page
Then "This question cannot be attempted until the previous question has been completed." "link" should be visible
Then "Question 2 cannot be attempted until the previous question 1 has been completed" "button" should be visible
@javascript
Scenario: A question can depend on a random question
@@ -66,9 +66,9 @@ Feature: Edit quizzes where some questions require the previous one to have been
| Random (Test questions) | 1 | 0 | Test questions |
| TF1 | 1 | 1 | |
And I am on the "Quiz 1" "mod_quiz > Edit" page
Then "This question cannot be attempted until the previous question has been completed." "link" should be visible
Then "Question 2 cannot be attempted until the previous question 1 has been completed" "button" should be visible
@javascript
@javascript @accessibility
Scenario: The second question can be set to depend on the first
Given the following "activities" exist:
| activity | name | intro | course | idnumber | preferredbehaviour |
@@ -84,9 +84,10 @@ Feature: Edit quizzes where some questions require the previous one to have been
| TF2 | 1 | 0 |
| TF3 | 1 | 0 |
And I am on the "Quiz 1" "mod_quiz > Edit" page
When I follow "No restriction on when question 2 can be attempted Click to change"
Then "Question 2 cannot be attempted until the previous question 1 has been completed Click to change" "link" should be visible
And "No restriction on when question 3 can be attempted Click to change" "link" should be visible
When I click on "No restriction on when question 2 can be attempted Click to change" "button"
Then "Question 2 cannot be attempted until the previous question 1 has been completed Click to change" "button" should be visible
And "No restriction on when question 3 can be attempted Click to change" "button" should be visible
And the "region-main" "region" should meet accessibility standards with "best-practice" extra tests
@javascript
Scenario: A question that did depend on the previous can be un-linked
@@ -104,9 +105,9 @@ Feature: Edit quizzes where some questions require the previous one to have been
| TF2 | 1 | 1 |
| TF3 | 1 | 1 |
And I am on the "Quiz 1" "mod_quiz > Edit" page
When I follow "Question 3 cannot be attempted until the previous question 2 has been completed Click to change"
Then "Question 2 cannot be attempted until the previous question 1 has been completed Click to change" "link" should be visible
And "No restriction on when question 3 can be attempted Click to change" "link" should be visible
When I click on "Question 3 cannot be attempted until the previous question 2 has been completed Click to change" "button"
Then "Question 2 cannot be attempted until the previous question 1 has been completed Click to change" "button" should be visible
And "No restriction on when question 3 can be attempted Click to change" "button" should be visible
@javascript
Scenario: Question dependency cannot apply to deferred feedback quizzes so UI is hidden
@@ -230,6 +231,6 @@ Feature: Edit quizzes where some questions require the previous one to have been
| TF3 | 1 | 1 |
And I am on the "Quiz 1" "mod_quiz > Edit" page
When I move "TF1" to "After Question 3" in the quiz by clicking the move icon
Then "Question 2 cannot be attempted until the previous question 1 has been completed Click to change" "link" should be visible
And "No restriction on when question 3 can be attempted Click to change" "link" should be visible
Then "Question 2 cannot be attempted until the previous question 1 has been completed Click to change" "button" should be visible
And "No restriction on when question 3 can be attempted Click to change" "button" should be visible
And "be attempted" "link" in the "TF2" "list_item" should not be visible
@@ -384,8 +384,8 @@ Y.extend(DRAGRESOURCE, M.core.dragdrop, {
// Replace move icons
var move = resourcesnode.one('a.' + CSS.EDITINGMOVE);
if (move) {
var resourcedraghandle = this.get_drag_handle(M.util.get_string('move', 'moodle'),
CSS.EDITINGMOVE, CSS.ICONCLASS, true);
var dragHandleClass = 'btn px-1 icon-no-margin ' + CSS.EDITINGMOVE;
var resourcedraghandle = this.get_drag_handle(M.util.get_string('move', 'moodle'), dragHandleClass, CSS.ICONCLASS);
move.replace(resourcedraghandle);
}
}, this);
File diff suppressed because one or more lines are too long
@@ -378,8 +378,8 @@ Y.extend(DRAGRESOURCE, M.core.dragdrop, {
// Replace move icons
var move = resourcesnode.one('a.' + CSS.EDITINGMOVE);
if (move) {
var resourcedraghandle = this.get_drag_handle(M.util.get_string('move', 'moodle'),
CSS.EDITINGMOVE, CSS.ICONCLASS, true);
var dragHandleClass = 'btn px-1 icon-no-margin ' + CSS.EDITINGMOVE;
var resourcedraghandle = this.get_drag_handle(M.util.get_string('move', 'moodle'), dragHandleClass, CSS.ICONCLASS);
move.replace(resourcedraghandle);
}
}, this);
@@ -345,11 +345,10 @@ Y.Moodle.mod_quiz.util.slot = {
// Update the link and image titles
pagebreaklink.set('title', M.util.get_string(action, 'quiz'));
pagebreaklink.set('aria-label', M.util.get_string(action, 'quiz'));
pagebreaklink.setData('action', action);
// Update the image title.
var icon = pagebreaklink.one(this.SELECTORS.ICON);
icon.set('title', M.util.get_string(action, 'quiz'));
icon.set('alt', M.util.get_string(action, 'quiz'));
// Update the image src.
icon.set('src', M.util.image_url(iconname));
@@ -417,27 +416,27 @@ Y.Moodle.mod_quiz.util.slot = {
requiresprevious = link.getData('action') === 'removedependency';
}
var iconname = '';
if (requiresprevious) {
link.set('title', M.util.get_string('questiondependencyremove', 'quiz', a));
link.set('aria-label', M.util.get_string('questiondependencyremove', 'quiz', a));
link.setData('action', 'removedependency');
window.require(['core/templates'], function(Templates) {
Templates.renderPix('t/locked', 'core', M.util.get_string('questiondependsonprevious', 'quiz')).then(
function(html) {
icon.replace(html);
}
);
});
iconname = 't/locked';
} else {
link.set('title', M.util.get_string('questiondependencyadd', 'quiz', a));
link.set('aria-label', M.util.get_string('questiondependencyadd', 'quiz', a));
link.setData('action', 'adddependency');
window.require(['core/templates'], function(Templates) {
Templates.renderPix('t/unlocked', 'core', M.util.get_string('questiondependencyfree', 'quiz')).then(
iconname = 't/unlocked';
}
window.require(['core/templates', 'core/notification'], function(Templates, Notification) {
Templates.renderPix(iconname, 'core', '')
.then(
function(html) {
icon.replace(html);
return;
}
);
});
}
).catch(Notification.exception);
});
}
};
File diff suppressed because one or more lines are too long
@@ -345,11 +345,10 @@ Y.Moodle.mod_quiz.util.slot = {
// Update the link and image titles
pagebreaklink.set('title', M.util.get_string(action, 'quiz'));
pagebreaklink.set('aria-label', M.util.get_string(action, 'quiz'));
pagebreaklink.setData('action', action);
// Update the image title.
var icon = pagebreaklink.one(this.SELECTORS.ICON);
icon.set('title', M.util.get_string(action, 'quiz'));
icon.set('alt', M.util.get_string(action, 'quiz'));
// Update the image src.
icon.set('src', M.util.image_url(iconname));
@@ -417,27 +416,27 @@ Y.Moodle.mod_quiz.util.slot = {
requiresprevious = link.getData('action') === 'removedependency';
}
var iconname = '';
if (requiresprevious) {
link.set('title', M.util.get_string('questiondependencyremove', 'quiz', a));
link.set('aria-label', M.util.get_string('questiondependencyremove', 'quiz', a));
link.setData('action', 'removedependency');
window.require(['core/templates'], function(Templates) {
Templates.renderPix('t/locked', 'core', M.util.get_string('questiondependsonprevious', 'quiz')).then(
function(html) {
icon.replace(html);
}
);
});
iconname = 't/locked';
} else {
link.set('title', M.util.get_string('questiondependencyadd', 'quiz', a));
link.set('aria-label', M.util.get_string('questiondependencyadd', 'quiz', a));
link.setData('action', 'adddependency');
window.require(['core/templates'], function(Templates) {
Templates.renderPix('t/unlocked', 'core', M.util.get_string('questiondependencyfree', 'quiz')).then(
iconname = 't/unlocked';
}
window.require(['core/templates', 'core/notification'], function(Templates, Notification) {
Templates.renderPix(iconname, 'core', '')
.then(
function(html) {
icon.replace(html);
return;
}
);
});
}
).catch(Notification.exception);
});
}
};
+2 -2
View File
@@ -83,8 +83,8 @@ Y.extend(DRAGRESOURCE, M.core.dragdrop, {
// Replace move icons
var move = resourcesnode.one('a.' + CSS.EDITINGMOVE);
if (move) {
var resourcedraghandle = this.get_drag_handle(M.util.get_string('move', 'moodle'),
CSS.EDITINGMOVE, CSS.ICONCLASS, true);
var dragHandleClass = 'btn px-1 icon-no-margin ' + CSS.EDITINGMOVE;
var resourcedraghandle = this.get_drag_handle(M.util.get_string('move', 'moodle'), dragHandleClass, CSS.ICONCLASS);
move.replace(resourcedraghandle);
}
}, this);
+13 -14
View File
@@ -343,11 +343,10 @@ Y.Moodle.mod_quiz.util.slot = {
// Update the link and image titles
pagebreaklink.set('title', M.util.get_string(action, 'quiz'));
pagebreaklink.set('aria-label', M.util.get_string(action, 'quiz'));
pagebreaklink.setData('action', action);
// Update the image title.
var icon = pagebreaklink.one(this.SELECTORS.ICON);
icon.set('title', M.util.get_string(action, 'quiz'));
icon.set('alt', M.util.get_string(action, 'quiz'));
// Update the image src.
icon.set('src', M.util.image_url(iconname));
@@ -415,26 +414,26 @@ Y.Moodle.mod_quiz.util.slot = {
requiresprevious = link.getData('action') === 'removedependency';
}
var iconname = '';
if (requiresprevious) {
link.set('title', M.util.get_string('questiondependencyremove', 'quiz', a));
link.set('aria-label', M.util.get_string('questiondependencyremove', 'quiz', a));
link.setData('action', 'removedependency');
window.require(['core/templates'], function(Templates) {
Templates.renderPix('t/locked', 'core', M.util.get_string('questiondependsonprevious', 'quiz')).then(
function(html) {
icon.replace(html);
}
);
});
iconname = 't/locked';
} else {
link.set('title', M.util.get_string('questiondependencyadd', 'quiz', a));
link.set('aria-label', M.util.get_string('questiondependencyadd', 'quiz', a));
link.setData('action', 'adddependency');
window.require(['core/templates'], function(Templates) {
Templates.renderPix('t/unlocked', 'core', M.util.get_string('questiondependencyfree', 'quiz')).then(
iconname = 't/unlocked';
}
window.require(['core/templates', 'core/notification'], function(Templates, Notification) {
Templates.renderPix(iconname, 'core', '')
.then(
function(html) {
icon.replace(html);
return;
}
);
});
}
).catch(Notification.exception);
});
}
};