You are here

protected function WebTestBase::drupalProcessAjaxResponse in Drupal 8

Processes an AJAX response into current content.

This processes the AJAX response as ajax.js does. It uses the response's JSON data, an array of commands, to update $this->content using equivalent DOM manipulation as is used by ajax.js. It does not apply custom AJAX commands though, because emulation is only implemented for the AJAX commands that ship with Drupal core.

Parameters

string $content: The current HTML content.

array $ajax_response: An array of AJAX commands.

array $ajax_settings: An array of AJAX settings which will be used to process the response.

array $drupal_settings: An array of settings to update the value of drupalSettings for the currently-loaded page.

See also

drupalPostAjaxForm()

ajax.js

1 call to WebTestBase::drupalProcessAjaxResponse()
WebTestBase::drupalPostAjaxForm in core/modules/simpletest/src/WebTestBase.php
Executes an Ajax form submission.

File

core/modules/simpletest/src/WebTestBase.php, line 1234

Class

WebTestBase
Test case for typical Drupal tests.

Namespace

Drupal\simpletest

Code

protected function drupalProcessAjaxResponse($content, array $ajax_response, array $ajax_settings, array $drupal_settings) {

  // ajax.js applies some defaults to the settings object, so do the same
  // for what's used by this function.
  $ajax_settings += [
    'method' => 'replaceWith',
  ];

  // DOM can load HTML soup. But, HTML soup can throw warnings, suppress
  // them.
  $dom = new \DOMDocument();
  @$dom
    ->loadHTML($content);

  // XPath allows for finding wrapper nodes better than DOM does.
  $xpath = new \DOMXPath($dom);
  foreach ($ajax_response as $command) {

    // Error messages might be not commands.
    if (!is_array($command)) {
      continue;
    }
    switch ($command['command']) {
      case 'settings':
        $drupal_settings = NestedArray::mergeDeepArray([
          $drupal_settings,
          $command['settings'],
        ], TRUE);
        break;
      case 'insert':
        $wrapperNode = NULL;

        // When a command doesn't specify a selector, use the
        // #ajax['wrapper'] which is always an HTML ID.
        if (!isset($command['selector'])) {
          $wrapperNode = $xpath
            ->query('//*[@id="' . $ajax_settings['wrapper'] . '"]')
            ->item(0);
        }
        elseif (in_array($command['selector'], [
          'head',
          'body',
        ])) {
          $wrapperNode = $xpath
            ->query('//' . $command['selector'])
            ->item(0);
        }
        if ($wrapperNode) {

          // ajax.js adds an enclosing DIV to work around a Safari bug.
          $newDom = new \DOMDocument();

          // DOM can load HTML soup. But, HTML soup can throw warnings,
          // suppress them.
          @$newDom
            ->loadHTML('<div>' . $command['data'] . '</div>');

          // Suppress warnings thrown when duplicate HTML IDs are encountered.
          // This probably means we are replacing an element with the same ID.
          $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;
          }
        }
        break;

      // @todo Add suitable implementations for these commands in order to
      //   have full test coverage of what ajax.js can do.
      case 'remove':
        break;
      case 'changed':
        break;
      case 'css':
        break;
      case 'data':
        break;
      case 'restripe':
        break;
      case 'add_css':
        break;
      case 'update_build_id':
        $buildId = $xpath
          ->query('//input[@name="form_build_id" and @value="' . $command['old'] . '"]')
          ->item(0);
        if ($buildId) {
          $buildId
            ->setAttribute('value', $command['new']);
        }
        break;
    }
  }
  $content = $dom
    ->saveHTML();
  $this
    ->setRawContent($content);
  $this
    ->setDrupalSettings($drupal_settings);
}