diff --git a/lib/behat/classes/behat_session_trait.php b/lib/behat/classes/behat_session_trait.php index abbc003f897..55b5fc1ad3b 100644 --- a/lib/behat/classes/behat_session_trait.php +++ b/lib/behat/classes/behat_session_trait.php @@ -30,6 +30,7 @@ use Behat\Mink\Exception\ExpectationException; use Behat\Mink\Exception\ElementNotFoundException; use Behat\Mink\Exception\NoSuchWindowException; use Behat\Mink\Session; +use Behat\Testwork\Hook\Scope\HookScope; use Facebook\WebDriver\Exception\ScriptTimeoutException; use Facebook\WebDriver\WebDriverBy; use Facebook\WebDriver\WebDriverElement; @@ -1597,4 +1598,63 @@ EOF; return get_fast_modinfo($course)->get_cm($result->cmid); } + + /** + * Check whether any of the tags availble to the current scope match using the given callable. + * + * This function is typically called from within a Behat Hook, such as BeforeFeature, BeforeScenario, AfterStep, etc. + * + * The callable is used as the second argument to `array_filter()`, and is passed a single string argument for each of the + * tags available in the scope. + * + * The tags passed will include: + * - For a FeatureScope, the Feature tags only + * - For a ScenarioScope, the Feature and Scenario tags + * - For a StepScope, the Feature, Scenario, and Step tags + * + * An example usage may be: + * + * // Note: phpDoc beforeStep attribution not shown. + * public function before_step(StepScope $scope) { + * $callback = function (string $tag): bool { + * return $tag === 'editor_atto' || substr($tag, 0, 5) === 'atto_'; + * }; + * + * if (!self::scope_tags_match($scope, $callback)) { + * return; + * } + * + * // Do something here. + * } + * + * @param HookScope $scope The scope to check + * @param callable $callback The callable to use to check the scope + * @return boolean Whether any of the scope tags match + */ + public static function scope_tags_match(HookScope $scope, callable $callback): bool { + $tags = []; + + if (is_subclass_of($scope, \Behat\Behat\Hook\Scope\FeatureScope::class)) { + $tags = $scope->getFeature()->getTags(); + } + + if (is_subclass_of($scope, \Behat\Behat\Hook\Scope\ScenarioScope::class)) { + $tags = array_merge( + $scope->getFeature()->getTags(), + $scope->getScenario()->getTags() + ); + } + + if (is_subclass_of($scope, \Behat\Behat\Hook\Scope\StepScope::class)) { + $tags = array_merge( + $scope->getFeature()->getTags(), + $scope->getScenario()->getTags(), + $scope->getStep()->getTags() + ); + } + + $matches = array_filter($tags, $callback); + + return !empty($matches); + } } diff --git a/lib/editor/atto/tests/behat/behat_editor_atto.php b/lib/editor/atto/tests/behat/behat_editor_atto.php index 94c9d5a90ab..a982bc5cc68 100644 --- a/lib/editor/atto/tests/behat/behat_editor_atto.php +++ b/lib/editor/atto/tests/behat/behat_editor_atto.php @@ -23,6 +23,8 @@ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ +use Behat\Behat\Hook\Scope\BeforeScenarioScope; + // NOTE: no MOODLE_INTERNAL test here, this file may be required by behat before including /config.php. require_once(__DIR__ . '/../../../../behat/behat_base.php'); @@ -59,6 +61,24 @@ class behat_editor_atto extends behat_base { $field->select_text(); } + /** + * Ensure that the Atto editor is used for all tests using the editor_atto, or atto_* tags. + * + * This ensures, whatever the default editor, that the Atto editor is used for these tests. + * + * @BeforeScenario + * @param BeforeScenarioScope $scope The Behat Scope + */ + public function set_default_editor_flag(BeforeScenarioScope $scope): void { + // This only applies to a scenario which matches the editor_atto, or an atto subplugin. + $callback = function (string $tag): bool { + return $tag === 'editor_atto' || substr($tag, 0, 5) === 'atto_'; + }; + if (!self::scope_tags_match($scope, $callback)) { + return; + } + + $this->execute('behat_general::the_default_editor_is_set_to', ['atto']); + } } - diff --git a/lib/tests/behat/behat_general.php b/lib/tests/behat/behat_general.php index 72a893fb46c..18103b8d9ac 100644 --- a/lib/tests/behat/behat_general.php +++ b/lib/tests/behat/behat_general.php @@ -2190,4 +2190,33 @@ EOF; $class = core_plugin_manager::resolve_plugininfo_class($plugintype); $class::enable_plugin($plugin, true); } + + /** + * Set the default text editor to the named text editor. + * + * @Given the default editor is set to :editor + * @param string $editor + * @throws ExpectationException If the specified editor is not available. + */ + public function the_default_editor_is_set_to(string $editor): void { + global $CFG; + + // Check if the provided editor is available. + if (!array_key_exists($editor, editors_get_available())) { + throw new ExpectationException( + "Unable to set the editor to {$editor} as it is not installed. The available editors are: " . + implode(', ', array_keys(editors_get_available())), + $this->getSession() + ); + } + + // Make the provided editor the default one in $CFG->texteditors by + // moving it to the first [editor],atto,tiny,tinymce,textarea on the list. + $list = explode(',', $CFG->texteditors); + array_unshift($list, $editor); + $list = array_unique($list); + + // Set the list new list of editors. + set_config('texteditors', implode(',', $list)); + } }