Proyectos de Subversion Moodle

Rev

| Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1 efrain 1
<?php
2
// This file is part of Moodle - http://moodle.org/
3
//
4
// Moodle is free software: you can redistribute it and/or modify
5
// it under the terms of the GNU General Public License as published by
6
// the Free Software Foundation, either version 3 of the License, or
7
// (at your option) any later version.
8
//
9
// Moodle is distributed in the hope that it will be useful,
10
// but WITHOUT ANY WARRANTY; without even the implied warranty of
11
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
// GNU General Public License for more details.
13
//
14
// You should have received a copy of the GNU General Public License
15
// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
16
 
17
use Behat\Mink\Element\NodeElement;
18
use Behat\Mink\Exception\DriverException;
19
use Behat\Mink\Exception\ExpectationException;
20
 
21
/**
22
 * Behat helpers for TinyMCE Plugins.
23
 *
24
 * @package    editor_tiny
25
 * @category   test
26
 * @copyright  2022 Andrew Lyons <andrew@nicols.co.uk>
27
 */
28
trait editor_tiny_helpers {
29
    /**
30
     * Execute some JavaScript for a particular Editor instance.
31
     *
32
     * The editor instance is available on the 'instnace' variable.
33
     *
34
     * @param string $editorid The ID of the editor
35
     * @param string $code The code to execute
36
     */
37
    protected function execute_javascript_for_editor(string $editorid, string $code): void {
38
        $js = <<<EOF
39
        require(['editor_tiny/editor'], (editor) => {
40
            const instance = editor.getInstanceForElementId('{$editorid}');
41
            {$code}
42
        });
43
        EOF;
44
 
45
        $this->execute_script($js);
46
    }
47
 
48
    /**
49
     * Resolve some JavaScript for a particular Editor instance.
50
     *
51
     * The editor instance is available on the 'instnace' variable.
52
     * The code should return a value by passing it to the `resolve` function.
53
     *
54
     * @param string $editorid The ID of the editor
55
     * @param string $code The code to evaluate
56
     * @return string|null|array
57
     */
58
    protected function evaluate_javascript_for_editor(string $editorid, string $code) {
59
        $js = <<<EOF
60
        return new Promise((resolve, reject) => {
61
            require(['editor_tiny/editor'], (editor) => {
62
                const instance = editor.getInstanceForElementId('{$editorid}');
63
                if (!instance) {
64
                    reject("Instance '{$editorid}' not found");
65
                }
66
 
67
                {$code}
68
            });
69
        });
70
        EOF;
71
 
72
        return $this->evaluate_script($js);
73
    }
74
 
75
    /**
76
     * Set the value for the editor.
77
     *
78
     * Note: This function is called by the behat_form_editor class.
79
     * It is called regardless of the current default editor as editor selection is a user preference.
80
     * Therefore it must fail gracefully and only set a value if the editor instance was found on the page.
81
     *
82
     * @param string $editorid
83
     * @param string $value
84
     */
85
    public function set_editor_value(string $editorid, string $value): void {
86
        if (!$this->running_javascript()) {
87
            return;
88
        }
89
 
90
        $this->execute_javascript_for_editor($editorid, <<<EOF
91
            instance.setContent('{$value}');
92
            instance.undoManager.add();
93
            EOF);
94
    }
95
 
96
    /**
97
     * Store the current value of the editor, if it is a Tiny editor, to the textarea.
98
     *
99
     * @param string $editorid The ID of the editor.
100
     */
101
    public function store_current_value(string $editorid): void {
102
        $this->execute_javascript_for_editor($editorid, "instance?.save();");
103
    }
104
 
105
    /**
106
     * Ensure that the editor_tiny tag is in use.
107
     *
108
     * This function should be used for any step defined in this file.
109
     *
110
     * @throws DriverException Thrown if the editor_tiny tag is not specified for this file
111
     */
112
    protected function require_tiny_tags(): void {
113
        // Ensure that this step only runs in TinyMCE tags.
114
        if (!$this->has_tag('editor_tiny')) {
115
            throw new DriverException(
116
                'TinyMCE tests using this step must have the @editor_tiny tag on either the scenario or feature.'
117
            );
118
        }
119
    }
120
 
121
    /**
122
     * Get the Mink NodeElement of the <textarea> for the specified locator.
123
     *
124
     * Moodle mostly referes to the textarea, rather than the editor itself and interactions are translated to the
125
     * Editor using the TinyMCE API.
126
     *
127
     * @param string $locator A Moodle field locator
128
     * @return NodeElement The element found by the find_field function
129
     */
130
    protected function get_textarea_for_locator(string $locator): NodeElement {
131
        return $this->find_field($locator);
132
    }
133
 
134
    /**
135
     * Get the Mink NodeElement of the container for the specified locator.
136
     *
137
     * This is the top-most HTML element for the editor found by TinyMCE.getContainer().
138
     *
139
     * @param string $locator A Moodle field locator
140
     * @return NodeElement The Mink NodeElement representing the container.
141
     */
142
    protected function get_editor_container_for_locator(string $locator): NodeElement {
143
        $textarea = $this->get_textarea_for_locator($locator);
144
        $editorid = $textarea->getAttribute('id');
145
 
146
        $targetid = uniqid();
147
        $js = <<<EOF
148
            const container = instance.getContainer();
149
            if (!container.id) {
150
                container.id = '{$targetid}';
151
            }
152
            resolve(container.id);
153
        EOF;
154
        $containerid = $this->evaluate_javascript_for_editor($editorid, $js);
155
 
156
        return $this->find('css', "#{$containerid}");
157
    }
158
 
159
    /**
160
     * Get the name of the iframe relating to the editor.
161
     *
162
     * If no name is found, then add one.
163
     *
164
     * If the editor it not found, then throw an exception.
165
     *
166
     * @param string $locator The name of the editor
167
     * @return string The name of the iframe
168
     */
169
    protected function get_editor_iframe_name(string $locator): string {
170
        return $this->get_editor_iframe_name_for_element($this->get_textarea_for_locator($locator));
171
    }
172
 
173
    /**
174
     * Get the name of the iframe relating to the editor.
175
     *
176
     * If no name is found, then add one.
177
     *
178
     * If the editor it not found, then throw an exception.
179
 
180
     * @param NodeElement $editor The editor element
181
     * @return string The name of the iframe
182
     */
183
    protected function get_editor_iframe_name_for_element(NodeElement $editor): string {
184
        $editorid = $editor->getAttribute('id');
185
 
186
        // Ensure that a name is set on the iframe relating to the editorid.
187
        $js = <<<EOF
188
            if (!instance.iframeElement.name) {
189
                instance.iframeElement.name = '{$editorid}';
190
            }
191
            resolve(instance.iframeElement.name);
192
        EOF;
193
 
194
        return $this->evaluate_javascript_for_editor($editorid, $js);
195
    }
196
 
197
    /**
198
     * Normalise the fixture file path relative to the dirroot.
199
     *
200
     * @param string $filepath
201
     * @return string
202
     */
203
    protected function normalise_fixture_filepath(string $filepath): string {
204
        global $CFG;
205
 
206
        $filepath = str_replace('/', DIRECTORY_SEPARATOR, $filepath);
207
        if (!is_readable($filepath)) {
208
            $filepath = $CFG->dirroot . DIRECTORY_SEPARATOR . $filepath;
209
            if (!is_readable($filepath)) {
210
                throw new ExpectationException('The file to be uploaded does not exist.', $this->getSession());
211
            }
212
        }
213
 
214
        return $filepath;
215
    }
216
}