Merge branch 'MDL-75105_401_STABLE' of https://github.com/marxjohnson/moodle into MOODLE_401_STABLE

This commit is contained in:
Andrew Nicols 2022-12-21 21:11:22 +08:00 committed by Ilya Tregubov
commit 5c15c1f211
14 changed files with 460 additions and 1 deletions

11
admin/tool/behat/amd/build/steps.min.js vendored Normal file
View File

@ -0,0 +1,11 @@
define("tool_behat/steps",["exports","core/ajax","core/templates","core/pending"],(function(_exports,_ajax,_templates,_pending){function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}}
/**
* Enhancements for the step definitions page.
*
* @module tool_behat/steps
* @copyright 2022 Catalyst IT EU
* @author Mark Johnson <mark.johnson@catalyst-eu.net>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.init=void 0,_ajax=_interopRequireDefault(_ajax),_templates=_interopRequireDefault(_templates),_pending=_interopRequireDefault(_pending);_exports.init=()=>{document.addEventListener("change",(async e=>{const entityElement=e.target.closest(".entities"),stepElement=e.target.closest(".stepcontent");if(!entityElement||!stepElement)return;const pendingPromise=new _pending.default("tool_behat/steps:change"),entityData=await(entityType=e.target.value,_ajax.default.call([{methodname:"tool_behat_get_entity_generator",args:{entitytype:entityType}}])[0]);var entityType;const{html:html,js:js}=await(entityData=>{var _entityData$required;return null!==(_entityData$required=entityData.required)&&void 0!==_entityData$required&&_entityData$required.length?_templates.default.renderForPromise("tool_behat/steprequiredfields",{fields:entityData.required}):Promise.resolve({html:"",js:""})})(entityData),stepRequiredFields=stepElement.querySelector(".steprequiredfields");stepRequiredFields?await _templates.default.replaceNode(stepRequiredFields,html,js):await _templates.default.appendNodeContents(stepElement,html,js),pendingPromise.resolve()}))}}));
//# sourceMappingURL=steps.min.js.map

View File

@ -0,0 +1 @@
{"version":3,"file":"steps.min.js","sources":["../src/steps.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see <http://www.gnu.org/licenses/>.\n\nimport Ajax from 'core/ajax';\nimport Templates from 'core/templates';\nimport PendingJS from 'core/pending';\n\n/**\n * Enhancements for the step definitions page.\n *\n * @module tool_behat/steps\n * @copyright 2022 Catalyst IT EU\n * @author Mark Johnson <mark.johnson@catalyst-eu.net>\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\n/**\n * Call the get_entity_generator web service function\n *\n * Takes the name of an entity generator and returns an object containing a list of the required fields.\n *\n * @param {String} entityType\n * @returns {Promise}\n */\nconst getGeneratorEntities = (entityType) => Ajax.call([{\n methodname: 'tool_behat_get_entity_generator',\n args: {entitytype: entityType}\n}])[0];\n\n/**\n * Render HTML for required fields\n *\n * Takes the entity data returned from getGeneratorEntities and renders the HTML to display the required fields.\n *\n * @param {String} entityData\n * @return {Promise}\n */\nconst getRequiredFieldsContent = (entityData) => {\n if (!entityData.required?.length) {\n return Promise.resolve({\n html: '',\n js: ''\n });\n }\n return Templates.renderForPromise('tool_behat/steprequiredfields', {fields: entityData.required});\n};\n\nexport const init = () => {\n // When an entity is selected in the \"the following exist\" step, fetch and display the required fields.\n document.addEventListener('change', async(e) => {\n const entityElement = e.target.closest('.entities');\n const stepElement = e.target.closest('.stepcontent');\n if (!entityElement || !stepElement) {\n return;\n }\n\n const pendingPromise = new PendingJS('tool_behat/steps:change');\n\n const entityData = await getGeneratorEntities(e.target.value);\n const {html, js} = await getRequiredFieldsContent(entityData);\n\n const stepRequiredFields = stepElement.querySelector('.steprequiredfields');\n if (stepRequiredFields) {\n await Templates.replaceNode(stepRequiredFields, html, js);\n } else {\n await Templates.appendNodeContents(stepElement, html, js);\n }\n pendingPromise.resolve();\n });\n};\n"],"names":["document","addEventListener","async","entityElement","e","target","closest","stepElement","pendingPromise","PendingJS","entityData","entityType","value","Ajax","call","methodname","args","entitytype","html","js","required","_entityData$required","length","Templates","renderForPromise","fields","Promise","resolve","getRequiredFieldsContent","stepRequiredFields","querySelector","replaceNode","appendNodeContents"],"mappings":";;;;;;;;4NA2DoB,KAEhBA,SAASC,iBAAiB,UAAUC,MAAAA,UAC1BC,cAAgBC,EAAEC,OAAOC,QAAQ,aACjCC,YAAcH,EAAEC,OAAOC,QAAQ,oBAChCH,gBAAkBI,yBAIjBC,eAAiB,IAAIC,iBAAU,2BAE/BC,iBAlCgBC,WAkCwBP,EAAEC,OAAOO,MAlClBC,cAAKC,KAAK,CAAC,CACpDC,WAAY,kCACZC,KAAM,CAACC,WAAYN,eACnB,IAH0BA,IAAAA,iBAmChBO,KAACA,KAADC,GAAOA,SAtBaT,CAAAA,0EACzBA,WAAWU,0CAAXC,qBAAqBC,OAMnBC,mBAAUC,iBAAiB,gCAAiC,CAACC,OAAQf,WAAWU,WAL5EM,QAAQC,QAAQ,CACnBT,KAAM,GACNC,GAAI,MAkBiBS,CAAyBlB,YAE5CmB,mBAAqBtB,YAAYuB,cAAc,uBACjDD,yBACMN,mBAAUQ,YAAYF,mBAAoBX,KAAMC,UAEhDI,mBAAUS,mBAAmBzB,YAAaW,KAAMC,IAE1DX,eAAemB"}

View File

@ -0,0 +1,82 @@
// 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/>.
import Ajax from 'core/ajax';
import Templates from 'core/templates';
import PendingJS from 'core/pending';
/**
* Enhancements for the step definitions page.
*
* @module tool_behat/steps
* @copyright 2022 Catalyst IT EU
* @author Mark Johnson <mark.johnson@catalyst-eu.net>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
/**
* Call the get_entity_generator web service function
*
* Takes the name of an entity generator and returns an object containing a list of the required fields.
*
* @param {String} entityType
* @returns {Promise}
*/
const getGeneratorEntities = (entityType) => Ajax.call([{
methodname: 'tool_behat_get_entity_generator',
args: {entitytype: entityType}
}])[0];
/**
* Render HTML for required fields
*
* Takes the entity data returned from getGeneratorEntities and renders the HTML to display the required fields.
*
* @param {String} entityData
* @return {Promise}
*/
const getRequiredFieldsContent = (entityData) => {
if (!entityData.required?.length) {
return Promise.resolve({
html: '',
js: ''
});
}
return Templates.renderForPromise('tool_behat/steprequiredfields', {fields: entityData.required});
};
export const init = () => {
// When an entity is selected in the "the following exist" step, fetch and display the required fields.
document.addEventListener('change', async(e) => {
const entityElement = e.target.closest('.entities');
const stepElement = e.target.closest('.stepcontent');
if (!entityElement || !stepElement) {
return;
}
const pendingPromise = new PendingJS('tool_behat/steps:change');
const entityData = await getGeneratorEntities(e.target.value);
const {html, js} = await getRequiredFieldsContent(entityData);
const stepRequiredFields = stepElement.querySelector('.steprequiredfields');
if (stepRequiredFields) {
await Templates.replaceNode(stepRequiredFields, html, js);
} else {
await Templates.appendNodeContents(stepElement, html, js);
}
pendingPromise.resolve();
});
};

View File

@ -0,0 +1,88 @@
<?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/>.
/**
* Return data about an entity generator.
*
* @package tool_behat
* @copyright 2022 onwards Catalyst IT EU {@link https://catalyst-eu.net}
* @author Mark Johnson <mark.johnson@catalyst-eu.net>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace tool_behat\external;
defined('MOODLE_INTERNAL') || die();
require_once($CFG->libdir . '/externallib.php');
require_once($CFG->dirroot . '/vendor/autoload.php'); // Ensure we can load Behat and Facebook namespaces in behat libraries.
require_once($CFG->libdir . '/tests/behat/behat_data_generators.php');
/**
* External function for getting properties of entity generators.
*/
class get_entity_generator extends \external_api {
/**
* Define parameters for external function.
*
* The parameter is either in the format 'entity' or 'component_name > entity'. There is no appropriate param type for a
* string like this containing angle brackets, so we will do PARAM_RAW. The value will be parsed by
* behat_data_generators::parse_entity_type, which validates the format of the parameter and throws an exception if it is not
* correct.
*
* @return \external_function_parameters
*/
public static function execute_parameters(): \external_function_parameters {
return new \external_function_parameters([
'entitytype' => new \external_value(PARAM_RAW, 'Entity type that can be created by a generator.'),
]);
}
/**
* Return a list of the required fields for a given entity type.
*
* @param string $entitytype
* @return array
*/
public static function execute(string $entitytype): array {
$params = self::validate_parameters(self::execute_parameters(), ['entitytype' => $entitytype]);
$context = \context_system::instance();
self::validate_context($context);
require_capability('moodle/site:config', $context);
$generators = new \behat_data_generators();
$entity = $generators->get_entity($params['entitytype']);
return ['required' => $entity['required']];
}
/**
* Define return values.
*
* Return required fields
*
* @return \external_single_structure
*/
public static function execute_returns(): \external_single_structure {
return new \external_single_structure([
'required' => new \external_multiple_structure(
new \external_value(PARAM_TEXT, 'Required field'),
'Required fields',
VALUE_OPTIONAL
),
]);
}
}

View File

@ -0,0 +1,37 @@
<?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/>.
/**
* Define web service functions for tool_behat
*
* @package tool_behat
* @copyright 2022 onwards Catalyst IT EU {@link https://catalyst-eu.net}
* @author Mark Johnson <mark.johnson@catalyst-eu.net>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
$functions = [
'tool_behat_get_entity_generator' => [
'classname' => 'tool_behat\\external\\get_entity_generator',
'methodname' => 'execute',
'description' => 'Get the generator details for an entity',
'type' => 'read',
'ajax' => true,
'capabilities' => 'moodle/site:config'
]
];

View File

@ -56,6 +56,7 @@ if ($components) {
$form = new steps_definitions_form(null, array('components' => $componentswithsteps));
// Output contents.
$PAGE->requires->js_call_amd('tool_behat/steps', 'init');
$renderer = $PAGE->get_renderer('tool_behat');
echo $renderer->render_stepsdefinitions($steps, $form);

View File

@ -24,6 +24,8 @@
defined('MOODLE_INTERNAL') || die();
require_once($CFG->libdir . '/behat/classes/behat_generator_base.php');
/**
* Renderer for behat tool web features
*
@ -106,6 +108,32 @@ class tool_behat_renderer extends plugin_renderer_base {
},
$stepsdefinitions
);
$elementstrings = [];
$count = 1;
$stepsdefinitions = preg_replace_callback('/(the following ")ELEMENT\d?_STRING(" exist:)/',
function($matches) use (&$elementstrings, &$count) {
// Replace element type arguments with a user-friendly select.
if (empty($elementstrings)) {
$behatgenerators = new behat_data_generators();
$componententities = $behatgenerators->get_all_entities();
ksort($componententities);
$elementstrings = [];
foreach ($componententities as $component => $entities) {
asort($entities);
foreach ($entities as $entity) {
$string = ($component === 'core') ? $entity : $component . ' > ' . $entity;
$elementstrings[$string] = $string;
}
}
}
$select = html_writer::select($elementstrings, 'entities' . $count, '', ['' => 'choosedots'],
['class' => 'entities']);
$count++;
return $matches[1] . $select . $matches[2];
},
$stepsdefinitions
);
}
// Steps definitions.

View File

@ -29,3 +29,9 @@
#page-admin-tool-behat-index .steps-definitions .stepregex {
color: #060;
}
#page-admin-tool-behat-index .steprequiredfields {
font-weight: bold;
font-size: 1em;
margin-top: 1em;
}

View File

@ -0,0 +1,33 @@
{{!
This file is part of Moodle - https://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/>.
}}
{{!
@template tool_behat/steprequiredfields
Display a Gherkin-style table row showing required columns for a step.
Example context (json):
{
"fields": [
"user",
"role",
"context"
]
}
}}
<pre class="steprequiredfields">
|{{#fields}} {{{.}}} |{{/fields}}
</pre>

View File

@ -13,6 +13,10 @@ Feature: List the system steps definitions
Scenario: Accessing the list
Then I should see "Step definitions"
And I should not see "There aren't steps definitions matching this filter"
And I should not see "the following \"ELEMENT_STRING\" exist:"
And "entities1" "select" should exist
And "users" "option" should exist in the "entities1" "select"
And "mod_assign > submissions" "option" should exist in the "entities1" "select"
@javascript
Scenario: Filtering by type
@ -34,3 +38,11 @@ Feature: List the system steps definitions
Then I should not see "There aren't steps definitions matching this filter"
And I should see "Checks the provided element and selector type exists in the current page."
And I should see "Checks that an element and selector type exists in another element and selector type on the current page."
@javascript
Scenario: Get required fields
Given I set the field "entities1" to "users"
And I should see "| username |"
When I set the field "entities1" to "mod_quiz > user overrides"
Then I should not see "| username |"
And I should see "| quiz | user |"

View File

@ -0,0 +1,104 @@
<?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/>.
/**
* Unit tests for get_entity_generator web service
*
* @package tool_behat
* @copyright 2022 onwards Catalyst IT EU {@link https://catalyst-eu.net}
* @author Mark Johnson <mark.johnson@catalyst-eu.net>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace tool_behat\external;
/**
* Tests for get_entity_generator web service
*
* @covers \tool_behat\external\get_entity_generator
*/
class get_entity_generator_test extends \advanced_testcase {
/**
* Log in as admin
*
* @return void
*/
public function setUp(): void {
$this->resetAfterTest();
$this->setAdminUser();
}
/**
* Get the generator for a core entity.
*
* @return void
*/
public function test_execute_core_entity() : void {
$generator = get_entity_generator::execute('users');
$this->assertEquals(['required' => ['username']], $generator);
}
/**
* Get the generator for the plugin entity.
*
* @return void
*/
public function test_execute_plugin_entity() : void {
$generator = get_entity_generator::execute('mod_book > chapters');
$this->assertEquals(['required' => ['book', 'title', 'content']], $generator);
}
/**
* Get the generator for an entity with no required fields.
*
* @return void
*/
public function test_execute_no_requried() : void {
$generator = get_entity_generator::execute('mod_forum > posts');
$this->assertEquals(['required' => []], $generator);
}
/**
* Attempt to get the generator for a core entity that does not exist.
*
* @return void
*/
public function test_execute_invalid_entity() : void {
$this->expectException('coding_exception');
get_entity_generator::execute('foo');
}
/**
* Attempt to get a generator form a plugin that does not exist.
*
* @return void
*/
public function test_execute_invalid_plugin() : void {
$this->expectException('coding_exception');
get_entity_generator::execute('foo > bar');
}
/**
* Attempt to get a generator for an entity that does not exist, from a plugin that does.
*
* @return void
*/
public function test_execute_invalid_plugin_entity() : void {
$this->expectException('coding_exception');
get_entity_generator::execute('mod_book > bar');
}
}

View File

@ -24,6 +24,6 @@
defined('MOODLE_INTERNAL') || die();
$plugin->version = 2022112800; // The current plugin version (Date: YYYYMMDDXX).
$plugin->version = 2022120900; // The current plugin version (Date: YYYYMMDDXX).
$plugin->requires = 2022111800; // Requires this Moodle version.
$plugin->component = 'tool_behat'; // Full name of the plugin (used for diagnostics)

View File

@ -165,6 +165,15 @@ abstract class behat_generator_base {
*/
protected abstract function get_creatable_entities(): array;
/**
* Get the list of available generators for this class.
*
* @return array
*/
final public function get_available_generators(): array {
return $this->get_creatable_entities();
}
/**
* Do the work to generate an entity.
*

View File

@ -198,4 +198,51 @@ class behat_data_generators extends behat_base {
$instance = new $componentclass($component);
return $instance;
}
/**
* Get all entities that can be created in all components using the_following_entities_exist()
*
* @return array
* @throws coding_exception
*/
public function get_all_entities(): array {
global $CFG;
// Ensure the generator class is loaded.
require_once($CFG->libdir . '/behat/classes/behat_generator_base.php');
$componenttypes = core_component::get_component_list();
$coregenerator = $this->get_instance_for_component('core');
$pluginswithentities = ['core' => array_keys($coregenerator->get_available_generators())];
foreach ($componenttypes as $components) {
foreach ($components as $component => $componentdir) {
try {
$plugingenerator = $this->get_instance_for_component($component);
$entities = array_keys($plugingenerator->get_available_generators());
if (!empty($entities)) {
$pluginswithentities[$component] = $entities;
}
} catch (Exception $e) {
// The component has no generator, skip it.
continue;
}
}
}
return $pluginswithentities;
}
/**
* Get the required fields for a specific creatable entity.
*
* @param string $entitytype
* @return mixed
* @throws coding_exception
*/
public function get_entity(string $entitytype): array {
[$component, $entity] = $this->parse_entity_type($entitytype);
$generator = $this->get_instance_for_component($component);
$entities = $generator->get_available_generators();
if (!array_key_exists($entity, $entities)) {
throw new coding_exception('No generator for ' . $entity . ' in component ' . $component);
}
return $entities[$entity];
}
}