mirror of
https://github.com/moodle/moodle.git
synced 2025-03-20 07:30:01 +01:00
Merge branch 'MDL-75982-master-latest' of https://github.com/andrewnicols/moodle
This commit is contained in:
commit
07ab2c0cfb
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -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)';
|
||||
}
|
||||
|
||||
|
@ -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"
|
@ -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>"
|
@ -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>"
|
@ -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
|
@ -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"}
|
@ -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
|
@ -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"}
|
@ -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);
|
||||
|
||||
|
@ -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;
|
||||
};
|
||||
|
210
lib/editor/tiny/plugins/h5p/tests/behat/h5p.feature
Normal file
210
lib/editor/tiny/plugins/h5p/tests/behat/h5p.feature
Normal 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"
|
28
lib/editor/tiny/plugins/media/tests/behat/image.feature
Normal file
28
lib/editor/tiny/plugins/media/tests/behat/image.feature
Normal 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
|
29
lib/editor/tiny/plugins/media/tests/behat/video.feature
Normal file
29
lib/editor/tiny/plugins/media/tests/behat/video.feature
Normal 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
|
@ -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']);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
BIN
lib/editor/tiny/tests/behat/fixtures/moodle-logo.mp4
Normal file
BIN
lib/editor/tiny/tests/behat/fixtures/moodle-logo.mp4
Normal file
Binary file not shown.
BIN
lib/editor/tiny/tests/behat/fixtures/tinyscreenshot.png
Normal file
BIN
lib/editor/tiny/tests/behat/fixtures/tinyscreenshot.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 296 KiB |
Loading…
x
Reference in New Issue
Block a user