MDL-59926 core_search: Allow Behat testing of results screens
authorsam marshall <s.marshall@open.ac.uk>
Thu, 24 Aug 2017 16:37:14 +0000 (17:37 +0100)
committersam marshall <s.marshall@open.ac.uk>
Mon, 4 Sep 2017 16:42:53 +0000 (17:42 +0100)
search/classes/manager.php
search/tests/behat/behat_search.php [new file with mode: 0644]
search/tests/behat/search_query.feature [new file with mode: 0644]

index 0ce1d84..f89b3a1 100644 (file)
@@ -136,8 +136,11 @@ class manager {
 
         $serverstatus = $engine->is_server_ready();
         if ($serverstatus !== true) {
 
         $serverstatus = $engine->is_server_ready();
         if ($serverstatus !== true) {
-            // Error message with no details as this is an exception that any user may find if the server crashes.
-            throw new \core_search\engine_exception('engineserverstatus', 'search');
+            // Skip this error in Behat when faking seach results.
+            if (!defined('BEHAT_SITE_RUNNING') || !get_config('core_search', 'behat_fakeresult')) {
+                // Error message with no details as this is an exception that any user may find if the server crashes.
+                throw new \core_search\engine_exception('engineserverstatus', 'search');
+            }
         }
 
         static::$instance = new \core_search\manager($engine);
         }
 
         static::$instance = new \core_search\manager($engine);
@@ -552,7 +555,39 @@ class manager {
      * @return \core_search\document[]
      */
     public function search(\stdClass $formdata, $limit = 0) {
      * @return \core_search\document[]
      */
     public function search(\stdClass $formdata, $limit = 0) {
-        global $USER;
+        // For Behat testing, the search results can be faked using a special step.
+        if (defined('BEHAT_SITE_RUNNING')) {
+            $fakeresult = get_config('core_search', 'behat_fakeresult');
+            if ($fakeresult) {
+                // Clear config setting.
+                unset_config('core_search', 'behat_fakeresult');
+
+                // Check query matches expected value.
+                $details = json_decode($fakeresult);
+                if ($formdata->q !== $details->query) {
+                    throw new \coding_exception('Unexpected search query: ' . $formdata->q);
+                }
+
+                // Create search documents from the JSON data.
+                $docs = [];
+                foreach ($details->results as $result) {
+                    $doc = new \core_search\document($result->itemid, $result->componentname,
+                            $result->areaname);
+                    foreach ((array)$result->fields as $field => $value) {
+                        $doc->set($field, $value);
+                    }
+                    foreach ((array)$result->extrafields as $field => $value) {
+                        $doc->set_extra($field, $value);
+                    }
+                    $area = $this->get_search_area($doc->get('areaid'));
+                    $doc->set_doc_url($area->get_doc_url($doc));
+                    $doc->set_context_url($area->get_context_url($doc));
+                    $docs[] = $doc;
+                }
+
+                return $docs;
+            }
+        }
 
         $limitcourseids = false;
         if (!empty($formdata->courseids)) {
 
         $limitcourseids = false;
         if (!empty($formdata->courseids)) {
diff --git a/search/tests/behat/behat_search.php b/search/tests/behat/behat_search.php
new file mode 100644 (file)
index 0000000..743dda2
--- /dev/null
@@ -0,0 +1,138 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Behat search-related step definitions.
+ *
+ * @package core_search
+ * @category test
+ * @copyright 2017 The Open University
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+// NOTE: no MOODLE_INTERNAL used, this file may be required by behat before including /config.php.
+require_once(__DIR__ . '/../../../lib/behat/behat_base.php');
+
+use Behat\Gherkin\Node\TableNode as TableNode;
+
+/**
+ * Behat search-related step definitions.
+ *
+ * @package core_search
+ * @category test
+ * @copyright 2017 The Open University
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class behat_search extends behat_base {
+    /**
+     * Create event when starting on the front page.
+     *
+     * @Given /^I search for "(?P<query>[^"]*)" using the header global search box$/
+     * @param string $query Query to search for
+     */
+    public function i_search_for_using_the_header_global_search_box($query) {
+        // Hover over the search icon.
+        $this->execute('behat_general::i_hover', ['i[title=Search]', 'css_element']);
+
+        // Set the field.
+        $this->execute('behat_forms::i_set_the_field_to', ['q', $query]);
+
+        // Submit the form.
+        $this->getSession()->executeScript('document.querySelector(".search-input-form.expanded").submit();');
+    }
+
+    /**
+     * Sets results which will be returned for the next search. It will only return links to
+     * activities at present.
+     *
+     * @Given /^global search expects the query "(?P<query>[^"]*)" and will return:$/
+     * @param string $query Expected query value (just used to check the query passed to the engine)
+     * @param TableNode $data Data rows
+     */
+    public function global_search_expects_the_query_and_will_return($query, TableNode $data) {
+        global $DB;
+        $outdata = new stdClass();
+        $outdata->query = $query;
+        $outdata->results = [];
+        foreach ($data->getHash() as $rowdata) {
+            // Check and get the data from the user-entered row.
+            $input = [
+                'type' => '',
+                'idnumber' => '',
+                'title' => '',
+                'content' => '',
+                'modified' => ''
+            ];
+            foreach ($rowdata as $key => $value) {
+                if (!array_key_exists($key, $input)) {
+                    throw new Exception('Field ' . $key . '" does not exist');
+                }
+                $input[$key] = $value;
+            }
+            foreach (['idnumber', 'type'] as $requiredfield) {
+                if (!$input[$requiredfield]) {
+                    throw new Exception('Must specify required field: ' . $requiredfield);
+                }
+            }
+
+            // Check type (we only support activity at present, this could be extended to allow
+            // faking other types of search results such as a user, course, or forum post).
+            if ($input['type'] !== 'activity') {
+                throw new Exception('Unsupported type: ' . $input['type']);
+            }
+
+            // Find the specified activity.
+            $idnumber = $input['idnumber'];
+            $cmid = $DB->get_field('course_modules', 'id', ['idnumber' => $idnumber], '*', IGNORE_MISSING);
+            if (!$cmid) {
+                throw new Exception('Cannot find activity with idnumber: ' . $idnumber);
+            }
+            list ($course, $cm) = get_course_and_cm_from_cmid($cmid);
+            $rec = $DB->get_record($cm->modname, ['id' => $cm->instance], '*', MUST_EXIST);
+            $context = \context_module::instance($cm->id);
+
+            // Set up the internal fields used in creating the search document.
+            $out = new stdClass();
+            $out->itemid = $cm->instance;
+            $out->componentname = 'mod_' . $cm->modname;
+            $out->areaname = 'activity';
+            $out->fields = new stdClass();
+            $out->fields->contextid = $context->id;
+            $out->fields->courseid = $course->id;
+            if ($input['title']) {
+                $out->fields->title = $input['title'];
+            } else {
+                $out->fields->title = $cm->name;
+            }
+            if ($input['content']) {
+                $out->fields->content = $input['content'];
+            } else {
+                $out->fields->content = content_to_text($rec->intro, $rec->introformat);
+            }
+            if ($input['modified']) {
+                $out->fields->modified = strtotime($input['modified']);
+            } else {
+                $out->fields->modified = $cm->added;
+            }
+            $out->extrafields = new stdClass();
+            $out->extrafields->coursefullname = $course->fullname;
+
+            $outdata->results[] = $out;
+        }
+
+        set_config('behat_fakeresult', json_encode($outdata), 'core_search');
+    }
+}
diff --git a/search/tests/behat/search_query.feature b/search/tests/behat/search_query.feature
new file mode 100644 (file)
index 0000000..129bbb7
--- /dev/null
@@ -0,0 +1,49 @@
+@core @core_search
+Feature: Use global search interface
+  In order to search for things
+  As a user
+  I need to be able to type search queries and see results
+
+  Background:
+    Given the following config values are set as admin:
+      | enableglobalsearch | 1 |
+    And the following "activities" exist:
+      | activity | name       | intro      | course               | idnumber |
+      | page     | PageName1  | PageDesc1  | Acceptance test site | PAGE1    |
+      | forum    | ForumName1 | ForumDesc1 | Acceptance test site | FORUM1   |
+    And I log in as "admin"
+
+  @javascript
+  Scenario: Search from header search box with one result
+    Given global search expects the query "frogs" and will return:
+      | type     | idnumber |
+      | activity | PAGE1    |
+    When I search for "frogs" using the header global search box
+    Then I should see "PageName1"
+    And I should see "PageDesc1"
+
+    # Check the link works.
+    And I follow "PageName1"
+    And I should see "PageName1" in the ".breadcrumb" "css_element"
+
+  @javascript
+  Scenario: Search from search page with two results
+    Given global search expects the query "zombies" and will return:
+      | nothing |
+    When I search for "zombies" using the header global search box
+    Then I should see "No results"
+    And I set the field "id_q" to "Toads"
+    And global search expects the query "Toads" and will return:
+      | type     | idnumber |
+      | activity | FORUM1   |
+      | activity | PAGE1    |
+    # You cannot press "Search" because there's a fieldset with the same name that gets in the way.
+    And I press "id_submitbutton"
+    And I should see "ForumName1"
+    And I should see "ForumDesc1"
+    And I should see "PageName1"
+    And I should see "PageDesc1"
+
+    # Check the link works.
+    And I follow "ForumName1"
+    And I should see "ForumName1" in the ".breadcrumb" "css_element"