diff --git a/mod/feedback/item/feedback_item_class.php b/mod/feedback/item/feedback_item_class.php index 9d914b72399..5585adb49ea 100644 --- a/mod/feedback/item/feedback_item_class.php +++ b/mod/feedback/item/feedback_item_class.php @@ -154,7 +154,7 @@ abstract class feedback_item_base { * Prepares the value for exporting to Excel * * @param object $item the db-object from feedback_item - * @param string $value a item-related value from feedback_values + * @param object $value object with item-related value from feedback_values in the 'value' property * @return string */ abstract public function get_printval($item, $value); diff --git a/mod/feedback/tests/behat/groups.feature b/mod/feedback/tests/behat/groups.feature index 7a91900b5a1..ca710d649d7 100644 --- a/mod/feedback/tests/behat/groups.feature +++ b/mod/feedback/tests/behat/groups.feature @@ -50,43 +50,28 @@ Feature: Feedbacks in courses with groups | feedback | Site feedback | Acceptance test site | feedback0 | 2 | 1 | 1 | 1 | | feedback | Course feedback | C1 | feedback1 | 2 | 1 | 1 | 0 | | feedback | Course anon feedback | C1 | feedback2 | 1 | 1 | 1 | 0 | - And I am on the "Site feedback" "feedback activity" page logged in as manager - And I click on "Edit questions" "link" in the "[role=main]" "css_element" - And I add a "Multiple choice" question to the feedback with: - | Question | Do you like our site? | - | Label | multichoice2 | - | Multiple choice type | Multiple choice - single answer | - | Hide the "Not selected" option | Yes | - | Multiple choice values | Yes of course\nNot at all\nI don't know | - And I log out @javascript Scenario: Non anonymous feedback with groups in a course - Given I am on the "Course feedback" "feedback activity" page logged in as teacher - And I click on "Edit questions" "link" in the "[role=main]" "css_element" - And I add a "Multiple choice" question to the feedback with: - | Question | Do you like this course? | - | Label | multichoice1 | - | Multiple choice type | Multiple choice - single answer | - | Hide the "Not selected" option | Yes | - | Multiple choice values | Yes of course\nNot at all\nI don't know | - And I log out - And I log in as "user1" and complete feedback "Course feedback" in course "Course 1" with: - | Not at all | 1 | - And I log in as "user2" and complete feedback "Course feedback" in course "Course 1" with: - | I don't know | 1 | - And I log in as "user3" and complete feedback "Course feedback" in course "Course 1" with: - | Not at all | 1 | - And I log in as "user4" and complete feedback "Course feedback" in course "Course 1" with: - | Yes of course | 1 | - And I log in as "user5" and complete feedback "Course feedback" in course "Course 1" with: - | Yes of course | 1 | - And I log in as "user6" and complete feedback "Course feedback" in course "Course 1" with: - | Not at all | 1 | - And I log in as "user7" and complete feedback "Course feedback" in course "Course 1" with: - | I don't know | 1 | + Given the following "mod_feedback > question" exists: + | activity | feedback1 | + | name | Do you like this course? | + | questiontype | multichoice | + | label | multichoice1 | + | subtype | r | + | hidenoselect | 1 | + | values | Yes of course\nNot at all\nI don't know | + And the following "mod_feedback > responses" exist: + | activity | user | Do you like this course? | + | feedback1 | user1 | Not at all | + | feedback1 | user2 | I don't know | + | feedback1 | user3 | Not at all | + | feedback1 | user4 | Yes of course | + | feedback1 | user5 | Yes of course | + | feedback1 | user6 | Not at all | + | feedback1 | user7 | I don't know | # View analysis, user1 should only see one group - group 1 - And I am on the "Course feedback" "feedback activity" page logged in as user1 + When I am on the "Course feedback" "feedback activity" page logged in as user1 And I follow "Analysis" And I should see "Separate groups: Group 1" And I show chart data for the "multichoice1" feedback @@ -152,37 +137,44 @@ Feature: Feedbacks in courses with groups And I should see "Username 3" @javascript - Scenario: Anonymous feedback with groups in a course - Given I am on the "Course anon feedback" "feedback activity" page logged in as teacher - And I click on "Edit questions" "link" in the "[role=main]" "css_element" - And I add a "Multiple choice" question to the feedback with: - | Question | Do you like this course? | - | Label | multichoice1 | - | Multiple choice type | Multiple choice - single answer | - | Hide the "Not selected" option | Yes | - | Multiple choice values | Yes of course\nNot at all\nI don't know | - And I log out - And I log in as "user1" and complete feedback "Course anon feedback" in course "Course 1" with: - | Not at all | 1 | - And I am on the "Course anon feedback" "feedback activity" page logged in as user1 + Scenario: Anonymous feedback with groups in a course - insufficient responses + Given the following "mod_feedback > question" exists: + | activity | feedback2 | + | name | Do you like this course? | + | questiontype | multichoice | + | label | multichoice1 | + | subtype | r | + | hidenoselect | 1 | + | values | Yes of course\nNot at all\nI don't know | + And the following "mod_feedback > responses" exist: + | activity | user | Do you like this course? | + | feedback2 | user1 | Not at all | + When I am on the "Course anon feedback" "feedback activity" page logged in as user1 And I follow "Analysis" + Then I should not see "Yes of course" And I should see "There are insufficient responses for this group" - And I should not see "Yes of course" - And I log out - And I log in as "user2" and complete feedback "Course anon feedback" in course "Course 1" with: - | I don't know | 1 | - And I log in as "user3" and complete feedback "Course anon feedback" in course "Course 1" with: - | Not at all | 1 | - And I log in as "user4" and complete feedback "Course anon feedback" in course "Course 1" with: - | Yes of course | 1 | - And I log in as "user5" and complete feedback "Course anon feedback" in course "Course 1" with: - | Yes of course | 1 | - And I log in as "user6" and complete feedback "Course anon feedback" in course "Course 1" with: - | Not at all | 1 | - And I log in as "user7" and complete feedback "Course anon feedback" in course "Course 1" with: - | I don't know | 1 | + + @javascript + Scenario: Anonymous feedback with groups in a course + Given the following "mod_feedback > question" exists: + | activity | feedback2 | + | name | Do you like this course? | + | questiontype | multichoice | + | label | multichoice1 | + | subtype | r | + | hidenoselect | 1 | + | values | Yes of course\nNot at all\nI don't know | + And the following "mod_feedback > responses" exist: + | activity | user | Do you like this course? | + | feedback2 | user1 | Not at all | + | feedback2 | user2 | I don't know | + | feedback2 | user3 | Not at all | + | feedback2 | user4 | Yes of course | + | feedback2 | user5 | Yes of course | + | feedback2 | user6 | Not at all | + | feedback2 | user7 | I don't know | # View analysis, user1 should only see one group - group 1 - And I am on the "Course anon feedback" "feedback activity" page logged in as user1 + When I am on the "Course anon feedback" "feedback activity" page logged in as user1 And I follow "Analysis" And I should see "Separate groups: Group 1" And I show chart data for the "multichoice1" feedback diff --git a/mod/feedback/tests/generator/behat_mod_feedback_generator.php b/mod/feedback/tests/generator/behat_mod_feedback_generator.php new file mode 100644 index 00000000000..d2519290420 --- /dev/null +++ b/mod/feedback/tests/generator/behat_mod_feedback_generator.php @@ -0,0 +1,49 @@ +. + +/** + * Behat data generator for mod_feedback. + * + * @package mod_feedback + * @category test + * @copyright 2022 Noel De Martin + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class behat_mod_feedback_generator extends behat_generator_base { + + /** + * Get a list of the entities that Behat can create using the generator step. + * + * @return array + */ + protected function get_creatable_entities(): array { + return [ + 'questions' => [ + 'singular' => 'question', + 'datagenerator' => 'question', + 'required' => ['activity'], + 'switchids' => ['activity' => 'cmid'], + ], + 'responses' => [ + 'singular' => 'response', + 'datagenerator' => 'response', + 'required' => ['activity', 'user'], + 'switchids' => ['activity' => 'cmid', 'user' => 'userid'], + ], + ]; + } + +} diff --git a/mod/feedback/tests/generator/lib.php b/mod/feedback/tests/generator/lib.php index 402072290f5..1eedc41fb0f 100644 --- a/mod/feedback/tests/generator/lib.php +++ b/mod/feedback/tests/generator/lib.php @@ -83,6 +83,97 @@ class mod_feedback_generator extends testing_module_generator { return parent::create_instance($record, (array)$options); } + /** + * Create question. + * + * @param array $data Question data + * @return mixed Question instance + */ + public function create_question(array $data) { + global $DB; + + $questiontype = $data['questiontype'] ?? 'textfield'; + $cm = get_coursemodule_from_id('feedback', $data['cmid']); + $feedback = $DB->get_record('feedback', ['id' => $cm->instance]); + + unset($data['questiontype']); + unset($data['cmid']); + + if (isset($data['values'])) { + $data['values'] = $this->format_item_values($questiontype, $data['values']); + } + + return call_user_func([$this, "create_item_{$questiontype}"], $feedback, $data); + } + + /** + * Create response. + * + * @param array $data Response data. + * @return stdClass feedback_completed response instance. + */ + public function create_response(array $data): stdClass { + global $DB; + + $userid = $data['userid']; + $responsenumber = null; + $cm = get_coursemodule_from_id('feedback', $data['cmid']); + $feedback = $DB->get_record('feedback', ['id' => $cm->instance]); + $answers = []; + + if (isset($data['responsenumber']) && trim($data['responsenumber']) !== '') { + $responsenumber = $data['responsenumber']; + } + + if (isset($data['anonymous']) && trim($data['anonymous']) !== '') { + $anonymous = filter_var(trim($data['anonymous']), FILTER_VALIDATE_BOOLEAN); + $feedback->anonymous = $anonymous ? FEEDBACK_ANONYMOUS_YES : FEEDBACK_ANONYMOUS_NO; + } + + unset($data['cmid']); + unset($data['userid']); + unset($data['anonymous']); + unset($data['responsenumber']); + + foreach ($data as $question => $response) { + $item = $DB->get_record('feedback_item', ['name' => trim($question)], '*', MUST_EXIST); + + $answers["{$item->typ}_{$item->id}"] = $this->get_item_response_value($item, $response); + } + + $feedbackcompletion = new mod_feedback_completion( + $feedback, + $cm, + $cm->course, + false, + null, + $feedback->anonymous === FEEDBACK_ANONYMOUS_YES ? null : $userid, + $userid + ); + + if (!$feedbackcompletion->can_complete()) { + throw new coding_exception("User {$userid} cannot complete this feedback activity."); + } + + if (!$feedbackcompletion->is_open()) { + throw new coding_exception("This activity is not open."); + } + + $feedbackcompletion->set_module_viewed(); + $feedbackcompletion->save_response_tmp((object) $answers); + $feedbackcompletion->save_response(); + $completed = $feedbackcompletion->get_completed(); + + if (!is_null($responsenumber)) { + $DB->update_record('feedback_completed', [ + 'id' => $completed->id, + 'random_response' => $responsenumber, + ]); + } + + return $completed; + } + /** * Create info question item. * @@ -392,5 +483,71 @@ class mod_feedback_generator extends testing_module_generator { return feedback_create_pagebreak($feedback->id); } -} + /** + * Format feedback item values. + * + * This method will replace newline characters with the proper line separator for each question type. + * + * @param string $questiontype Question types + * @param string $values Values + * @return string Formatted values + */ + protected function format_item_values(string $questiontype, string $values): string { + global $CFG; + + if (!file_exists($CFG->dirroot.'/mod/feedback/item/'.$questiontype.'/lib.php')) { + throw new coding_exception("Question type '$questiontype' not found"); + } + + require_once($CFG->dirroot.'/mod/feedback/item/'.$questiontype.'/lib.php'); + + $questiontype = strtoupper($questiontype); + + if (defined("FEEDBACK_{$questiontype}_LINE_SEP")) { + return implode(constant("FEEDBACK_{$questiontype}_LINE_SEP"), explode('\n', $values)); + } + + return $values; + } + + /** + * Given a response to a feedback item, return its corresponding value. + * + * @param mixed $record Item record + * @param string $response Response name + * @return int|string Response value + */ + protected function get_item_response_value($record, string $response) { + if (strpos($record->typ, 'multichoice') === 0) { + $item = feedback_get_item_class($record->typ); + + return $this->get_choice_item_response_value($item, $record, $response); + } + + return $response; + } + + /** + * Given a response to a feedback choice item, return its corresponding value. + * + * @param feedback_item_base $item Feedback item + * @param mixed $record Item record + * @param string $response Response + * @param int $offset Choice to start looking from + * @return int Response choice index + */ + protected function get_choice_item_response_value(feedback_item_base $item, $record, string $response, int $offset = 1): int { + $printval = $item->get_printval($record, (object) ['value' => $offset]); + + if (empty($printval)) { + throw new coding_exception("Value '$offset' not found"); + } + + if ($printval === $response) { + return $offset; + } + + return $this->get_choice_item_response_value($item, $record, $response, $offset + 1); + } +}