From febd5d944ca0e9700ac224a18f4b3d0b1133945f Mon Sep 17 00:00:00 2001 From: Andrew Nicols Date: Thu, 13 Oct 2022 16:03:49 +0800 Subject: [PATCH] MDL-75985 behat: Standardise HTML output when comparing editor content This commit adds a standardise_html function and updates the matches function to compare normalised content. This allows for a wider variety of valid editor output to be handled using the standard value matching steps in Behat, thus supporting editors other than Atto better. --- lib/behat/form_field/behat_form_editor.php | 57 +++++++++++++++++++++- lib/behat/form_field/behat_form_field.php | 9 ++-- lib/tests/behat/behat_general.php | 12 ++++- 3 files changed, 72 insertions(+), 6 deletions(-) diff --git a/lib/behat/form_field/behat_form_editor.php b/lib/behat/form_field/behat_form_editor.php index acc00513254..89ab42bd542 100644 --- a/lib/behat/form_field/behat_form_editor.php +++ b/lib/behat/form_field/behat_form_editor.php @@ -93,7 +93,60 @@ class behat_form_editor extends behat_form_textarea { * @return bool The provided value matches the field value? */ public function matches($expectedvalue) { - // A text editor may silently wrap the content in p tags (or not). Neither is an error. - return $this->text_matches($expectedvalue) || $this->text_matches('

' . $expectedvalue . '

'); + // Fetch the actual value to save fetching it multiple times. + $actualvalue = $this->get_value(); + + if ($this->text_matches($expectedvalue, $actualvalue)) { + // The text is an exact match already. + return true; + } + + if ($this->text_matches("

{$expectedvalue}

", $actualvalue)) { + // A text editor may silently wrap the content in p tags. + return true; + } + + // Standardise both the expected value and the actual field value. + // We are likely dealing with HTML content, given this is an editor. + $expectedvalue = $this->standardise_html($expectedvalue); + $actualvalue = $this->standardise_html($actualvalue); + + // Note: We don't need to worry about the floats here that we care about in text_matches. + // That condition isn't relevant to the content of an editor. + if ($expectedvalue === $actualvalue) { + return true; + } + + return false; + } + + /** + * Standardises the HTML content for comparison. + * + * @param string $html The HTML content to standardise + * @return string The standardised HTML content + */ + protected function standardise_html(string $html): string { + $document = new DOMDocument(); + $errorstate = libxml_use_internal_errors(true); + + // Format the whitespace nicely. + $document->preserveWhiteSpace = false; + $document->formatOutput = true; + + // Wrap the content in a DIV element so that it is not parsed weirdly. + // Note: We must remove newlines too because DOMDocument does not do so, despite preserveWhiteSpace being false. + // Unfortunately this is slightly limited in that it will also remove newlines from
 content and similar.
+        $document->loadHTML(str_replace("\n", "", "
{$html}
"), LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD); + $document->normalizeDocument(); + libxml_clear_errors(); + libxml_use_internal_errors($errorstate); + + // Save the content of the 'div' element, removing the
and
tags at the start and end. + return trim(substr( + $document->saveHTML($document->getElementsByTagName('div')->item(0)), + 5, + -6 + )); } } diff --git a/lib/behat/form_field/behat_form_field.php b/lib/behat/form_field/behat_form_field.php index c834b7bdb35..5f58d2aab1a 100644 --- a/lib/behat/form_field/behat_form_field.php +++ b/lib/behat/form_field/behat_form_field.php @@ -244,18 +244,21 @@ class behat_form_field implements behat_session_interface { * Checks if the provided text matches the field value. * * @param string $expectedvalue + * @param string|null $actualvalue The actual value. If not specified, this will be fetched from $this->get_value(). * @return bool */ - protected function text_matches($expectedvalue) { + protected function text_matches($expectedvalue, ?string $actualvalue = null): bool { + $actualvalue = $actualvalue ?? $this->get_value(); + // Non strict string comparison. - if (trim($expectedvalue) == trim($this->get_value())) { + if (trim($expectedvalue) == trim($actualvalue)) { return true; } // Do one more matching attempt for floats that are valid with current decsep in use // (let's continue non strict comparing them as strings, but once unformatted). $expectedfloat = unformat_float(trim($expectedvalue), true); - $actualfloat = unformat_float(trim($this->get_value()), true); + $actualfloat = unformat_float(trim($actualvalue), true); // If they aren't null or false, then we are good to be compared (basically is_numeric()). $goodfloats = !is_null($expectedfloat) && ($expectedfloat !== false) && !is_null($actualfloat) && ($actualfloat !== false); diff --git a/lib/tests/behat/behat_general.php b/lib/tests/behat/behat_general.php index 18103b8d9ac..b2184d10ec4 100644 --- a/lib/tests/behat/behat_general.php +++ b/lib/tests/behat/behat_general.php @@ -1776,7 +1776,17 @@ EOF; * @param string $keys The key, or list of keys, to type */ public function i_type(string $keys): void { - behat_base::type_keys($this->getSession(), str_split($keys)); + // Certain keys, such as the newline character, must be converted to the appropriate character code. + // Without this, keys will behave differently depending on the browser. + $keylist = array_map(function($key): string { + switch ($key) { + case '\n': + behat_keys::ENTER; + default: + return $key; + } + }, str_split($keys)); + behat_base::type_keys($this->getSession(), $keylist); } /**