MDL-69107 form_autocomplete: Rewrite item selection
The form_autocomplete is essentially a custom element. Unfortunately the `setValue()` function in Mink has undesired actions so it is necessary to write our own handling for it. The standard Mink `setValue()` function focuses the element, sets a value, and then blurs the element. In the case of the autocomplete this can cause the autocomplete suggestions list to be closed in some situations. Instead of using the setValue we click, and type the value, but do not immediately blur.
This commit is contained in:
+1
-1
File diff suppressed because one or more lines are too long
@@ -1001,7 +1001,7 @@ define(['jquery', 'core/log', 'core/str', 'core/templates', 'core/notification']
|
||||
// Increment the unique counter so we don't get duplicates ever.
|
||||
uniqueId++;
|
||||
|
||||
options.multiple = originalSelect.attr('multiple');
|
||||
options.multiple = !!originalSelect.attr('multiple');
|
||||
|
||||
if (typeof closeSuggestionsOnSelect !== "undefined") {
|
||||
options.closeSuggestionsOnSelect = closeSuggestionsOnSelect;
|
||||
@@ -1043,6 +1043,10 @@ define(['jquery', 'core/log', 'core/str', 'core/templates', 'core/notification']
|
||||
return $.when(renderInput, renderDatalist, renderSelection)
|
||||
.then(function(input, suggestions, selection) {
|
||||
originalSelect.hide();
|
||||
|
||||
// Ensure that the data-fieldtype is set for behat.
|
||||
$(input).find('input').attr('data-fieldtype', 'autocomplete');
|
||||
|
||||
originalSelect.after(suggestions);
|
||||
originalSelect.after(input);
|
||||
originalSelect.after(selection);
|
||||
|
||||
@@ -101,7 +101,6 @@ class behat_field_manager {
|
||||
* @return behat_form_field
|
||||
*/
|
||||
public static function get_field_instance($type, NodeElement $fieldnode, Session $session) {
|
||||
|
||||
global $CFG;
|
||||
|
||||
// If the field is not part of a moodleform, we should still try to find out
|
||||
@@ -152,6 +151,10 @@ class behat_field_manager {
|
||||
$type = $fieldnode->getAttribute('type');
|
||||
switch ($type) {
|
||||
case 'text':
|
||||
if ($fieldtype = $fieldnode->getAttribute('data-fieldtype')) {
|
||||
return self::normalise_fieldtype($fieldtype);
|
||||
}
|
||||
return 'text';
|
||||
case 'password':
|
||||
case 'email':
|
||||
case 'file':
|
||||
|
||||
@@ -48,44 +48,77 @@ class behat_form_autocomplete extends behat_form_text {
|
||||
throw new coding_exception('Setting the value of an autocomplete field requires javascript.');
|
||||
}
|
||||
|
||||
// Set the value of the autocomplete's input.
|
||||
// If this autocomplete offers suggestions then these should be fetched by setting the value and waiting for the
|
||||
// JS to finish fetching those suggestions.
|
||||
// Clear all current selections.
|
||||
$rootnode = $this->field->getParent()->getParent();
|
||||
$selections = $rootnode->findAll('css', '.form-autocomplete-selection > [role=listitem]');
|
||||
foreach (array_reverse($selections) as $selection) {
|
||||
$selection->click();
|
||||
$this->wait_for_pending_js();
|
||||
}
|
||||
|
||||
$istagelement = $this->field->hasAttribute('data-tags') && $this->field->getAttribute('data-tags');
|
||||
$allowscreation = $this->field->hasAttribute('data-tags') && !empty($this->field->getAttribute('data-tags'));
|
||||
$hasmultiple = $this->field->hasAttribute('data-multiple') && !empty($this->field->getAttribute('data-multiple'));
|
||||
|
||||
if ($istagelement && false !== strpos($value, ',')) {
|
||||
// Commas have a special meaning as a value separator in 'tag' autocomplete elements.
|
||||
if ($hasmultiple && false !== strpos($value, ',')) {
|
||||
// Commas have a special meaning as a value separator in 'multiple' autocomplete elements.
|
||||
// To handle this we break the value up by comma, and enter it in chunks.
|
||||
$values = explode(',', $value);
|
||||
|
||||
while ($value = array_shift($values)) {
|
||||
$this->set_value($value);
|
||||
$this->add_value(trim($value), $allowscreation);
|
||||
}
|
||||
} else {
|
||||
$this->field->setValue($value);
|
||||
$this->wait_for_pending_js();
|
||||
|
||||
// If the autocomplete found suggestions, then it will have:
|
||||
// 1) marked itself as expanded; and
|
||||
// 2) have an aria-selected suggestion in the list.
|
||||
$expanded = $this->field->getAttribute('aria-expanded');
|
||||
$suggestion = $this->field->getParent()->find('css', '.form-autocomplete-suggestions > [aria-selected="true"]');
|
||||
|
||||
if ($expanded && null !== $suggestion) {
|
||||
// A suggestion was found.
|
||||
// Click on the first item in the list.
|
||||
$suggestion->click();
|
||||
} else {
|
||||
// Press the return key to create a new tag.
|
||||
behat_base::type_keys($this->session, [behat_keys::ENTER]);
|
||||
}
|
||||
$this->wait_for_pending_js();
|
||||
|
||||
// Note: This does not make use of the `type_keys` API because it can cause some modals to close.
|
||||
// This is not an issue in later versions of Moodle where the autocomplete handling has been updated.
|
||||
$this->key_press(27);
|
||||
$this->wait_for_pending_js();
|
||||
$this->add_value(trim($value), $allowscreation);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a value to the autocomplete.
|
||||
*
|
||||
* @param string $value
|
||||
* @param bool $allowscreation
|
||||
*/
|
||||
protected function add_value(string $value, bool $allowscreation) {
|
||||
$value = trim($value);
|
||||
|
||||
// Click into the field.
|
||||
$this->field->click();
|
||||
|
||||
// Remove any existing text.
|
||||
do {
|
||||
behat_base::type_keys($this->session, [behat_keys::BACKSPACE, behat_keys::DELETE]);
|
||||
} while (strlen($this->field->getValue()) > 0);
|
||||
$this->wait_for_pending_js();
|
||||
|
||||
// Type in the new value.
|
||||
behat_base::type_keys($this->session, str_split($value));
|
||||
$this->wait_for_pending_js();
|
||||
|
||||
// If the autocomplete found suggestions, then it will have:
|
||||
// 1) marked itself as expanded; and
|
||||
// 2) have an aria-selected suggestion in the list.
|
||||
$expanded = $this->field->getAttribute('aria-expanded');
|
||||
$suggestion = $this->field->getParent()->getParent()->find('css', '.form-autocomplete-suggestions > [aria-selected="true"]');
|
||||
|
||||
if ($expanded && null !== $suggestion) {
|
||||
// A suggestion was found.
|
||||
// Click on the first item in the list.
|
||||
$suggestion->click();
|
||||
} else if ($allowscreation) {
|
||||
// Press the return key to create a new entry.
|
||||
behat_base::type_keys($this->session, [behat_keys::ENTER]);
|
||||
} else {
|
||||
throw new \InvalidArgumentException(
|
||||
"Unable to find '{$value}' in the list of options, and unable to create a new option"
|
||||
);
|
||||
}
|
||||
|
||||
$this->wait_for_pending_js();
|
||||
|
||||
// Press the escape to close the autocomplete suggestions list.
|
||||
// Note: This does not make use of the `type_keys` API because it can cause some modals to close.
|
||||
// This is not an issue in later versions of Moodle where the autocomplete handling has been updated.
|
||||
$this->key_press(27);
|
||||
$this->wait_for_pending_js();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,10 +36,32 @@
|
||||
{ "inputID": 1, "suggestionsId": 2, "selectionId": 3, "downArrowId": 4, "placeholder": "Select something" }
|
||||
}}
|
||||
{{#showSuggestions}}
|
||||
<input type="text" id="{{inputId}}" list="{{suggestionsId}}" placeholder="{{placeholder}}" role="combobox" aria-expanded="false" autocomplete="off" autocorrect="off" autocapitalize="off" aria-autocomplete="list" aria-owns="{{suggestionsId}} {{selectionId}}" {{#tags}}data-tags="1"{{/tags}}/><span class="form-autocomplete-downarrow" id="{{downArrowId}}">▼</span>
|
||||
<input type="text"{{!
|
||||
}} id="{{inputId}}"{{!
|
||||
}} class="form-control"{{!
|
||||
}} list="{{suggestionsId}}"{{!
|
||||
}} placeholder="{{placeholder}}"{{!
|
||||
}} role="combobox"{{!
|
||||
}} aria-expanded="false"{{!
|
||||
}} autocomplete="off"{{!
|
||||
}} autocorrect="off"{{!
|
||||
}} autocapitalize="off"{{!
|
||||
}} aria-autocomplete="list"{{!
|
||||
}} aria-owns="{{suggestionsId}} {{selectionId}}"{{!
|
||||
}}{{#tags}} data-tags="1"{{/tags}}{{!
|
||||
}}{{#multiple}} data-multiple="multiple"{{/multiple}}{{!
|
||||
}}>
|
||||
<span class="form-autocomplete-downarrow position-absolute p-1" id="{{downArrowId}}">▼</span>
|
||||
{{/showSuggestions}}
|
||||
{{^showSuggestions}}
|
||||
<input type="text" id="{{inputId}}" placeholder="{{placeholder}}" role="textbox" aria-owns="{{selectionId}}" {{#tags}}data-tags="1"{{/tags}}/>
|
||||
<input type="text"{{!
|
||||
}} id="{{inputId}}"{{!
|
||||
}} placeholder="{{placeholder}}"{{!
|
||||
}} role="textbox"{{!
|
||||
}} aria-owns="{{selectionId}}"{{!
|
||||
}}{{#tags}} data-tags="1"{{/tags}}{{!
|
||||
}}{{#multiple}} data-multiple="multiple"{{/multiple}}{{!
|
||||
}}>
|
||||
{{/showSuggestions}}
|
||||
|
||||
{{#js}}
|
||||
|
||||
@@ -693,18 +693,21 @@ class behat_forms extends behat_base {
|
||||
$xpathtarget = "//ul[@class='form-autocomplete-suggestions']//*[contains(concat('|', string(.), '|'),'|" . $item . "|')]";
|
||||
|
||||
$this->execute('behat_general::i_click_on', [$xpathtarget, 'xpath_element']);
|
||||
|
||||
$this->execute('behat_general::i_press_key_in_element', ['13', 'body', 'xpath_element']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Open the auto-complete suggestions list (Assuming there is only one on the page.).
|
||||
*
|
||||
* @Given /^I open the autocomplete suggestions list$/
|
||||
* @Given I open the autocomplete suggestions list
|
||||
* @Given I open the autocomplete suggestions list in the :container :containertype
|
||||
*/
|
||||
public function i_open_the_autocomplete_suggestions_list() {
|
||||
public function i_open_the_autocomplete_suggestions_list($container = null, $containertype = null) {
|
||||
$csstarget = ".form-autocomplete-downarrow";
|
||||
$this->execute('behat_general::i_click_on', [$csstarget, 'css_element']);
|
||||
if ($container && $containertype) {
|
||||
$this->execute('behat_general::i_click_on', [$csstarget, 'css_element', $container, $containertype]);
|
||||
} else {
|
||||
$this->execute('behat_general::i_click_on', [$csstarget, 'css_element']);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -36,10 +36,32 @@
|
||||
{ "inputID": 1, "suggestionsId": 2, "selectionId": 3, "downArrowId": 4, "placeholder": "Select something" }
|
||||
}}
|
||||
{{#showSuggestions}}
|
||||
<input type="text" id="{{inputId}}" class="form-control" list="{{suggestionsId}}" placeholder="{{placeholder}}" role="combobox" aria-expanded="false" autocomplete="off" autocorrect="off" autocapitalize="off" aria-autocomplete="list" aria-owns="{{suggestionsId}} {{selectionId}}" {{#tags}}data-tags="1"{{/tags}}/><span class="form-autocomplete-downarrow" id="{{downArrowId}}">▼</span>
|
||||
<input type="text"{{!
|
||||
}} id="{{inputId}}"{{!
|
||||
}} class="form-control"{{!
|
||||
}} list="{{suggestionsId}}"{{!
|
||||
}} placeholder="{{placeholder}}"{{!
|
||||
}} role="combobox"{{!
|
||||
}} aria-expanded="false"{{!
|
||||
}} autocomplete="off"{{!
|
||||
}} autocorrect="off"{{!
|
||||
}} autocapitalize="off"{{!
|
||||
}} aria-autocomplete="list"{{!
|
||||
}} aria-owns="{{suggestionsId}} {{selectionId}}"{{!
|
||||
}}{{#tags}} data-tags="1"{{/tags}}{{!
|
||||
}}{{#multiple}} data-multiple="multiple"{{/multiple}}{{!
|
||||
}}>
|
||||
<span class="form-autocomplete-downarrow" id="{{downArrowId}}">▼</span>
|
||||
{{/showSuggestions}}
|
||||
{{^showSuggestions}}
|
||||
<input type="text" id="{{inputId}}" placeholder="{{placeholder}}" role="textbox" aria-owns="{{selectionId}}" {{#tags}}data-tags="1"{{/tags}}/>
|
||||
<input type="text"{{!
|
||||
}} id="{{inputId}}"{{!
|
||||
}} placeholder="{{placeholder}}"{{!
|
||||
}} role="textbox"{{!
|
||||
}} aria-owns="{{selectionId}}"{{!
|
||||
}}{{#tags}} data-tags="1"{{/tags}}{{!
|
||||
}}{{#multiple}} data-multiple="multiple"{{/multiple}}{{!
|
||||
}}>
|
||||
{{/showSuggestions}}
|
||||
|
||||
{{#js}}
|
||||
|
||||
Reference in New Issue
Block a user