Skip to content

Commit

Permalink
MDL-69107 form_autocomplete: Rewrite item selection
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
andrewnicols committed Dec 15, 2020
1 parent c537ed7 commit 22a5445
Show file tree
Hide file tree
Showing 7 changed files with 92 additions and 34 deletions.
2 changes: 1 addition & 1 deletion lib/amd/build/form-autocomplete.min.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion lib/amd/build/form-autocomplete.min.js.map

Large diffs are not rendered by default.

3 changes: 3 additions & 0 deletions lib/amd/src/form-autocomplete.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
5 changes: 4 additions & 1 deletion lib/behat/behat_field_manager.php
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,6 @@ public static function get_form_field(NodeElement $fieldnode, Session $session)
* @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
Expand Down Expand Up @@ -152,6 +151,10 @@ public static function guess_field_type(NodeElement $fieldnode, Session $session
$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':
Expand Down
86 changes: 59 additions & 27 deletions lib/behat/form_field/behat_form_autocomplete.php
Original file line number Diff line number Diff line change
Expand Up @@ -48,43 +48,75 @@ public function set_value($value) {
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();
$this->add_value(trim($value), $allowscreation);
}
}

// 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();
/**
* Add a value to the autocomplete.
*
* @param string $value
* @param bool $allowscreation
*/
protected function add_value(string $value, bool $allowscreation): void {
$value = trim($value);

// Press the escape to close the autocomplete suggestions list.
behat_base::type_keys($this->session, [behat_keys::ESCAPE]);
$this->wait_for_pending_js();
// 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();
}
}
26 changes: 24 additions & 2 deletions lib/templates/form_autocomplete_input.mustache
Original file line number Diff line number Diff line change
Expand Up @@ -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}}

Expand Down
2 changes: 0 additions & 2 deletions lib/tests/behat/behat_forms.php
Original file line number Diff line number Diff line change
Expand Up @@ -693,8 +693,6 @@ public function i_click_on_item_in_the_autocomplete_list($item) {
$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']);
}

/**
Expand Down

0 comments on commit 22a5445

Please sign in to comment.