Merge branch 'MDL-75982-master-latest' of https://github.com/andrewnicols/moodle

This commit is contained in:
Shamim Rezaie 2022-11-24 13:36:11 +11:00
commit 07ab2c0cfb
18 changed files with 828 additions and 18 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -456,8 +456,15 @@ export default class {
* @private
*/
getComputedBackgroundColor(node, color) {
if (!node.parentNode) {
// This is the document node and has no colour.
// We cannot use window.getComputedStyle on the document.
// If we got here, then the document has no background colour. Fall back to white.
return this.colorBase.toArray('rgba(255, 255, 255, 1)');
}
color = color ? color : window.getComputedStyle(node, null).getPropertyValue('background-color');
if (color.toLowerCase() === 'transparent') {
if (color.toLowerCase() === 'rgba(0, 0, 0, 0)' || color.toLowerCase() === 'transparent') {
color = 'rgba(1, 1, 1, 0)';
}

View File

@ -0,0 +1,45 @@
@editor @editor_tiny
Feature: Tiny editor accessibility checker
To write accessible content in Tiny, I need to check for accessibility warnings.
@javascript
Scenario Outline: Perform basic accessibility validations
Given I log in as "admin"
And I open my profile in edit mode
And I set the field "Description" to "<content>"
When I click on the "Tools > Accessibility checker" menu item for the "Description" TinyMCE editor
Then I should see "<result>" in the "Accessibility checker" "dialogue"
Examples:
| result | content |
| The colours of the foreground and background text do not have enough contrast. | <p style='color: #7c7cff; background-color: #ffffff;'>Hard to read</p> |
| There is a lot of text with no headings. | <p>Sweet roll oat cake jelly-o macaroon donut oat cake. Caramels macaroon cookie sweet roll croissant cheesecake candy jelly-o. Gummies sugar plum sugar plum gingerbread dessert. Tiramisu bonbon jujubes danish marshmallow cookie chocolate cake cupcake tiramisu. Bear claw oat cake chocolate bar croissant. Lollipop cookie topping liquorice croissant. Brownie cookie cupcake lollipop cupcake cupcake. Fruitcake dessert sweet biscuit dragée caramels marzipan brownie. Chupa chups gingerbread apple pie cookie liquorice caramels carrot cake cookie gingerbread. Croissant candy jelly beans. Tiramisu apple pie dessert apple pie macaroon soufflé. Brownie powder carrot cake chocolate. Tart applicake croissant dragée macaroon chocolate donut.</p><p>Jelly beans gingerbread tootsie roll. Sugar plum tiramisu cotton candy toffee pie cotton candy tiramisu. Carrot cake chocolate bar sesame snaps cupcake cake dessert sweet fruitcake wafer. Marshmallow cupcake gingerbread pie sweet candy canes powder gummi bears. Jujubes cake muffin marshmallow candy jelly beans tootsie roll pie. Gummi bears applicake chocolate cake sweet jelly sesame snaps lollipop lollipop carrot cake. Marshmallow cake jelly beans. Jelly beans sesame snaps muffin halvah cookie ice cream candy canes carrot cake. Halvah donut marshmallow tiramisu. Cookie dessert gummi bears. Sugar plum apple pie jelly beans gummi bears tart chupa chups. Liquorice macaroon gummi bears gummies macaroon marshmallow sweet roll cake topping. Lemon drops caramels pie icing danish. Chocolate cake oat cake dessert halvah danish carrot cake apple pie.</p> |
| Tables should not contain merged cells. | <table><caption>Dogs that look good in pants</caption><tr><th>Breed</th><th>Coolness</th></tr><tr><td>Poodle</td><td rowspan='2'>NOT COOL</td></tr><tr><td>Doberman</td></tr></table> |
| Tables should use row and/or column headers. | <table><caption>Dogs that look good in pants</caption><tr><th>Breed</th><td>Coolness</td></tr><tr><td>Poodle</td><td>NOT COOL</td></tr><tr><td>Doberman</td><td>COOL</td></tr></table> |
| Tables should have captions. | <table><tr><th>Breed</th><th>Coolness</th></tr><tr><td>Poodle</td><td>NOT COOL</td></tr><tr><td>Doberman</td><td>COOL</td></tr></table> |
@javascript
Scenario: Perform accessibility validation on images with no alt attribute
Given I log in as "admin"
And I open my profile in edit mode
And I set the field "Description" to "<p>Some plain text</p><img src='/broken-image' width='1' height='1'/><p>Some more text</p>"
And I click on the "Tools > Accessibility checker" menu item for the "Description" TinyMCE editor
And I should see "Images require alternative text." in the "Accessibility checker" "dialogue"
And I click on "View" "link" in the "Accessibility checker" "dialogue"
And I click on the "Image" button for the "Description" TinyMCE editor
And the field "Enter URL" matches value "/broken-image"
And I set the field "Describe this image for someone who cannot see it" to "No more warning!"
And I press "Save image"
And I click on the "Tools > Accessibility checker" menu item for the "Description" TinyMCE editor
And I should see "Congratulations, no accessibility issues found!" in the "Accessibility checker" "dialogue"
And I click on "Close" "button" in the "Accessibility checker" "dialogue"
And I select the "img" element in position "2" of the "Description" TinyMCE editor
And I click on the "Image" button for the "Description" TinyMCE editor
And I set the field "Enter URL" to "/decorative-image.png"
And I set the field "Describe this image for someone who cannot see it" to ""
And I set the field "Width" to "1"
And I set the field "Height" to "1"
And I click on "This image is decorative only" "checkbox"
When I press "Save image"
And I click on the "Tools > Accessibility checker" menu item for the "Description" TinyMCE editor
Then I should see "Congratulations, no accessibility issues found!" in the "Accessibility checker" "dialogue"

View File

@ -0,0 +1,71 @@
@editor @editor_tiny @_file_upload
Feature: Tiny editor autosave
In order to prevent data loss
As a content creator
I need my content to be saved automatically
Background:
Given the following "courses" exist:
| fullname | shortname | category | groupmode | description | summaryformat |
| Course 1 | C1 | 0 | 1 | | 1 |
And the following "users" exist:
| username | firstname | lastname | email |
| teacher1 | Teacher | 1 | teacher1@example.com |
| teacher2 | Teacher | 2 | teacher2@example.com |
And the following "course enrolments" exist:
| user | course | role |
| teacher1 | C1 | editingteacher |
| teacher2 | C1 | editingteacher |
And the following "blocks" exist:
| blockname | contextlevel | reference | pagetypepattern | defaultregion |
| private_files | System | 1 | my-index | side-post |
@javascript
Scenario: Restore a draft on user profile page
Given I log in as "teacher1"
And I open my profile in edit mode
And I set the field "Description" to "This is my draft"
And I log out
When I log in as "teacher1"
And I open my profile in edit mode
Then the field "Description" matches value "This is my draft"
@javascript
Scenario: Do not restore a draft if files have been modified
Given I am on the "Course 1" course page logged in as teacher1
And I navigate to "Settings" in current page administration
And I set the field "Course summary" to "This is my draft"
And I log out
And I log in as "teacher2"
And I follow "Manage private files..."
And I upload "/lib/editor/tiny/tests/behat/fixtures/tinyscreenshot.png" file to "Files" filemanager
And I click on "Save changes" "button"
And I am on "Course 1" course homepage
And I navigate to "Settings" in current page administration
And I set the field "Course summary" to "<p>Image test</p>"
And I select the "p" element in position "1" of the "Course summary" TinyMCE editor
And I click on the "Image" button for the "Course summary" TinyMCE editor
And I click on "Browse repositories..." "button"
And I click on "Private files" "link" in the ".fp-repo-area" "css_element"
And I click on "tinyscreenshot.png" "link"
And I click on "Select this file" "button"
And I set the field "Describe this image" to "It's the Moodle"
And I click on "Save image" "button"
And I click on "Save and display" "button"
When I am on the "Course 1" course page logged in as teacher1
And I navigate to "Settings" in current page administration
Then I should not see "This is my draft"
@javascript
Scenario: Do not restore a draft if text has been modified
Given I am on the "Course 1" course page logged in as teacher1
And I navigate to "Settings" in current page administration
And I set the field "Course summary" to "This is my draft"
And I am on the "Course 1" course page logged in as teacher2
And I navigate to "Settings" in current page administration
And I set the field "Course summary" to "Modified text"
And I click on "Save and display" "button"
When I am on the "Course 1" course page logged in as teacher1
And I navigate to "Settings" in current page administration
Then I should not see "This is my draft" in the "#id_summary_editor" "css_element"
And the field "Course summary" matches value "<p>Modified text</p>"

View File

@ -0,0 +1,36 @@
@editor @editor_tiny @tiny_equation
Feature: Tiny equation editor
To teach maths to students, I need to write equations
@javascript
Scenario: Create an equation using TinyMCE
Given I log in as "admin"
When I open my profile in edit mode
And I set the field "Description" to "<p>Equation test</p>"
# Set field on the bottom of page, so equation editor dialogue is visible.
And I expand all fieldsets
And I set the field "Picture description" to "Test"
And I expand all toolbars for the "Description" TinyMCE editor
And I click on the "Equation editor" button for the "Description" TinyMCE editor
And the "class" attribute of "Edit equation using" "field" should contain "text-ltr"
And I set the field "Edit equation using" to " = 1 \div 0"
And I click on "\infty" "button"
And I click on "Save equation" "button"
And I click on "Update profile" "button"
And I follow "Profile" in the user menu
Then "\infty" "text" should exist
@javascript
Scenario: Edit an equation using TinyMCE
Given I log in as "admin"
When I open my profile in edit mode
And I set the field "Description" to "<p>\( \pi \)</p>"
# Set field on the bottom of page, so equation editor dialogue is visible.
And I expand all fieldsets
And I set the field "Picture description" to "Test"
And I expand all toolbars for the "Description" TinyMCE editor
And I click on the "Equation editor" button for the "Description" TinyMCE editor
And the "class" attribute of "Edit equation using" "field" should contain "text-ltr"
Then the field "Edit equation using" matches value " \pi "
And I click on "Save equation" "button"
And the field "Description" matches value "<p>\( \pi \)</p>"

View File

@ -1,3 +1,3 @@
define("tiny_h5p/commands",["exports","editor_tiny/utils","./ui","core/str","./common"],(function(_exports,_utils,_ui,_str,_common){Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.getSetup=void 0;_exports.getSetup=async()=>{const[buttonText,buttonImage]=await Promise.all([(0,_str.get_string)("buttontitle",_common.component),(0,_utils.getButtonImage)("icon",_common.component)]);return editor=>{editor.ui.registry.addIcon(_common.icon,buttonImage.html),editor.ui.registry.addToggleButton(_common.buttonName,{icon:_common.icon,tooltip:buttonText,onAction:()=>(0,_ui.handleAction)(editor),onSetup:api=>{api.setActive(editor.formatter.match("h5p"));const changed=editor.formatter.formatChanged("h5p",(state=>api.setActive(state)));return()=>changed.unbind()}}),editor.ui.registry.addMenuItem(_common.buttonName,{icon:_common.icon,text:buttonText,onAction:()=>(0,_ui.handleAction)(editor)})}}}));
define("tiny_h5p/commands",["exports","editor_tiny/utils","./ui","core/str","./common","./options"],(function(_exports,_utils,_ui,_str,_common,_options){Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.getSetup=void 0;_exports.getSetup=async()=>{const[buttonText,buttonImage]=await Promise.all([(0,_str.get_string)("buttontitle",_common.component),(0,_utils.getButtonImage)("icon",_common.component)]);return editor=>{(0,_options.hasAnyH5PPermission)(editor)&&(editor.ui.registry.addIcon(_common.icon,buttonImage.html),editor.ui.registry.addToggleButton(_common.buttonName,{icon:_common.icon,tooltip:buttonText,onAction:()=>(0,_ui.handleAction)(editor),onSetup:api=>{api.setActive(editor.formatter.match("h5p"));const changed=editor.formatter.formatChanged("h5p",(state=>api.setActive(state)));return()=>changed.unbind()}}),editor.ui.registry.addMenuItem(_common.buttonName,{icon:_common.icon,text:buttonText,onAction:()=>(0,_ui.handleAction)(editor)}))}}}));
//# sourceMappingURL=commands.min.js.map

View File

@ -1 +1 @@
{"version":3,"file":"commands.min.js","sources":["../src/commands.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\n/**\n * Tiny H5P Content configuration.\n *\n * @module tiny_h5p/commands\n * @copyright 2022 Andrew Lyons <andrew@nicols.co.uk>\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport {getButtonImage} from 'editor_tiny/utils';\nimport {handleAction} from './ui';\nimport {get_string as getString} from 'core/str';\nimport {\n component,\n buttonName,\n icon,\n} from './common';\n\nexport const getSetup = async() => {\n const [\n buttonText,\n buttonImage,\n ] = await Promise.all([\n getString('buttontitle', component),\n getButtonImage('icon', component),\n ]);\n\n return (editor) => {\n // Register the H5P Icon.\n editor.ui.registry.addIcon(icon, buttonImage.html);\n\n // Register the Menu Button as a toggle.\n // This means that when highlighted over an existing H5P element it will show as toggled on.\n editor.ui.registry.addToggleButton(buttonName, {\n icon,\n tooltip: buttonText,\n onAction: () => handleAction(editor),\n onSetup: (api) => {\n // Set the button to be active if the current selection matches the h5p formatter registered above during PreInit.\n api.setActive(editor.formatter.match('h5p'));\n const changed = editor.formatter.formatChanged('h5p', (state) => api.setActive(state));\n return () => changed.unbind();\n },\n });\n\n // Add the H5P Menu Item.\n // This allows it to be added to a standard menu, or a context menu.\n editor.ui.registry.addMenuItem(buttonName, {\n icon,\n text: buttonText,\n onAction: () => handleAction(editor),\n });\n };\n};\n"],"names":["async","buttonText","buttonImage","Promise","all","component","editor","ui","registry","addIcon","icon","html","addToggleButton","buttonName","tooltip","onAction","onSetup","api","setActive","formatter","match","changed","formatChanged","state","unbind","addMenuItem","text"],"mappings":"uOAgCwBA,gBAEhBC,WACAC,mBACMC,QAAQC,IAAI,EAClB,mBAAU,cAAeC,oBACzB,yBAAe,OAAQA,4BAGnBC,SAEJA,OAAOC,GAAGC,SAASC,QAAQC,aAAMR,YAAYS,MAI7CL,OAAOC,GAAGC,SAASI,gBAAgBC,mBAAY,CAC3CH,KAAAA,aACAI,QAASb,WACTc,SAAU,KAAM,oBAAaT,QAC7BU,QAAUC,MAENA,IAAIC,UAAUZ,OAAOa,UAAUC,MAAM,cAC/BC,QAAUf,OAAOa,UAAUG,cAAc,OAAQC,OAAUN,IAAIC,UAAUK,eACxE,IAAMF,QAAQG,YAM7BlB,OAAOC,GAAGC,SAASiB,YAAYZ,mBAAY,CACvCH,KAAAA,aACAgB,KAAMzB,WACNc,SAAU,KAAM,oBAAaT"}
{"version":3,"file":"commands.min.js","sources":["../src/commands.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\n/**\n * Tiny H5P Content configuration.\n *\n * @module tiny_h5p/commands\n * @copyright 2022 Andrew Lyons <andrew@nicols.co.uk>\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport {getButtonImage} from 'editor_tiny/utils';\nimport {handleAction} from './ui';\nimport {get_string as getString} from 'core/str';\nimport {\n component,\n buttonName,\n icon,\n} from './common';\nimport {hasAnyH5PPermission} from './options';\n\nexport const getSetup = async() => {\n const [\n buttonText,\n buttonImage,\n ] = await Promise.all([\n getString('buttontitle', component),\n getButtonImage('icon', component),\n ]);\n\n return (editor) => {\n if (!hasAnyH5PPermission(editor)) {\n return;\n }\n // Register the H5P Icon.\n editor.ui.registry.addIcon(icon, buttonImage.html);\n\n // Register the Menu Button as a toggle.\n // This means that when highlighted over an existing H5P element it will show as toggled on.\n editor.ui.registry.addToggleButton(buttonName, {\n icon,\n tooltip: buttonText,\n onAction: () => handleAction(editor),\n onSetup: (api) => {\n // Set the button to be active if the current selection matches the h5p formatter registered above during PreInit.\n api.setActive(editor.formatter.match('h5p'));\n const changed = editor.formatter.formatChanged('h5p', (state) => api.setActive(state));\n return () => changed.unbind();\n },\n });\n\n // Add the H5P Menu Item.\n // This allows it to be added to a standard menu, or a context menu.\n editor.ui.registry.addMenuItem(buttonName, {\n icon,\n text: buttonText,\n onAction: () => handleAction(editor),\n });\n };\n};\n"],"names":["async","buttonText","buttonImage","Promise","all","component","editor","ui","registry","addIcon","icon","html","addToggleButton","buttonName","tooltip","onAction","onSetup","api","setActive","formatter","match","changed","formatChanged","state","unbind","addMenuItem","text"],"mappings":"4PAiCwBA,gBAEhBC,WACAC,mBACMC,QAAQC,IAAI,EAClB,mBAAU,cAAeC,oBACzB,yBAAe,OAAQA,4BAGnBC,UACC,gCAAoBA,UAIzBA,OAAOC,GAAGC,SAASC,QAAQC,aAAMR,YAAYS,MAI7CL,OAAOC,GAAGC,SAASI,gBAAgBC,mBAAY,CAC3CH,KAAAA,aACAI,QAASb,WACTc,SAAU,KAAM,oBAAaT,QAC7BU,QAAUC,MAENA,IAAIC,UAAUZ,OAAOa,UAAUC,MAAM,cAC/BC,QAAUf,OAAOa,UAAUG,cAAc,OAAQC,OAAUN,IAAIC,UAAUK,eACxE,IAAMF,QAAQG,YAM7BlB,OAAOC,GAAGC,SAASiB,YAAYZ,mBAAY,CACvCH,KAAAA,aACAgB,KAAMzB,WACNc,SAAU,KAAM,oBAAaT"}

View File

@ -1,4 +1,4 @@
define("tiny_h5p/options",["exports","editor_tiny/options","./common"],(function(_exports,_options,_common){Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.register=_exports.getPermissions=void 0;
define("tiny_h5p/options",["exports","editor_tiny/options","./common"],(function(_exports,_options,_common){Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.register=_exports.hasAnyH5PPermission=_exports.getPermissions=void 0;
/**
* Options helper for Tiny H5P plugin.
*
@ -6,6 +6,6 @@ define("tiny_h5p/options",["exports","editor_tiny/options","./common"],(function
* @copyright 2022 Andrew Lyons <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
const permissionsName=(0,_options.getPluginOptionName)(_common.pluginName,"permissions");_exports.register=editor=>{(0,editor.options.register)(permissionsName,{processor:"object",default:{upload:!1,embed:!1}})};_exports.getPermissions=editor=>editor.options.get(permissionsName)}));
const permissionsName=(0,_options.getPluginOptionName)(_common.pluginName,"permissions");_exports.register=editor=>{(0,editor.options.register)(permissionsName,{processor:"object",default:{upload:!1,embed:!1}})};const getPermissions=editor=>editor.options.get(permissionsName);_exports.getPermissions=getPermissions;_exports.hasAnyH5PPermission=editor=>{const permissions=getPermissions(editor);return permissions.upload||permissions.embed}}));
//# sourceMappingURL=options.min.js.map

View File

@ -1 +1 @@
{"version":3,"file":"options.min.js","sources":["../src/options.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\n/**\n * Options helper for Tiny H5P plugin.\n *\n * @module tiny_h5p/options\n * @copyright 2022 Andrew Lyons <andrew@nicols.co.uk>\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport {getPluginOptionName} from 'editor_tiny/options';\nimport {pluginName} from './common';\n\nconst permissionsName = getPluginOptionName(pluginName, 'permissions');\n\n/**\n * Register the options for the Tiny H5P plugin.\n *\n * @param {TinyMCE} editor\n */\nexport const register = (editor) => {\n const registerOption = editor.options.register;\n\n registerOption(permissionsName, {\n processor: 'object',\n \"default\": {\n upload: false,\n embed: false,\n },\n });\n};\n\n/**\n * Get the permissions configuration for the Tiny H5P plugin.\n *\n * @param {TinyMCE} editor\n * @returns {object}\n */\nexport const getPermissions = (editor) => editor.options.get(permissionsName);\n"],"names":["permissionsName","pluginName","editor","registerOption","options","register","processor","upload","embed","get"],"mappings":";;;;;;;;MA0BMA,iBAAkB,gCAAoBC,mBAAY,iCAO/BC,UAGrBC,EAFuBD,OAAOE,QAAQC,UAEvBL,gBAAiB,CAC5BM,UAAW,iBACA,CACPC,QAAQ,EACRC,OAAO,8BAWYN,QAAWA,OAAOE,QAAQK,IAAIT"}
{"version":3,"file":"options.min.js","sources":["../src/options.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\n/**\n * Options helper for Tiny H5P plugin.\n *\n * @module tiny_h5p/options\n * @copyright 2022 Andrew Lyons <andrew@nicols.co.uk>\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport {getPluginOptionName} from 'editor_tiny/options';\nimport {pluginName} from './common';\n\nconst permissionsName = getPluginOptionName(pluginName, 'permissions');\n\n/**\n * Register the options for the Tiny H5P plugin.\n *\n * @param {TinyMCE} editor\n */\nexport const register = (editor) => {\n const registerOption = editor.options.register;\n\n registerOption(permissionsName, {\n processor: 'object',\n \"default\": {\n upload: false,\n embed: false,\n },\n });\n};\n\n/**\n * Get the permissions configuration for the Tiny H5P plugin.\n *\n * @param {TinyMCE} editor\n * @returns {object}\n */\nexport const getPermissions = (editor) => editor.options.get(permissionsName);\n\n/**\n * Check whether any H5P Permission is available.\n *\n * @param {TinyMCE} editor\n * @returns {boolean}\n */\nexport const hasAnyH5PPermission = (editor) => {\n const permissions = getPermissions(editor);\n return permissions.upload || permissions.embed;\n};\n"],"names":["permissionsName","pluginName","editor","registerOption","options","register","processor","upload","embed","getPermissions","get","permissions"],"mappings":";;;;;;;;MA0BMA,iBAAkB,gCAAoBC,mBAAY,iCAO/BC,UAGrBC,EAFuBD,OAAOE,QAAQC,UAEvBL,gBAAiB,CAC5BM,UAAW,iBACA,CACPC,QAAQ,EACRC,OAAO,YAWNC,eAAkBP,QAAWA,OAAOE,QAAQM,IAAIV,qFAQzBE,eAC1BS,YAAcF,eAAeP,eAC5BS,YAAYJ,QAAUI,YAAYH"}

View File

@ -29,6 +29,7 @@ import {
buttonName,
icon,
} from './common';
import {hasAnyH5PPermission} from './options';
export const getSetup = async() => {
const [
@ -40,6 +41,9 @@ export const getSetup = async() => {
]);
return (editor) => {
if (!hasAnyH5PPermission(editor)) {
return;
}
// Register the H5P Icon.
editor.ui.registry.addIcon(icon, buttonImage.html);

View File

@ -50,3 +50,14 @@ export const register = (editor) => {
* @returns {object}
*/
export const getPermissions = (editor) => editor.options.get(permissionsName);
/**
* Check whether any H5P Permission is available.
*
* @param {TinyMCE} editor
* @returns {boolean}
*/
export const hasAnyH5PPermission = (editor) => {
const permissions = getPermissions(editor);
return permissions.upload || permissions.embed;
};

View File

@ -0,0 +1,210 @@
@editor @editor_tiny @tiny_media @javascript @_file_upload
Feature: Use the TinyMCE editor to upload an h5p package
In order to work with h5p
As a content creator
I need to be able to embed H5P packages
Background:
Given the following "courses" exist:
| shortname | fullname |
| C1 | Course 1 |
And the following "users" exist:
| username | firstname | lastname | email |
| teacher1 | Teacher | 1 | teacher1@example.com |
And the following "course enrolments" exist:
| user | course | role |
| teacher1 | C1 | editingteacher |
And the following "activities" exist:
| activity | name | intro | introformat | course | content | contentformat | idnumber |
| page | PageName1 | PageDesc1 | 1 | C1 | H5Ptest | 1 | 1 |
And the "displayh5p" filter is "on"
And the following config values are set as admin:
| allowedsources | https://moodle.h5p.com/content/[id] | filter_displayh5p |
And the following "blocks" exist:
| blockname | contextlevel | reference | pagetypepattern | defaultregion |
| private_files | System | 1 | my-index | side-post |
@javascript @external
Scenario: TinyMCE can be used to embed an H5P activity
Given I change window size to "large"
And I am on the PageName1 "page activity editing" page logged in as admin
And I click on the "Configure H5P content" button for the "Page content" TinyMCE editor
And I set the field "H5P URL or file upload" to "https://moodle.h5p.com/content/1290772960722742119"
And I click on "Insert H5P content" "button" in the "Insert H5P content" "dialogue"
When I click on "Save and display" "button"
Then ".h5p-placeholder" "css_element" should exist
And I switch to "h5pcontent" iframe
And I should see "Lorum ipsum"
@javascript
Scenario: TinyMCE can be used to upload and embed an H5P activity
Given I log in as "admin"
And I change window size to "large"
And I follow "Manage private files..."
And I upload "h5p/tests/fixtures/guess-the-answer.h5p" file to "Files" filemanager
And I click on "Save changes" "button"
And I am on the "PageName1" "page activity editing" page
And I click on the "Configure H5P content" button for the "Page content" TinyMCE editor
And I click on "Browse repositories..." "button" in the "Insert H5P content" "dialogue"
And I click on "Private files" "link" in the ".fp-repo-area" "css_element"
And I click on "guess-the-answer.h5p" "link"
And I click on "Select this file" "button"
And I click on "Insert H5P content" "button" in the "Insert H5P content" "dialogue"
When I click on "Save and display" "button"
Then ".h5p-placeholder" "css_element" should exist
@javascript
Scenario: When a user does not have any H5P capabilities, they cannot embed H5P content with TinyMCE
Given the following "permission overrides" exist:
| capability | permission | role | contextlevel | reference |
| tiny/h5p:addembed | Prohibit | editingteacher | Course | C1 |
| moodle/h5p:deploy | Prohibit | editingteacher | Course | C1 |
When I am on the PageName1 "page activity editing" page logged in as teacher1
Then "Configure H5P content" "button" should not exist
@javascript
Scenario: When a user does not have the Embed H5P capability, they cannot embed H5P content with TinyMCE
Given the following "permission overrides" exist:
| capability | permission | role | contextlevel | reference |
| tiny/h5p:addembed | Prohibit | editingteacher | Course | C1 |
And I am on the PageName1 "page activity editing" page logged in as teacher1
And I click on "Configure H5P" "button"
Then I should not see "H5P URL" in the "Insert H5P content" "dialogue"
And I should see "H5P file upload" in the "Insert H5P content" "dialogue"
And I should see "H5P options" in the "Insert H5P content" "dialogue"
@javascript
Scenario: When a user does not have the Upload H5P capability, they can embed but not upload H5P content with TinyMCE
Given the following "permission overrides" exist:
| capability | permission | role | contextlevel | reference |
| moodle/h5p:deploy | Prohibit | editingteacher | Course | C1 |
When I am on the PageName1 "page activity editing" page logged in as teacher1
And I click on the "Configure H5P content" button for the "Page content" TinyMCE editor
Then I should not see "H5P file upload" in the "Insert H5P content" "dialogue"
And I should see "H5P URL" in the "Insert H5P content" "dialogue"
And I should not see "H5P options" in the "Insert H5P content" "dialogue"
@javascript @external
Scenario: A user can edit H5P content embedding with TinyMCE
Given I log in as "admin"
And I follow "Manage private files..."
And I upload "lib/editor/atto/tests/fixtures/drag.h5p" file to "Files" filemanager
And I click on "Save changes" "button"
And I am on the PageName1 "page activity editing" page
And I click on the "Configure H5P content" button for the "Page content" TinyMCE editor
And I click on "Browse repositories..." "button" in the "Insert H5P content" "dialogue"
And I click on "Private files" "link" in the ".fp-repo-area" "css_element"
And I click on "drag" "link"
And I click on "Select this file" "button"
And I click on "Insert H5P content" "button" in the "Insert H5P content" "dialogue"
When I click on "Save and display" "button"
And I switch to "h5pcontent" iframe
And I switch to "h5p-iframe" class iframe
Then I should not see "reveal"
And I should see "Cloudberries"
And I switch to the main frame
And I navigate to "Settings" in current page administration
And I select the ".h5p-placeholder" "css_element" in the "Page content" TinyMCE editor
And I click on the "Configure H5P content" button for the "Page content" TinyMCE editor
And I set the field "H5P URL or file upload" to "https://moodle.h5p.com/content/1290772960722742119"
And I click on "Insert H5P" "button" in the "Insert H5P content" "dialogue"
And I wait "1" seconds
And I click on "Save and display" "button"
And I switch to "h5pcontent" iframe
And I should see "Lorum ipsum"
And I should not see "Cloudberries"
@javascript
Scenario: Enable/disable H5P options
Given I log in as "admin"
And I follow "Manage private files..."
And I upload "h5p/tests/fixtures/guess-the-answer.h5p" file to "Files" filemanager
And I click on "Save changes" "button"
And I am on the PageName1 "page activity editing" page
And I click on the "Configure H5P content" button for the "Page content" TinyMCE editor
And I click on "Browse repositories..." "button" in the "Insert H5P content" "dialogue"
And I click on "Private files" "link" in the ".fp-repo-area" "css_element"
And I click on "guess-the-answer.h5p" "link"
And I click on "Select this file" "button"
And I click on "Insert H5P" "button" in the "Insert H5P content" "dialogue"
When I click on "Save and display" "button"
And I switch to "h5pcontent" iframe
And I switch to "h5p-iframe" class iframe
Then I should not see "Reuse"
And I should not see "Embed"
And I should not see "Rights of use"
And I switch to the main frame
And I navigate to "Settings" in current page administration
And I select the ".h5p-placeholder" "css_element" in the "Page content" TinyMCE editor
And I click on the "Configure H5P content" button for the "Page content" TinyMCE editor
And I click on "H5P options" "link"
And I click on "Allow download" "checkbox"
And I click on "Insert H5P" "button" in the "Insert H5P content" "dialogue"
And I wait "1" seconds
And I click on "Save and display" "button"
And I switch to "h5pcontent" iframe
And I switch to "h5p-iframe" class iframe
And I should see "Reuse"
And I should not see "Embed"
And I should not see "Rights of use"
And I switch to the main frame
And I navigate to "Settings" in current page administration
And I select the ".h5p-placeholder" "css_element" in the "Page content" TinyMCE editor
And I click on the "Configure H5P content" button for the "Page content" TinyMCE editor
And I click on "Allow download" "checkbox"
And I click on "Embed button" "checkbox"
And I click on "Copyright button" "checkbox"
And I click on "Insert H5P" "button" in the "Insert H5P content" "dialogue"
And I wait "1" seconds
And I click on "Save and display" "button"
And I switch to "h5pcontent" iframe
And I switch to "h5p-iframe" class iframe
And I should not see "Reuse"
And I should see "Embed"
And I should see "Rights of use"
@javascript @external
Scenario: H5P options are ignored for H5P URLs
Given I change window size to "large"
And I am on the PageName1 "page activity editing" page logged in as admin
And I click on the "Configure H5P content" button for the "Page content" TinyMCE editor
And I set the field "H5P URL or file upload" to "https://moodle.h5p.com/content/1291366510035871129"
And I click on "H5P options" "link"
And I click on "Embed button" "checkbox"
And I click on "Insert H5P content" "button" in the "Insert H5P content" "dialogue"
When I click on "Save and display" "button"
Then ".h5p-placeholder" "css_element" should exist
And I switch to "h5pcontent" iframe
And I should see "Far far away"
And I should not see "Embed"
And I switch to the main frame
And I navigate to "Settings" in current page administration
And I select the ".h5p-placeholder" "css_element" in the "Page content" TinyMCE editor
And I click on the "Configure H5P content" button for the "Page content" TinyMCE editor
And I click on "H5P options" "link"
And "input[aria-label=\"Embed button\"]:not([checked=checked])" "css_element" should exist
@javascript
Scenario: Private H5P files are shown to students
Given the following "users" exist:
| username | firstname | lastname | email |
| student1 | Student | 1 | student1@example.com |
And the following "course enrolments" exist:
| user | course | role |
| student1 | C1 | student |
And I log in as "admin"
And I follow "Manage private files..."
And I upload "h5p/tests/fixtures/guess-the-answer.h5p" file to "Files" filemanager
And I click on "Save changes" "button"
And I am on the PageName1 "page activity editing" page
And I click on the "Configure H5P content" button for the "Page content" TinyMCE editor
And I click on "Browse repositories..." "button" in the "Insert H5P content" "dialogue"
And I click on "Private files" "link" in the ".fp-repo-area" "css_element"
And I click on "guess-the-answer.h5p" "link"
And I click on "Select this file" "button"
And I click on "Insert H5P content" "button" in the "Insert H5P content" "dialogue"
And I click on "Save and display" "button"
When I am on the PageName1 "page activity" page logged in as student1
Then I switch to "h5pcontent" iframe
And I switch to "h5p-iframe" class iframe
And I should see "reveal"

View File

@ -0,0 +1,28 @@
@editor @editor_tiny @tiny_media @javascript
Feature: Use the TinyMCE editor to upload an image
In order to work with images
As a user
I need to be able to upload and manipulate images
Scenario: Clicking on the Image button in the TinyMCE editor opens the image dialog
Given I log in as "admin"
And I open my profile in edit mode
When I click on the "Image" button for the "Description" TinyMCE editor
Then "Image properties" "dialogue" should exist
Scenario: Browsing repositories in the TinyMCE editor shows the FilePicker
Given I log in as "admin"
And I open my profile in edit mode
When I click on the "Image" button for the "Description" TinyMCE editor
And I click on "Browse repositories" "button" in the "Image properties" "dialogue"
Then "File picker" "dialogue" should exist
@_file_upload @test_tiny
Scenario: Browsing repositories in the TinyMCE editor shows the FilePicker
Given I log in as "admin"
And I open my profile in edit mode
When I click on the "Image" button for the "Description" TinyMCE editor
And I click on "Browse repositories" "button" in the "Image properties" "dialogue"
And I upload "/lib/editor/tiny/tests/behat/fixtures/tinyscreenshot.png" to the file picker for TinyMCE
# Note: This needs to be replaced with a label.
Then ".tiny_image_preview" "css_element" should be visible

View File

@ -0,0 +1,29 @@
@editor @editor_tiny @tiny_media @javascript
Feature: Use the TinyMCE editor to upload a video
In order to work with videos
As a user
I need to be able to upload and manipulate videos
Scenario: Clicking on the Video button in the TinyMCE editor opens the video dialog
Given I log in as "admin"
And I open my profile in edit mode
When I click on the "Multimedia" button for the "Description" TinyMCE editor
Then "Insert media" "dialogue" should exist
Scenario: Browsing repositories in the TinyMCE editor shows the FilePicker
Given I log in as "admin"
And I open my profile in edit mode
When I click on the "Multimedia" button for the "Description" TinyMCE editor
And I click on "Browse repositories" "button" in the "Insert media" "dialogue"
Then "File picker" "dialogue" should exist
@_file_upload
Scenario: Browsing repositories in the TinyMCE editor shows the FilePicker
Given I log in as "admin"
And I open my profile in edit mode
When I click on the "Multimedia" button for the "Description" TinyMCE editor
And I follow "Video"
And I click on "Browse repositories..." "button" in the "#id_description_editor_video .tiny_media_source.tiny_media_media_source" "css_element"
And I upload "/lib/editor/tiny/tests/behat/fixtures/moodle-logo.mp4" to the file picker for TinyMCE
When I click on "Insert media" "button"
And I select the "video" element in position "1" of the "Description" TinyMCE editor

View File

@ -24,6 +24,9 @@
*/
use Behat\Behat\Hook\Scope\BeforeScenarioScope;
use Behat\Mink\Element\NodeElement;
use Behat\Mink\Exception\DriverException;
use Behat\Mink\Exception\ExpectationException;
// NOTE: no MOODLE_INTERNAL test here, this file may be required by behat before including /config.php.
require_once(__DIR__ . '/../../../../behat/behat_base.php');
@ -36,6 +39,52 @@ require_once(__DIR__ . '/../../../../behat/behat_base.php');
* @copyright 2022 Andrew Lyons <andrew@nicols.co.uk>
*/
class behat_editor_tiny extends behat_base implements \core_behat\settable_editor {
/**
* Execute some JavaScript for a particular Editor instance.
*
* The editor instance is available on the 'instnace' variable.
*
* @param string $editorid The ID of the editor
* @param string $code The code to execute
*/
protected function execute_javascript_for_editor(string $editorid, string $code): void {
$js = <<<EOF
require(['editor_tiny/editor'], (editor) => {
const instance = editor.getInstanceForElementId('${editorid}');
{$code}
});
EOF;
$this->execute_script($js);
}
/**
* Resolve some JavaScript for a particular Editor instance.
*
* The editor instance is available on the 'instnace' variable.
* The code should return a value by passing it to the `resolve` function.
*
* @param string $editorid The ID of the editor
* @param string $code The code to evaluate
* @return string|null|array
*/
protected function evaluate_javascript_for_editor(string $editorid, string $code) {
$js = <<<EOF
return new Promise((resolve, reject) => {
require(['editor_tiny/editor'], (editor) => {
const instance = editor.getInstanceForElementId('${editorid}');
if (!instance) {
reject("Instance '${editorid}' not found");
}
{$code}
});
});
EOF;
return $this->evaluate_script($js);
}
/**
* Set the value for the editor.
*
@ -51,17 +100,19 @@ class behat_editor_tiny extends behat_base implements \core_behat\settable_edito
return;
}
$js = <<<EOF
require(['editor_tiny/editor'], (editor) => {
const instance = editor.getInstanceForElementId('${editorid}');
if (instance) {
instance.setContent('${value}');
instance.undoManager.add();
}
});
EOF;
$this->execute_javascript_for_editor($editorid, <<<EOF
instance.setContent('${value}');
instance.undoManager.add();
EOF);
}
$this->execute_script($js);
/**
* Store the current value of the editor, if it is a Tiny editor, to the textarea.
*
* @param string $editorid The ID of the editor.
*/
public function store_current_value(string $editorid): void {
$this->execute_javascript_for_editor($editorid, "instance?.save();");
}
/**
@ -92,4 +143,322 @@ class behat_editor_tiny extends behat_base implements \core_behat\settable_edito
$this->execute('behat_general::the_default_editor_is_set_to', ['tiny']);
}
/**
* Ensure that the editor_tiny tag is in use.
*
* This function should be used for any step defined in this file.
*
* @throws DriverException Thrown if the editor_tiny tag is not specified for this file
*/
protected function require_tiny_tags(): void {
// Ensure that this step only runs in TinyMCE tags.
if (!$this->has_tag('editor_tiny')) {
throw new DriverException(
'TinyMCE tests using this step must have the @editor_tiny tag on either the scenario or feature.'
);
}
}
/**
* Get the Mink NodeElement of the <textarea> for the specified locator.
*
* Moodle mostly referes to the textarea, rather than the editor itself and interactions are translated to the
* Editor using the TinyMCE API.
*
* @param string $locator A Moodle field locator
* @return NodeElement The element found by the find_field function
*/
protected function get_textarea_for_locator(string $locator): NodeElement {
return $this->find_field($locator);
}
/**
* Get the Mink NodeElement of the container for the specified locator.
*
* This is the top-most HTML element for the editor found by TinyMCE.getContainer().
*
* @param string $locator A Moodle field locator
* @return NodeElement The Mink NodeElement representing the container.
*/
protected function get_editor_container_for_locator(string $locator): NodeElement {
$textarea = $this->get_textarea_for_locator($locator);
$editorid = $textarea->getAttribute('id');
$targetid = uniqid();
$js = <<<EOF
const container = instance.getContainer();
if (!container.id) {
container.id = '${targetid}';
}
resolve(container.id);
EOF;
$containerid = $this->evaluate_javascript_for_editor($editorid, $js);
return $this->find('css', "#{$containerid}");
}
/**
* Get the name of the iframe relating to the editor.
*
* If no name is found, then add one.
*
* If the editor it not found, then throw an exception.
*
* @param string $locator The name of the editor
* @return string The name of the iframe
*/
protected function get_editor_iframe_name(string $locator): string {
return $this->get_editor_iframe_name_for_element($this->get_textarea_for_locator($locator));
}
/**
* Get the name of the iframe relating to the editor.
*
* If no name is found, then add one.
*
* If the editor it not found, then throw an exception.
* @param NodeElement $editor The editor element
* @return string The name of the iframe
*/
protected function get_editor_iframe_name_for_element(NodeElement $editor): string {
$editorid = $editor->getAttribute('id');
// Ensure that a name is set on the iframe relating to the editorid.
$js = <<<EOF
if (!instance.iframeElement.name) {
instance.iframeElement.name = '${editorid}';
}
resolve(instance.iframeElement.name);
EOF;
return $this->evaluate_javascript_for_editor($editorid, $js);
}
/**
* Click on a button for the specified TinyMCE editor.
*
* @When /^I click on the "(?P<button_string>(?:[^"]|\\")*)" button for the "(?P<locator_string>(?:[^"]|\\")*)" TinyMCE editor$/
*
* @param string $button The label of the button
* @param string $locator The locator for the editor
*/
public function i_click_on_button(string $button, string $locator): void {
$this->require_tiny_tags();
$container = $this->get_editor_container_for_locator($locator);
$this->execute('behat_general::i_click_on_in_the', [$button, 'button', $container, 'NodeElement']);
}
/**
* Confirm that the button state of the specified button/editor combination matches the expectation.
*
* @Then /^the "(?P<button_string>(?:[^"]|\\")*)" button of the "(?P<locator_string>(?:[^"]|\\")*)" TinyMCE editor has state "(?P<state_string>(?:[^"]|\\")*)"$/
*
* @param string $button The text name of the button
* @param string $locator The locator string for the editor
* @param string $state The state of the button
* @throws ExpectationException Thrown if the button state is not correct
*/
public function button_state_is(string $button, string $locator, string $state): void {
$this->require_tiny_tags();
$container = $this->get_editor_container_for_locator($locator);
$button = $this->find_button($button, false, $container);
$buttonstate = $button->getAttribute('aria-pressed');
if ($buttonstate !== $state) {
throw new ExpectationException("Button '{$button}' is in state '{$buttonstate}' not '{$state}'", $this->getSession());
}
}
/**
* Click on a button for the specified TinyMCE editor.
*
* @When /^I click on the "(?P<menuitem_string>(?:[^"]|\\")*)" menu item for the "(?P<locator_string>(?:[^"]|\\")*)" TinyMCE editor$/
*
* @param string $menuitem The label of the menu item
* @param string $locator The locator for the editor
*/
public function i_click_on_menuitem_in_menu(string $menuitem, string $locator): void {
$this->require_tiny_tags();
$container = $this->get_editor_container_for_locator($locator);
$menubar = $container->find('css', '[role="menubar"]');
$menus = array_map(function(string $value): string {
return trim($value);
}, explode('>', $menuitem));
// Open the menu bar.
$mainmenu = array_shift($menus);
$this->execute('behat_general::i_click_on_in_the', [$mainmenu, 'button', $menubar, 'NodeElement']);
foreach ($menus as $menuitem) {
// Find the menu that was opened.
$openmenu = $this->find('css', '.tox-selected-menu');
// Move the mouse to the first item in the list.
// This is required because WebDriver takes the shortest path to the next click location,
// which will mean crossing across other menu items.
$firstlink = $openmenu->find('css', "[role^='menuitem'] .tox-collection__item-icon");
$firstlink->mouseover();
// Now match by title where the role matches any menuitem, or menuitemcheckbox, or menuitem*.
$link = $openmenu->find('css', "[title='{$menuitem}'][role^='menuitem']");
$this->execute('behat_general::i_click_on', [$link, 'NodeElement']);
}
}
/**
* Select the element type/index for the specified TinyMCE editor.
*
* @When /^I select the "(?P<textlocator_string>(?:[^"]|\\")*)" element in position "(?P<position_int>(?:[^"]|\\")*)" of the "(?P<locator_string>(?:[^"]|\\")*)" TinyMCE editor$/
* @param string $textlocator The type of element to select (for example `p` or `span`)
* @param int $position The zero-indexed position
* @param string $locator The editor to select within
*/
public function select_text(string $textlocator, int $position, string $locator): void {
$this->require_tiny_tags();
$editor = $this->get_textarea_for_locator($locator);
$editorid = $editor->getAttribute('id');
// Ensure that a name is set on the iframe relating to the editorid.
$js = <<<EOF
const element = instance.dom.select("${textlocator}")[${position}];
instance.selection.select(element);
EOF;
$this->execute_javascript_for_editor($editorid, $js);
}
/**
* Upload a file in the file picker using the repository_upload plugin.
*
* Note: This step assumes we are already in the file picker.
* Note: This step is for use by TinyMCE and will be removed once an appropriate step is added to core.
* See MDL-76001 for details.
*
* @Given /^I upload "(?P<filepath_string>(?:[^"]|\\")*)" to the file picker for TinyMCE$/
*/
public function i_upload_a_file_in_the_filepicker(string $filepath): void {
if (!$this->has_tag('javascript')) {
throw new DriverException('The file picker is only available with javascript enabled');
}
if (!$this->has_tag('_file_upload')) {
throw new DriverException('File upload tests must have the @_file_upload tag on either the scenario or feature.');
}
if (!$this->has_tag('editor_tiny')) {
throw new DriverException('This step is intended for use in TinyMCE. Please vote for MDL-76001');
}
$filepicker = $this->find('dialogue', get_string('filepicker', 'core_repository'));
$this->execute('behat_general::i_click_on_in_the', [
get_string('pluginname', 'repository_upload'), 'link',
$filepicker, 'NodeElement',
]);
$reporegion = $filepicker->find('css', '.fp-repo-items');
$fileinput = $this->find('field', get_string('attachment', 'core_repository'), false, $reporegion);
$filepath = $this->normalise_fixture_filepath($filepath);
$fileinput->attachFile($filepath);
$this->execute('behat_general::i_click_on_in_the', [
get_string('upload', 'repository'), 'button',
$reporegion, 'NodeElement',
]);
}
/**
* Normalise the fixture file path relative to the dirroot.
*
* @param string $filepath
* @return string
*/
protected function normalise_fixture_filepath(string $filepath): string {
global $CFG;
$filepath = str_replace('/', DIRECTORY_SEPARATOR, $filepath);
if (!is_readable($filepath)) {
$filepath = $CFG->dirroot . DIRECTORY_SEPARATOR . $filepath;
if (!is_readable($filepath)) {
throw new ExpectationException('The file to be uploaded does not exist.', $this->getSession());
}
}
return $filepath;
}
/**
* Select in the editor.
*
* @param string $locator
* @param string $type
* @param string $editorlocator
*
* @Given /^I select the "(?P<locator_string>(?:[^"]|\\")*)" "(?P<type_string>(?:[^"]|\\")*)" in the "(?P<editorlocator_string>(?:[^"]|\\")*)" TinyMCE editor$/
*/
public function select_in_editor(string $locator, string $type, string $editorlocator): void {
$this->require_tiny_tags();
$editor = $this->get_textarea_for_locator($editorlocator);
$editorid = $editor->getAttribute('id');
// Get the iframe name for this editor.
$iframename = $this->get_editor_iframe_name($editorlocator);
// Switch to it.
$this->execute('behat_general::switch_to_iframe', [$iframename]);
// Find the element.
$element = $this->find($type, $locator);
$xpath = $element->getXpath();
// Switch back to the main window.
$this->execute('behat_general::switch_to_the_main_frame', []);
// Select the Node using the xpath.
$js = <<<EOF
const editorDocument = instance.getDoc();
const element = editorDocument.evaluate(
"${xpath}",
editorDocument,
null,
XPathResult.FIRST_ORDERED_NODE_TYPE,
null
).singleNodeValue;
instance.selection.select(element);
EOF;
$this->execute_javascript_for_editor($editorid, $js);
}
/**
* Expand all of the TinyMCE toolbars.
*
* @Given /^I expand all toolbars for the "(?P<editorlocator_string>(?:[^"]|\\")*)" TinyMCE editor$/
*
* @param string $locator
*/
public function expand_all_toolbars(string $editorlocator): void {
$this->require_tiny_tags();
$editor = $this->get_editor_container_for_locator($editorlocator);
try {
$button = $this->find('button', get_string('tiny:more...', 'editor_tiny'), false, $editor);
} catch (ExpectationException $e) {
// No more button, so no need to expand.
return;
}
if ($button->getAttribute(('aria-pressed')) === 'false') {
$this->execute('behat_general::i_click_on', [$button, 'NodeElement']);
}
}
}

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 296 KiB