mirror of
https://github.com/moodle/moodle.git
synced 2025-03-14 12:40:01 +01:00
Merge branch 'MDL-68597' of https://github.com/timhunt/moodle
This commit is contained in:
commit
eceeb180bf
@ -49,10 +49,9 @@ class backup_qtype_essay_plugin extends backup_qtype_plugin {
|
||||
|
||||
// Now create the qtype own structures.
|
||||
$essay = new backup_nested_element('essay', array('id'), array(
|
||||
'responseformat', 'responserequired', 'responsefieldlines',
|
||||
'attachments', 'attachmentsrequired', 'graderinfo',
|
||||
'graderinfoformat', 'responsetemplate', 'responsetemplateformat',
|
||||
'filetypeslist', 'maxbytes'));
|
||||
'responseformat', 'responserequired', 'responsefieldlines', 'minwordlimit', 'maxwordlimit',
|
||||
'attachments', 'attachmentsrequired', 'graderinfo', 'graderinfoformat', 'responsetemplate',
|
||||
'responsetemplateformat', 'filetypeslist', 'maxbytes'));
|
||||
|
||||
// Now the own qtype tree.
|
||||
$pluginwrapper->add_child($essay);
|
||||
|
@ -61,6 +61,12 @@ class restore_qtype_essay_plugin extends restore_qtype_plugin {
|
||||
if (!isset($data->responserequired)) {
|
||||
$data->responserequired = 1;
|
||||
}
|
||||
if (!isset($data->minwordlimit)) {
|
||||
$data->minwordlimit = null;
|
||||
}
|
||||
if (!isset($data->maxwordlimit)) {
|
||||
$data->maxwordlimit = null;
|
||||
}
|
||||
if (!isset($data->attachmentsrequired)) {
|
||||
$data->attachmentsrequired = 0;
|
||||
}
|
||||
@ -111,6 +117,8 @@ class restore_qtype_essay_plugin extends restore_qtype_plugin {
|
||||
$defaultoptions->responseformat = 'editor';
|
||||
$defaultoptions->responserequired = 1;
|
||||
$defaultoptions->responsefieldlines = 15;
|
||||
$defaultoptions->minwordlimit = null;
|
||||
$defaultoptions->maxwordlimit = null;
|
||||
$defaultoptions->attachments = 0;
|
||||
$defaultoptions->attachmentsrequired = 0;
|
||||
$defaultoptions->graderinfo = '';
|
||||
|
@ -11,6 +11,8 @@
|
||||
<FIELD NAME="responseformat" TYPE="char" LENGTH="16" NOTNULL="true" DEFAULT="editor" SEQUENCE="false" COMMENT="The type of input area students should be given for their response."/>
|
||||
<FIELD NAME="responserequired" TYPE="int" LENGTH="2" NOTNULL="true" DEFAULT="1" SEQUENCE="false" COMMENT="Nonzero if an online text response is optional"/>
|
||||
<FIELD NAME="responsefieldlines" TYPE="int" LENGTH="4" NOTNULL="true" DEFAULT="15" SEQUENCE="false" COMMENT="Approximate height, in lines, of the input box the students should be given for their response."/>
|
||||
<FIELD NAME="minwordlimit" TYPE="int" LENGTH="10" NOTNULL="false" SEQUENCE="false" COMMENT="Minimum number of words"/>
|
||||
<FIELD NAME="maxwordlimit" TYPE="int" LENGTH="10" NOTNULL="false" SEQUENCE="false" COMMENT="Maximum number of words"/>
|
||||
<FIELD NAME="attachments" TYPE="int" LENGTH="4" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="Whether, and how many, attachments a student is allowed to include with their response. -1 means unlimited."/>
|
||||
<FIELD NAME="attachmentsrequired" TYPE="int" LENGTH="4" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="The number of attachments that should be required"/>
|
||||
<FIELD NAME="graderinfo" TYPE="text" NOTNULL="false" SEQUENCE="false" COMMENT="Information shown to people with permission to manually grade the question, when they are grading."/>
|
||||
|
@ -63,5 +63,30 @@ function xmldb_qtype_essay_upgrade($oldversion) {
|
||||
// Essay savepoint reached.
|
||||
upgrade_plugin_savepoint(true, 2021052501, 'qtype', 'essay');
|
||||
}
|
||||
|
||||
if ($oldversion < 2021052502) {
|
||||
|
||||
// Define field minwordlimit to be added to qtype_essay_options.
|
||||
$table = new xmldb_table('qtype_essay_options');
|
||||
$field = new xmldb_field('minwordlimit', XMLDB_TYPE_INTEGER, '10', null, null, null, null, 'responsefieldlines');
|
||||
|
||||
// Conditionally launch add field minwordlimit.
|
||||
if (!$dbman->field_exists($table, $field)) {
|
||||
$dbman->add_field($table, $field);
|
||||
}
|
||||
|
||||
// Define field maxwordlimit to be added to qtype_essay_options.
|
||||
$table = new xmldb_table('qtype_essay_options');
|
||||
$field = new xmldb_field('maxwordlimit', XMLDB_TYPE_INTEGER, '10', null, null, null, null, 'minwordlimit');
|
||||
|
||||
// Conditionally launch add field maxwordlimit.
|
||||
if (!$dbman->field_exists($table, $field)) {
|
||||
$dbman->add_field($table, $field);
|
||||
}
|
||||
|
||||
// Essay savepoint reached.
|
||||
upgrade_plugin_savepoint(true, 2021052502, 'qtype', 'essay');
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -48,12 +48,34 @@ class qtype_essay_edit_form extends question_edit_form {
|
||||
$mform->addElement('select', 'responserequired',
|
||||
get_string('responserequired', 'qtype_essay'), $qtype->response_required_options());
|
||||
$mform->setDefault('responserequired', 1);
|
||||
$mform->disabledIf('responserequired', 'responseformat', 'eq', 'noinline');
|
||||
$mform->hideIf('responserequired', 'responseformat', 'eq', 'noinline');
|
||||
|
||||
$mform->addElement('select', 'responsefieldlines',
|
||||
get_string('responsefieldlines', 'qtype_essay'), $qtype->response_sizes());
|
||||
$mform->setDefault('responsefieldlines', 15);
|
||||
$mform->disabledIf('responsefieldlines', 'responseformat', 'eq', 'noinline');
|
||||
$mform->hideIf('responsefieldlines', 'responseformat', 'eq', 'noinline');
|
||||
|
||||
// Create a text box that can be enabled/disabled for max/min word limits options.
|
||||
$wordlimitoptions = ['size' => '6', 'maxlength' => '6'];
|
||||
$mingrp[] = $mform->createElement('text', 'minwordlimit', '', $wordlimitoptions);
|
||||
$mform->setType('minwordlimit', PARAM_INT);
|
||||
$mingrp[] = $mform->createElement('checkbox', 'minwordenabled', '', get_string('enable'));
|
||||
$mform->setDefault('minwordenabled', 0);
|
||||
$mform->addGroup($mingrp, 'mingroup', get_string('minwordlimit', 'qtype_essay'), ' ', false);
|
||||
$mform->addHelpButton('mingroup', 'minwordlimit', 'qtype_essay');
|
||||
$mform->disabledIf('minwordlimit', 'minwordenabled', 'notchecked');
|
||||
$mform->hideIf('mingroup', 'responserequired', 'eq', '0');
|
||||
$mform->hideIf('mingroup', 'responseformat', 'eq', 'noinline');
|
||||
|
||||
$maxgrp[] = $mform->createElement('text', 'maxwordlimit', '', $wordlimitoptions);
|
||||
$mform->setType('maxwordlimit', PARAM_INT);
|
||||
$maxgrp[] = $mform->createElement('checkbox', 'maxwordenabled', '', get_string('enable'));
|
||||
$mform->setDefault('maxwordenabled', 0);
|
||||
$mform->addGroup($maxgrp, 'maxgroup', get_string('maxwordlimit', 'qtype_essay'), ' ', false);
|
||||
$mform->addHelpButton('maxgroup', 'maxwordlimit', 'qtype_essay');
|
||||
$mform->disabledIf('maxwordlimit', 'maxwordenabled', 'notchecked');
|
||||
$mform->hideIf('maxgroup', 'responserequired', 'eq', '0');
|
||||
$mform->hideIf('maxgroup', 'responseformat', 'eq', 'noinline');
|
||||
|
||||
$mform->addElement('select', 'attachments',
|
||||
get_string('allowattachments', 'qtype_essay'), $qtype->attachment_options());
|
||||
@ -63,15 +85,15 @@ class qtype_essay_edit_form extends question_edit_form {
|
||||
get_string('attachmentsrequired', 'qtype_essay'), $qtype->attachments_required_options());
|
||||
$mform->setDefault('attachmentsrequired', 0);
|
||||
$mform->addHelpButton('attachmentsrequired', 'attachmentsrequired', 'qtype_essay');
|
||||
$mform->disabledIf('attachmentsrequired', 'attachments', 'eq', 0);
|
||||
$mform->hideIf('attachmentsrequired', 'attachments', 'eq', 0);
|
||||
|
||||
$mform->addElement('filetypes', 'filetypeslist', get_string('acceptedfiletypes', 'qtype_essay'));
|
||||
$mform->addHelpButton('filetypeslist', 'acceptedfiletypes', 'qtype_essay');
|
||||
$mform->disabledIf('filetypeslist', 'attachments', 'eq', 0);
|
||||
$mform->hideIf('filetypeslist', 'attachments', 'eq', 0);
|
||||
|
||||
$mform->addElement('select', 'maxbytes', get_string('maxbytes', 'qtype_essay'), $qtype->max_file_size_options());
|
||||
$mform->setDefault('maxbytes', '0');
|
||||
$mform->disabledIf('maxbytes', 'attachments', 'eq', 0);
|
||||
$mform->hideIf('maxbytes', 'attachments', 'eq', 0);
|
||||
|
||||
$mform->addElement('header', 'responsetemplateheader', get_string('responsetemplateheader', 'qtype_essay'));
|
||||
$mform->addElement('editor', 'responsetemplate', get_string('responsetemplate', 'qtype_essay'),
|
||||
@ -94,6 +116,10 @@ class qtype_essay_edit_form extends question_edit_form {
|
||||
$question->responseformat = $question->options->responseformat;
|
||||
$question->responserequired = $question->options->responserequired;
|
||||
$question->responsefieldlines = $question->options->responsefieldlines;
|
||||
$question->minwordenabled = $question->options->minwordlimit ? 1 : 0;
|
||||
$question->minwordlimit = $question->options->minwordlimit;
|
||||
$question->maxwordenabled = $question->options->maxwordlimit ? 1 : 0;
|
||||
$question->maxwordlimit = $question->options->maxwordlimit;
|
||||
$question->attachments = $question->options->attachments;
|
||||
$question->attachmentsrequired = $question->options->attachmentsrequired;
|
||||
$question->filetypeslist = $question->options->filetypeslist;
|
||||
@ -142,6 +168,36 @@ class qtype_essay_edit_form extends question_edit_form {
|
||||
$errors['attachmentsrequired'] = get_string('mustrequirefewer', 'qtype_essay');
|
||||
}
|
||||
|
||||
if ($fromform['responserequired']) {
|
||||
if (isset($fromform['minwordenabled'])) {
|
||||
if (!is_numeric($fromform['minwordlimit'])) {
|
||||
$errors['mingroup'] = get_string('err_numeric', 'form');
|
||||
}
|
||||
if ($fromform['minwordlimit'] < 0) {
|
||||
$errors['mingroup'] = get_string('err_minwordlimitnegative', 'qtype_essay');
|
||||
}
|
||||
if (!$fromform['minwordlimit']) {
|
||||
$errors['mingroup'] = get_string('err_minwordlimit', 'qtype_essay');
|
||||
}
|
||||
}
|
||||
if (isset($fromform['maxwordenabled'])) {
|
||||
if (!is_numeric($fromform['maxwordlimit'])) {
|
||||
$errors['maxgroup'] = get_string('err_numeric', 'form');
|
||||
}
|
||||
if ($fromform['maxwordlimit'] < 0) {
|
||||
$errors['maxgroup'] = get_string('err_maxwordlimitnegative', 'qtype_essay');
|
||||
}
|
||||
if (!$fromform['maxwordlimit']) {
|
||||
$errors['maxgroup'] = get_string('err_maxwordlimit', 'qtype_essay');
|
||||
}
|
||||
}
|
||||
if (isset($fromform['maxwordenabled']) && isset($fromform['minwordenabled'])) {
|
||||
if ($fromform['maxwordlimit'] < $fromform['minwordlimit'] &&
|
||||
$fromform['maxwordlimit'] > 0 && $fromform['minwordlimit'] > 0) {
|
||||
$errors['maxgroup'] = get_string('err_maxminmismatch', 'qtype_essay');
|
||||
}
|
||||
}
|
||||
}
|
||||
return $errors;
|
||||
}
|
||||
|
||||
|
@ -29,6 +29,11 @@ $string['allowattachments'] = 'Allow attachments';
|
||||
$string['attachmentsoptional'] = 'Attachments are optional';
|
||||
$string['attachmentsrequired'] = 'Require attachments';
|
||||
$string['attachmentsrequired_help'] = 'This option specifies the minimum number of attachments required for a response to be considered gradable.';
|
||||
$string['err_maxminmismatch'] = 'Maximum world limit must be greater than minimum word limit';
|
||||
$string['err_maxwordlimit'] = 'Maximum word limit is enabled but is not set';
|
||||
$string['err_maxwordlimitnegative'] = 'Maximum word limit cannot be a negative number';
|
||||
$string['err_minwordlimit'] = 'Minimum word limit is enabled but is not set';
|
||||
$string['err_minwordlimitnegative'] = 'Minimum word limit cannot be a negative number';
|
||||
$string['formateditor'] = 'HTML editor';
|
||||
$string['formateditorfilepicker'] = 'HTML editor with file picker';
|
||||
$string['formatmonospaced'] = 'Plain text, monospaced font';
|
||||
@ -37,6 +42,12 @@ $string['formatplain'] = 'Plain text';
|
||||
$string['graderinfo'] = 'Information for graders';
|
||||
$string['graderinfoheader'] = 'Grader Information';
|
||||
$string['maxbytes'] = 'Maximum file size';
|
||||
$string['maxwordlimit'] = 'Maximum word limit';
|
||||
$string['maxwordlimit_help'] = 'If the response requires that students enter text, this is the maximum number of words that each student will be allowed to submit.';
|
||||
$string['maxwordlimitboundary'] = 'The word limit for this question is {$a->limit} words and you are attempting to submit {$a->count} words. Please shorten your response and try again.';
|
||||
$string['minwordlimit'] = 'Minimum word limit';
|
||||
$string['minwordlimit_help'] = 'If the response requires that students enter text, this is the minimum number of words that each student will be allowed to submit.';
|
||||
$string['minwordlimitboundary'] = 'This question requires a response of at least {$a->limit} words and you are attempting to submit {$a->count} words. Please expand your response and try again.';
|
||||
$string['mustattach'] = 'When "No online text" is selected, or responses are optional, you must allow at least one attachment.';
|
||||
$string['mustrequire'] = 'When "No online text" is selected, or responses are optional, you must require at least one attachment.';
|
||||
$string['mustrequirefewer'] = 'You cannot require more attachments than you allow.';
|
||||
@ -58,3 +69,6 @@ $string['responseisrequired'] = 'Require the student to enter text';
|
||||
$string['responsetemplate'] = 'Response template';
|
||||
$string['responsetemplateheader'] = 'Response Template';
|
||||
$string['responsetemplate_help'] = 'Any text entered here will be displayed in the response input box when a new attempt at the question starts.';
|
||||
$string['wordcount'] = 'Word count: {$a}';
|
||||
$string['wordcounttoofew'] = 'Word count: {$a->count}, less than the required {$a->limit} words.';
|
||||
$string['wordcounttoomuch'] = 'Word count: {$a->count}, more than the limit of {$a->limit} words.';
|
||||
|
@ -42,6 +42,13 @@ class qtype_essay_question extends question_with_responses {
|
||||
public $responserequired;
|
||||
|
||||
public $responsefieldlines;
|
||||
|
||||
/** @var int indicates whether the minimum number of words required */
|
||||
public $minwordlimit;
|
||||
|
||||
/** @var int indicates whether the maximum number of words required */
|
||||
public $maxwordlimit;
|
||||
|
||||
public $attachments;
|
||||
|
||||
/** @var int maximum file size in bytes */
|
||||
@ -107,6 +114,13 @@ class qtype_essay_question extends question_with_responses {
|
||||
public function is_complete_response(array $response) {
|
||||
// Determine if the given response has online text and attachments.
|
||||
$hasinlinetext = array_key_exists('answer', $response) && ($response['answer'] !== '');
|
||||
|
||||
// If there is a response and min/max word limit is set in the form then validate the number of words in response.
|
||||
if ($hasinlinetext) {
|
||||
if ($this->check_input_word_count($response['answer'])) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
$hasattachments = array_key_exists('attachments', $response)
|
||||
&& $response['attachments'] instanceof question_response_files;
|
||||
|
||||
@ -140,6 +154,20 @@ class qtype_essay_question extends question_with_responses {
|
||||
return $hascontent && $meetsinlinereq && $meetsattachmentreq;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return null if is_complete_response() returns true
|
||||
* otherwise, return the minmax-limit error message
|
||||
*
|
||||
* @param array $response
|
||||
* @return string
|
||||
*/
|
||||
public function get_validation_error(array $response) {
|
||||
if ($this->is_complete_response($response)) {
|
||||
return '';
|
||||
}
|
||||
return $this->check_input_word_count($response['answer']);
|
||||
}
|
||||
|
||||
public function is_gradable_response(array $response) {
|
||||
// Determine if the given response has online text and attachments.
|
||||
if (array_key_exists('answer', $response) && ($response['answer'] !== '')) {
|
||||
@ -212,4 +240,64 @@ class qtype_essay_question extends question_with_responses {
|
||||
|
||||
return $settings;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check the input word count and return a message to user
|
||||
* when the number of words are outside the boundary settings.
|
||||
*
|
||||
* @param string $responsestring
|
||||
* @return string|null
|
||||
.*/
|
||||
private function check_input_word_count($responsestring) {
|
||||
if (!$this->responserequired) {
|
||||
return null;
|
||||
}
|
||||
if (!$this->minwordlimit && !$this->maxwordlimit) {
|
||||
// This question does not care about the word count.
|
||||
return null;
|
||||
}
|
||||
|
||||
// Count the number of words in the response string.
|
||||
$count = count_words($responsestring);
|
||||
if ($this->maxwordlimit && $count > $this->maxwordlimit) {
|
||||
return get_string('maxwordlimitboundary', 'qtype_essay',
|
||||
['limit' => $this->maxwordlimit, 'count' => $count]);
|
||||
} else if ($count < $this->minwordlimit) {
|
||||
return get_string('minwordlimitboundary', 'qtype_essay',
|
||||
['limit' => $this->minwordlimit, 'count' => $count]);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* If this question uses word counts, then return a display of the current
|
||||
* count, and whether it is within limit, for when the question is being reviewed.
|
||||
*
|
||||
* @param array $response responses, as returned by
|
||||
* {@see question_attempt_step::get_qt_data()}.
|
||||
* @return string If relevant to this question, a display of the word count.
|
||||
*/
|
||||
public function get_word_count_message_for_review(array $response): string {
|
||||
if (!$this->minwordlimit && !$this->maxwordlimit) {
|
||||
// This question does not care about the word count.
|
||||
return '';
|
||||
}
|
||||
|
||||
if (!array_key_exists('answer', $response) || ($response['answer'] === '')) {
|
||||
// No response.
|
||||
return '';
|
||||
}
|
||||
|
||||
$count = count_words($response['answer']);
|
||||
if ($this->maxwordlimit && $count > $this->maxwordlimit) {
|
||||
return get_string('wordcounttoomuch', 'qtype_essay',
|
||||
['limit' => $this->maxwordlimit, 'count' => $count]);
|
||||
} else if ($count < $this->minwordlimit) {
|
||||
return get_string('wordcounttoofew', 'qtype_essay',
|
||||
['limit' => $this->minwordlimit, 'count' => $count]);
|
||||
} else {
|
||||
return get_string('wordcount', 'qtype_essay', $count);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -65,6 +65,8 @@ class qtype_essay extends question_type {
|
||||
$options->responseformat = $formdata->responseformat;
|
||||
$options->responserequired = $formdata->responserequired;
|
||||
$options->responsefieldlines = $formdata->responsefieldlines;
|
||||
$options->minwordlimit = isset($formdata->minwordenabled) ? $formdata->minwordlimit : 0;
|
||||
$options->maxwordlimit = isset($formdata->maxwordenabled) ? $formdata->maxwordlimit : 0;
|
||||
$options->attachments = $formdata->attachments;
|
||||
$options->attachmentsrequired = $formdata->attachmentsrequired;
|
||||
if (!isset($formdata->filetypeslist)) {
|
||||
@ -86,6 +88,8 @@ class qtype_essay extends question_type {
|
||||
$question->responseformat = $questiondata->options->responseformat;
|
||||
$question->responserequired = $questiondata->options->responserequired;
|
||||
$question->responsefieldlines = $questiondata->options->responsefieldlines;
|
||||
$question->minwordlimit = $questiondata->options->minwordlimit;
|
||||
$question->maxwordlimit = $questiondata->options->maxwordlimit;
|
||||
$question->attachments = $questiondata->options->attachments;
|
||||
$question->attachmentsrequired = $questiondata->options->attachmentsrequired;
|
||||
$question->graderinfo = $questiondata->options->graderinfo;
|
||||
|
@ -55,6 +55,8 @@ class qtype_essay_renderer extends qtype_renderer {
|
||||
} else {
|
||||
$answer = $responseoutput->response_area_read_only('answer', $qa,
|
||||
$step, $question->responsefieldlines, $options->context);
|
||||
$answer .= html_writer::nonempty_tag('p', $question->get_word_count_message_for_review($step->get_qt_data()));
|
||||
|
||||
}
|
||||
|
||||
$files = '';
|
||||
@ -73,6 +75,12 @@ class qtype_essay_renderer extends qtype_renderer {
|
||||
|
||||
$result .= html_writer::start_tag('div', array('class' => 'ablock'));
|
||||
$result .= html_writer::tag('div', $answer, array('class' => 'answer'));
|
||||
|
||||
// If there is a response and min/max word limit is set in the form then check the response word count.
|
||||
if ($qa->get_state() == question_state::$invalid) {
|
||||
$result .= html_writer::nonempty_tag('div',
|
||||
$question->get_validation_error($step->get_qt_data()), ['class' => 'validationerror']);
|
||||
}
|
||||
$result .= html_writer::tag('div', $files, array('class' => 'attachments'));
|
||||
$result .= html_writer::end_tag('div');
|
||||
|
||||
|
58
question/type/essay/tests/behat/edit_min_max_fields.feature
Normal file
58
question/type/essay/tests/behat/edit_min_max_fields.feature
Normal file
@ -0,0 +1,58 @@
|
||||
@qtype @qtype_essay
|
||||
Feature: In an essay question, let the question author choose the min/max number of words for input text
|
||||
In order to constrain student submissions for marking
|
||||
As a teacher
|
||||
I need to choose the appropriate minimum and/or maximum number of words for input text
|
||||
Background:
|
||||
Given the following "users" exist:
|
||||
| username | firstname | lastname | email |
|
||||
| teacher1 | T1 | Teacher1 | teacher1@moodle.com |
|
||||
And the following "courses" exist:
|
||||
| fullname | shortname | category |
|
||||
| Course 1 | C1 | 0 |
|
||||
And the following "course enrolments" exist:
|
||||
| user | course | role |
|
||||
| teacher1 | C1 | editingteacher |
|
||||
And the following "question categories" exist:
|
||||
| contextlevel | reference | name |
|
||||
| Course | C1 | Test questions |
|
||||
And the following "questions" exist:
|
||||
| questioncategory | qtype | name | template | minwordlimit | maxwordlimit |
|
||||
| Test questions | essay | essay-min-max | editor | null | null |
|
||||
Given I log in as "teacher1"
|
||||
And I am on "Course 1" course homepage
|
||||
And I navigate to "Question bank" in current page administration
|
||||
|
||||
@javascript
|
||||
Scenario: Minimum/Maximum word limit are enabled but not set.
|
||||
Given I choose "Edit question" action for "essay-min-max" in the question bank
|
||||
When I set the field "minwordenabled" to "1"
|
||||
And I click on "Save changes" "button"
|
||||
Then I should see "Minimum word limit is enabled but is not set"
|
||||
|
||||
@javascript
|
||||
Scenario: Minimum/Maximum word limit cannot be set to a negative number.
|
||||
Given I choose "Edit question" action for "essay-min-max" in the question bank
|
||||
And I set the field "minwordenabled" to "1"
|
||||
When I set the field "id_minwordlimit" to "-10"
|
||||
And I click on "Save changes" "button"
|
||||
Then I should see "Minimum word limit cannot be a negative number"
|
||||
|
||||
@javascript
|
||||
Scenario: Maximum word limit cannot be greater than minimum word limit.
|
||||
Given I choose "Edit question" action for "essay-min-max" in the question bank
|
||||
And I set the field "minwordenabled" to "1"
|
||||
And I set the field "id_minwordlimit" to "500"
|
||||
And I set the field "maxwordenabled" to "1"
|
||||
When I set the field "id_maxwordlimit" to "450"
|
||||
And I click on "Save changes" "button"
|
||||
Then I should see "Maximum world limit must be greater than minimum word limit"
|
||||
|
||||
@javascript
|
||||
Scenario: Modify the question to see 'Minimum word limit' and 'Maximum word limit' are hidden when 'Require text' field is set to 'Text input is optional'
|
||||
Given I choose "Edit question" action for "essay-min-max" in the question bank
|
||||
And I should see "Minimum word limit"
|
||||
And I should see "Maximum word limit"
|
||||
When I set the field "Require text" to "Text input is optional"
|
||||
Then I should not see "Minimum word limit"
|
||||
And I should not see "Minimum word limit"
|
@ -27,7 +27,7 @@ Feature: Preview Essay questions
|
||||
And I navigate to "Question bank" in current page administration
|
||||
|
||||
@javascript @_switch_window
|
||||
Scenario: Preview an Essay question and submit a partially correct response.
|
||||
Scenario: Preview an Essay question that uses the HTML editor.
|
||||
When I choose "Preview" action for "essay-001" in the question bank
|
||||
And I switch to "questionpreview" window
|
||||
And I set the field "How questions behave" to "Immediate feedback"
|
||||
@ -36,7 +36,7 @@ Feature: Preview Essay questions
|
||||
And I switch to the main window
|
||||
|
||||
@javascript @_switch_window
|
||||
Scenario: Preview an Essay question and submit a partially correct response.
|
||||
Scenario: Preview an Essay question that uses the HTML editor with embedded files.
|
||||
When I choose "Preview" action for "essay-002" in the question bank
|
||||
And I switch to "questionpreview" window
|
||||
And I set the field "How questions behave" to "Immediate feedback"
|
||||
@ -46,7 +46,7 @@ Feature: Preview Essay questions
|
||||
And I switch to the main window
|
||||
|
||||
@javascript @_switch_window
|
||||
Scenario: Preview an Essay question and submit a partially correct response.
|
||||
Scenario: Preview an Essay question that uses a plain text area.
|
||||
When I choose "Preview" action for "essay-003" in the question bank
|
||||
And I switch to "questionpreview" window
|
||||
And I set the field "How questions behave" to "Immediate feedback"
|
||||
|
@ -25,6 +25,8 @@
|
||||
<responseformat>editor</responseformat>
|
||||
<responserequired>1</responserequired>
|
||||
<responsefieldlines>15</responsefieldlines>
|
||||
<minwordlimit>0</minwordlimit>
|
||||
<maxwordlimit>0</maxwordlimit>
|
||||
<attachments>0</attachments>
|
||||
<attachmentsrequired>0</attachmentsrequired>
|
||||
<filetypeslist></filetypeslist>
|
||||
|
@ -51,6 +51,8 @@ class qtype_essay_test_helper extends question_test_helper {
|
||||
$q->responseformat = 'editor';
|
||||
$q->responserequired = 1;
|
||||
$q->responsefieldlines = 10;
|
||||
$q->minwordlimit = 0;
|
||||
$q->maxwordlimit = 0;
|
||||
$q->attachments = 0;
|
||||
$q->attachmentsrequired = 0;
|
||||
$q->filetypeslist = '';
|
||||
@ -86,6 +88,8 @@ class qtype_essay_test_helper extends question_test_helper {
|
||||
$fromform->responseformat = 'editor';
|
||||
$fromform->responserequired = 1;
|
||||
$fromform->responsefieldlines = 10;
|
||||
$fromform->minwordlimit = 0;
|
||||
$fromform->maxwordlimit = 0;
|
||||
$fromform->attachments = 0;
|
||||
$fromform->attachmentsrequired = 0;
|
||||
$fromform->filetypeslist = '';
|
||||
@ -138,6 +142,8 @@ class qtype_essay_test_helper extends question_test_helper {
|
||||
$fromform->responseformat = 'editorfilepicker';
|
||||
$fromform->responserequired = 1;
|
||||
$fromform->responsefieldlines = 10;
|
||||
$fromform->minwordlimit = 0;
|
||||
$fromform->maxwordlimit = 0;
|
||||
$fromform->attachments = 3;
|
||||
$fromform->attachmentsrequired = 0;
|
||||
$fromform->filetypeslist = '';
|
||||
@ -175,6 +181,8 @@ class qtype_essay_test_helper extends question_test_helper {
|
||||
$fromform->responseformat = 'plain';
|
||||
$fromform->responserequired = 1;
|
||||
$fromform->responsefieldlines = 10;
|
||||
$fromform->minwordlimit = 0;
|
||||
$fromform->maxwordlimit = 0;
|
||||
$fromform->attachments = 0;
|
||||
$fromform->attachmentsrequired = 0;
|
||||
$fromform->filetypeslist = '';
|
||||
|
@ -170,6 +170,25 @@ class qtype_essay_question_test extends advanced_testcase {
|
||||
$this->assertTrue($essay->is_complete_response(array('answer' => '0 times.')));
|
||||
$this->assertTrue($essay->is_complete_response(array('answer' => '0')));
|
||||
|
||||
// Test case for minimum and/or maximum word limit.
|
||||
$response = [];
|
||||
$response['answer'] = 'In this essay, I will be testing a function called check_input_word_count().';
|
||||
|
||||
$essay->minwordlimit = 50; // The answer is shorter than the required minimum word limit.
|
||||
$this->assertFalse($essay->is_complete_response($response));
|
||||
|
||||
$essay->minwordlimit = 10; // The word count meets the required minimum word limit.
|
||||
$this->assertTrue($essay->is_complete_response($response));
|
||||
|
||||
// The word count meets the required minimum and maximum word limit.
|
||||
$essay->minwordlimit = 10;
|
||||
$essay->maxwordlimit = 15;
|
||||
$this->assertTrue($essay->is_complete_response($response));
|
||||
|
||||
// Unset the minwordlimit/maxwordlimit variables to avoid the extra check in is_complete_response() for further tests.
|
||||
$essay->minwordlimit = null;
|
||||
$essay->maxwordlimit = null;
|
||||
|
||||
// Test the case where two files are required.
|
||||
$essay->attachmentsrequired = 2;
|
||||
|
||||
@ -236,7 +255,6 @@ class qtype_essay_question_test extends advanced_testcase {
|
||||
$essay->reponserequired = 1;
|
||||
$this->assertTrue($essay->is_complete_response(
|
||||
array('attachments' => $attachments[1])));
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@ -262,4 +280,88 @@ class qtype_essay_question_test extends advanced_testcase {
|
||||
$this->assertEquals('', $options['responsetemplate']);
|
||||
$this->assertEquals(FORMAT_MOODLE, $options['responsetemplateformat']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test get_validation_error when users submit their input text.
|
||||
*
|
||||
* (The tests are done with a fixed 14-word response.)
|
||||
*
|
||||
* @dataProvider get_min_max_wordlimit_test_cases()
|
||||
* @param int $responserequired whether response required (yes = 1, no = 0)
|
||||
* @param int $minwordlimit minimum word limit
|
||||
* @param int $maxwordlimit maximum word limit
|
||||
* @param string $expected error message | null
|
||||
*/
|
||||
public function test_get_validation_error(int $responserequired,
|
||||
int $minwordlimit, int $maxwordlimit, string $expected): void {
|
||||
$question = test_question_maker::make_an_essay_question();
|
||||
$response = ['answer' => 'In this essay, I will be testing a function called check_input_word_count().'];
|
||||
$question->responserequired = $responserequired;
|
||||
$question->minwordlimit = $minwordlimit;
|
||||
$question->maxwordlimit = $maxwordlimit;
|
||||
$actual = $question->get_validation_error($response);
|
||||
$this->assertEquals($expected, $actual);
|
||||
}
|
||||
|
||||
/**
|
||||
* Data provider for get_validation_error test.
|
||||
*
|
||||
* @return array the test cases.
|
||||
*/
|
||||
public function get_min_max_wordlimit_test_cases(): array {
|
||||
return [
|
||||
'text input required, min/max word limit not set' => [1, 0, 0, ''],
|
||||
'text input required, min/max word limit valid (within the boundaries)' => [1, 10, 25, ''],
|
||||
'text input required, min word limit not reached' => [1, 15, 25,
|
||||
get_string('minwordlimitboundary', 'qtype_essay', ['count' => 14, 'limit' => 15])],
|
||||
'text input required, max word limit is exceeded' => [1, 5, 12,
|
||||
get_string('maxwordlimitboundary', 'qtype_essay', ['count' => 14, 'limit' => 12])],
|
||||
'text input not required, min/max word limit not set' => [0, 5, 12, ''],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Test get_word_count_message_for_review when users submit their input text.
|
||||
*
|
||||
* (The tests are done with a fixed 14-word response.)
|
||||
*
|
||||
* @dataProvider get_word_count_message_for_review_test_cases()
|
||||
* @param int|null $minwordlimit minimum word limit
|
||||
* @param int|null $maxwordlimit maximum word limit
|
||||
* @param string $expected error message | null
|
||||
*/
|
||||
public function test_get_word_count_message_for_review(?int $minwordlimit, ?int $maxwordlimit, string $expected): void {
|
||||
$question = test_question_maker::make_an_essay_question();
|
||||
$question->minwordlimit = $minwordlimit;
|
||||
$question->maxwordlimit = $maxwordlimit;
|
||||
|
||||
$response = ['answer' => 'One two three four five six seven eight nine ten eleven twelve thirteen fourteen.'];
|
||||
$this->assertEquals($expected, $question->get_word_count_message_for_review($response));
|
||||
}
|
||||
|
||||
/**
|
||||
* Data provider for test_get_word_count_message_for_review.
|
||||
*
|
||||
* @return array the test cases.
|
||||
*/
|
||||
public function get_word_count_message_for_review_test_cases() {
|
||||
return [
|
||||
'No limit' =>
|
||||
[null, null, ''],
|
||||
'min and max, answer within range' =>
|
||||
[10, 25, get_string('wordcount', 'qtype_essay', 14)],
|
||||
'min and max, answer too short' =>
|
||||
[15, 25, get_string('wordcounttoofew', 'qtype_essay', ['count' => 14, 'limit' => 15])],
|
||||
'min and max, answer too long' =>
|
||||
[5, 12, get_string('wordcounttoomuch', 'qtype_essay', ['count' => 14, 'limit' => 12])],
|
||||
'min only, answer within range' =>
|
||||
[14, null, get_string('wordcount', 'qtype_essay', 14)],
|
||||
'min only, answer too short' =>
|
||||
[15, null, get_string('wordcounttoofew', 'qtype_essay', ['count' => 14, 'limit' => 15])],
|
||||
'max only, answer within range' =>
|
||||
[null, 14, get_string('wordcount', 'qtype_essay', 14)],
|
||||
'max only, answer too short' =>
|
||||
[null, 13, get_string('wordcounttoomuch', 'qtype_essay', ['count' => 14, 'limit' => 13])],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@ -619,4 +619,113 @@ class qtype_essay_walkthrough_testcase extends qbehaviour_walkthrough_test_base
|
||||
$this->check_step_count(3);
|
||||
$this->save_quba();
|
||||
}
|
||||
|
||||
public function test_deferred_feedback_word_limits() {
|
||||
global $PAGE;
|
||||
|
||||
// The current text editor depends on the users profile setting - so it needs a valid user.
|
||||
$this->setAdminUser();
|
||||
// Required to init a text editor.
|
||||
$PAGE->set_url('/');
|
||||
|
||||
// Create an essay question.
|
||||
/** @var qtype_essay_question $q */
|
||||
$q = test_question_maker::make_question('essay', 'editor');
|
||||
$q->minwordlimit = 3;
|
||||
$q->maxwordlimit = 7;
|
||||
$this->start_attempt_at_question($q, 'deferredfeedback', 1);
|
||||
|
||||
// Check the initial state.
|
||||
$this->check_current_state(question_state::$todo);
|
||||
$this->check_current_mark(null);
|
||||
$this->render();
|
||||
$this->check_contains_textarea('answer', '');
|
||||
$this->check_current_output(
|
||||
$this->get_contains_question_text_expectation($q),
|
||||
$this->get_does_not_contain_validation_error_expectation(),
|
||||
$this->get_does_not_contain_feedback_expectation());
|
||||
|
||||
// Save a response that is too short (and give the word-count code a tricky case).
|
||||
$response = '<div class="card">
|
||||
<div class="card-body">
|
||||
<h3 class="card-title">One</h3>
|
||||
<div class="card-text">
|
||||
<ul>
|
||||
<li>Two</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>';
|
||||
$this->process_submission(['answer' => $response, 'answerformat' => FORMAT_HTML]);
|
||||
|
||||
// Verify.
|
||||
$this->check_current_state(question_state::$invalid);
|
||||
$this->check_current_mark(null);
|
||||
$this->render();
|
||||
$this->check_contains_textarea('answer', $response);
|
||||
$this->check_current_output(
|
||||
$this->get_contains_question_text_expectation($q),
|
||||
$this->get_contains_validation_error_expectation(),
|
||||
$this->get_does_not_contain_feedback_expectation());
|
||||
$this->assertStringContainsString('This question requires a response of at least 3 words and you are ' .
|
||||
'attempting to submit 2 words. Please expand your response and try again.',
|
||||
$this->currentoutput);
|
||||
|
||||
// Save a response that is just long enough.
|
||||
$this->process_submission(['answer' => '<p>One two three.</p>', 'answerformat' => FORMAT_HTML]);
|
||||
|
||||
// Verify.
|
||||
$this->check_current_state(question_state::$complete);
|
||||
$this->check_current_mark(null);
|
||||
$this->render();
|
||||
$this->check_contains_textarea('answer', '<p>One two three.</p>');
|
||||
$this->check_current_output(
|
||||
$this->get_contains_question_text_expectation($q),
|
||||
$this->get_does_not_contain_validation_error_expectation(),
|
||||
$this->get_does_not_contain_feedback_expectation());
|
||||
|
||||
// Save a response that is as long as possible short.
|
||||
$this->process_submission(['answer' => '<p>One two three four five six seven.</p>',
|
||||
'answerformat' => FORMAT_HTML]);
|
||||
|
||||
// Verify.
|
||||
$this->check_current_state(question_state::$complete);
|
||||
$this->check_current_mark(null);
|
||||
$this->render();
|
||||
$this->check_contains_textarea('answer', '<p>One two three four five six seven.</p>');
|
||||
$this->check_current_output(
|
||||
$this->get_contains_question_text_expectation($q),
|
||||
$this->get_does_not_contain_validation_error_expectation(),
|
||||
$this->get_does_not_contain_feedback_expectation());
|
||||
|
||||
// Save a response that is just too long.
|
||||
$this->process_submission(['answer' => '<p>One two three four five six seven eight.</p>',
|
||||
'answerformat' => FORMAT_HTML]);
|
||||
|
||||
// Verify.
|
||||
$this->check_current_state(question_state::$invalid);
|
||||
$this->check_current_mark(null);
|
||||
$this->render();
|
||||
$this->check_contains_textarea('answer', '<p>One two three four five six seven eight.</p>');
|
||||
$this->check_current_output(
|
||||
$this->get_contains_question_text_expectation($q),
|
||||
$this->get_contains_validation_error_expectation(),
|
||||
$this->get_does_not_contain_feedback_expectation());
|
||||
$this->assertStringContainsString('The word limit for this question is 7 words and you are ' .
|
||||
'attempting to submit 8 words. Please shorten your response and try again.',
|
||||
$this->currentoutput);
|
||||
|
||||
// Now submit all and finish.
|
||||
$this->finish();
|
||||
|
||||
// Verify.
|
||||
$this->check_current_state(question_state::$needsgrading);
|
||||
$this->check_current_mark(null);
|
||||
$this->render();
|
||||
$this->check_current_output(
|
||||
$this->get_contains_question_text_expectation($q),
|
||||
$this->get_contains_general_feedback_expectation($q));
|
||||
$this->assertStringContainsString('Word count: 8, more than the limit of 7 words.',
|
||||
$this->currentoutput);
|
||||
}
|
||||
}
|
||||
|
@ -26,7 +26,7 @@
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
$plugin->component = 'qtype_essay';
|
||||
$plugin->version = 2021052501;
|
||||
$plugin->version = 2021052502;
|
||||
|
||||
$plugin->requires = 2021052500;
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user