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:
Andrew Nicols
2020-12-15 09:08:05 +08:00
parent c537ed7fed
commit 22a5445346
7 changed files with 94 additions and 36 deletions
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+3
View File
@@ -1173,6 +1173,9 @@ function($, log, str, templates, notification, LoadingIcon, Aria) {
originalSelect.hide();
var container = originalSelect.parent();
// Ensure that the data-fieldtype is set for behat.
$(input).find('input').attr('data-fieldtype', 'autocomplete');
container.append(layout);
container.find('[data-region="form_autocomplete-input"]').replaceWith(input);
container.find('[data-region="form_autocomplete-suggestions"]').replaceWith(suggestions);
+4 -1
View File
@@ -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,43 +48,75 @@ 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=option]');
foreach ($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();
// Press the escape to close the autocomplete suggestions list.
behat_base::type_keys($this->session, [behat_keys::ESCAPE]);
$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): void {
$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.
behat_base::type_keys($this->session, [behat_keys::ESCAPE]);
$this->wait_for_pending_js();
}
}
+24 -2
View File
@@ -37,13 +37,35 @@
}}
{{#showSuggestions}}
<div class="d-md-inline-block mr-md-2 position-relative">
<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}}/>
<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}}">&#x25BC;</span>
</div>
{{/showSuggestions}}
{{^showSuggestions}}
<div class="d-md-inline-block mr-md-2">
<input type="text" id="{{inputId}}" class="form-control" placeholder="{{placeholder}}" role="textbox" aria-owns="{{selectionId}}" {{#tags}}data-tags="1"{{/tags}}/>
<input type="text"{{!
}} id="{{inputId}}"{{!
}} class="form-control"{{!
}} placeholder="{{placeholder}}"{{!
}} role="textbox"{{!
}} aria-owns="{{selectionId}}"{{!
}} {{#tags}}data-tags="1"{{/tags}}{{!
}} {{#multiple}}data-multiple="multiple"{{/multiple}}{{!
}}>
</div>
{{/showSuggestions}}
-2
View File
@@ -693,8 +693,6 @@ 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']);
}
/**