diff options
author | Dries Buytaert <dries@buytaert.net> | 2010-04-07 17:30:43 +0000 |
---|---|---|
committer | Dries Buytaert <dries@buytaert.net> | 2010-04-07 17:30:43 +0000 |
commit | d5ce7f5281373044b8e1df6d95287270c0b33145 (patch) | |
tree | 0105e507304fc399d892bcca8f6805b3ae3d4739 /modules/simpletest/drupal_web_test_case.php | |
parent | 27aa5b1b174cb24ac10d3524773d9f6412d2297d (diff) | |
download | brdo-d5ce7f5281373044b8e1df6d95287270c0b33145.tar.gz brdo-d5ce7f5281373044b8e1df6d95287270c0b33145.tar.bz2 |
- Patch #384992 by effulgentsia, sun: drupal_html_id() does not work correctly in AJAX context with multiple forms on page.
Diffstat (limited to 'modules/simpletest/drupal_web_test_case.php')
-rw-r--r-- | modules/simpletest/drupal_web_test_case.php | 112 |
1 files changed, 108 insertions, 4 deletions
diff --git a/modules/simpletest/drupal_web_test_case.php b/modules/simpletest/drupal_web_test_case.php index 5d64abb5c..ccc2f354b 100644 --- a/modules/simpletest/drupal_web_test_case.php +++ b/modules/simpletest/drupal_web_test_case.php @@ -1659,7 +1659,9 @@ class DrupalWebTestCase extends DrupalTestCase { // http://www.w3.org/TR/html4/interact/forms.html#h-17.13.4.1 $post[$key] = urlencode($key) . '=' . urlencode($value); } - if ($ajax && isset($submit['triggering_element'])) { + // For AJAX requests, add '_triggering_element_*' and + // 'ajax_html_ids' to the POST data, as ajax.js does. + if ($ajax) { if (is_array($submit['triggering_element'])) { // Get the first key/value pair in the array. $post['_triggering_element_value'] = '_triggering_element_value=' . urlencode(reset($submit['triggering_element'])); @@ -1668,6 +1670,10 @@ class DrupalWebTestCase extends DrupalTestCase { else { $post['_triggering_element_name'] = '_triggering_element_name=' . urlencode($submit['triggering_element']); } + foreach ($this->xpath('//*[@id]') as $element) { + $id = (string) $element['id']; + $post[] = urlencode('ajax_html_ids[]') . '=' . urlencode($id); + } } $post = implode('&', $post); } @@ -1698,10 +1704,78 @@ class DrupalWebTestCase extends DrupalTestCase { } /** - * Execute a POST request on an AJAX path and JSON decode the result. + * Execute an AJAX submission. + * + * This executes a POST as ajax.js does. It uses the returned JSON data, an + * array of commands, to update $this->content using equivalent DOM + * manipulation as is used by ajax.js. It also returns the array of commands. + * + * @see ajax.js */ - protected function drupalPostAJAX($path, $edit, $triggering_element, $ajax_path = 'system/ajax', array $options = array(), array $headers = array()) { - return drupal_json_decode($this->drupalPost($path, $edit, array('path' => $ajax_path, 'triggering_element' => $triggering_element), $options, $headers)); + protected function drupalPostAJAX($path, $edit, $triggering_element, $ajax_path = 'system/ajax', array $options = array(), array $headers = array(), $form_html_id = NULL, $ajax_settings = array()) { + // Get the content of the initial page prior to calling drupalPost(), since + // drupalPost() replaces $this->content. + if (isset($path)) { + $this->drupalGet($path, $options); + } + $content = $this->content; + $return = drupal_json_decode($this->drupalPost(NULL, $edit, array('path' => $ajax_path, 'triggering_element' => $triggering_element), $options, $headers, $form_html_id)); + + // We need $ajax_settings['wrapper'] to perform DOM manipulation. + if (!empty($ajax_settings) && !empty($return)) { + // DOM can load HTML soup. But, HTML soup can throw warnings, suppress + // them. + @$dom = DOMDocument::loadHTML($content); + foreach ($return as $command) { + // @todo ajax.js can process commands other than 'insert' and can + // process commands that include a 'selector', but these are hard to + // emulate with DOMDocument. For now, we only implement 'insert' + // commands that use $ajax_settings['wrapper']. + if ($command['command'] == 'insert' && !isset($command['selector'])) { + // $dom->getElementById() doesn't work when drupalPostAJAX() is + // invoked multiple times for a page, so use XPath instead. This also + // sets us up for adding support for $command['selector'], though it + // will require transforming a jQuery selector to XPath. + $xpath = new DOMXPath($dom); + $wrapperNode = $xpath->query('//*[@id="' . $ajax_settings['wrapper'] . '"]')->item(0); + if ($wrapperNode) { + // ajax.js adds an enclosing DIV to work around a Safari bug. + $newDom = DOMDocument::loadHTML('<div>' . $command['data'] . '</div>'); + $newNode = $dom->importNode($newDom->documentElement->firstChild->firstChild, TRUE); + $method = isset($command['method']) ? $command['method'] : $ajax_settings['method']; + // The "method" is a jQuery DOM manipulation function. Emulate each + // one using PHP's DOMNode API. + switch ($method) { + case 'replaceWith': + $wrapperNode->parentNode->replaceChild($newNode, $wrapperNode); + break; + case 'append': + $wrapperNode->appendChild($newNode); + break; + case 'prepend': + // If no firstChild, insertBefore() falls back to appendChild(). + $wrapperNode->insertBefore($newNode, $wrapperNode->firstChild); + break; + case 'before': + $wrapperNode->parentNode->insertBefore($newNode, $wrapperNode); + break; + case 'after': + // If no nextSibling, insertBefore() falls back to appendChild(). + $wrapperNode->parentNode->insertBefore($newNode, $wrapperNode->nextSibling); + break; + case 'html': + foreach ($wrapperNode->childNodes as $childNode) { + $wrapperNode->removeChild($childNode); + } + $wrapperNode->appendChild($newNode); + break; + } + } + } + } + $this->drupalSetContent($dom->saveHTML()); + } + return $return; } /** @@ -2788,6 +2862,36 @@ class DrupalWebTestCase extends DrupalTestCase { } /** + * Assert that each HTML ID is used for just a single element. + * + * @param $message + * Message to display. + * @param $group + * The group this message belongs to. + * @param $ids_to_skip + * An optional array of ids to skip when checking for duplicates. It is + * always a bug to have duplicate HTML IDs, so this parameter is to enable + * incremental fixing of core code. Whenever a test passes this parameter, + * it should add a "todo" comment above the call to this function explaining + * the legacy bug that the test wishes to ignore and including a link to an + * issue that is working to fix that legacy bug. + * @return + * TRUE on pass, FALSE on fail. + */ + protected function assertNoDuplicateIds($message = '', $group = 'Other', $ids_to_skip = array()) { + $status = TRUE; + foreach ($this->xpath('//*[@id]') as $element) { + $id = (string) $element['id']; + if (isset($seen_ids[$id]) && !in_array($id, $ids_to_skip)) { + $this->fail(t('The HTML ID %id is unique.', array('%id' => $id)), $group); + $status = FALSE; + } + $seen_ids[$id] = TRUE; + } + return $this->assert($status, $message, $group); + } + + /** * Helper function: construct an XPath for the given set of attributes and value. * * @param $attribute |