Commit 5f0f49d7 authored by lauriii's avatar lauriii

Issue #736066 by droplet, drpal, vaplas, dmsmidt, effulgentsia, nod_, tic2000,...

Issue #736066 by droplet, drpal, vaplas, dmsmidt, effulgentsia, nod_, tic2000, tim.plunkett, lauriii, casey, morsok, idebr, alexpott, yched, tedbow, GuyPaddock, drintios, edurenye, dtamajon, adinac, VinayLondhe, ajalan065, hrmoller, ericduran, piyuesh23, andreyks, roborew, Wim Leers, lokapujya, samuel.mortenson, Dries, DuaelFr, OnkelTem, manu manu, khiminrm, Greg Boggs, SKAUGHT, itsekhmistro, fubhy, SpadXIII, s.messaris, zviryatko: ajax.js insert command sometimes wraps content in a div, potentially producing invalid HTML and other bugs
parent 27740c26
......@@ -1010,6 +1010,60 @@
throw new Drupal.AjaxError(xmlhttprequest, uri, customMessage);
};
/**
* Provide a wrapper for new content via Ajax.
*
* Wrap the inserted markup when inserting multiple root elements with an
* ajax effect.
*
* @param {jQuery} $newContent
* Response elements after parsing.
* @param {Drupal.Ajax} ajax
* {@link Drupal.Ajax} object created by {@link Drupal.ajax}.
* @param {object} response
* The response from the Ajax request.
*
* @deprecated in Drupal 8.6.x and will be removed before Drupal 9.0.0.
* Use data with desired wrapper. See https://www.drupal.org/node/2974880.
*
* @todo Add deprecation warning after it is possible. For more information
* see: https://www.drupal.org/project/drupal/issues/2973400
*
* @see https://www.drupal.org/node/2940704
*/
Drupal.theme.ajaxWrapperNewContent = ($newContent, ajax, response) => (
(response.effect || ajax.effect) !== 'none' &&
$newContent.filter(
i =>
!(
// We can not consider HTML comments or whitespace text as separate
// roots, since they do not cause visual regression with effect.
$newContent[i].nodeName === '#comment' ||
($newContent[i].nodeName === '#text' && /^(\s|\n|\r)*$/.test($newContent[i].textContent))
),
).length > 1
? Drupal.theme('ajaxWrapperMultipleRootElements', $newContent)
: $newContent
);
/**
* Provide a wrapper for multiple root elements via Ajax.
*
* @param {jQuery} $elements
* Response elements after parsing.
*
* @deprecated in Drupal 8.6.x and will be removed before Drupal 9.0.0.
* Use data with desired wrapper. See https://www.drupal.org/node/2974880.
*
* @todo Add deprecation warning after it is possible. For more information
* see: https://www.drupal.org/project/drupal/issues/2973400
*
* @see https://www.drupal.org/node/2940704
*/
Drupal.theme.ajaxWrapperMultipleRootElements = $elements => (
$('<div></div>').append($elements)
);
/**
* @typedef {object} Drupal.AjaxCommands~commandDefinition
*
......@@ -1056,39 +1110,24 @@
* A optional jQuery selector string.
* @param {object} [response.settings]
* An optional array of settings that will be used.
* @param {number} [status]
* The XMLHttpRequest status.
*/
insert(ajax, response, status) {
insert(ajax, response) {
// Get information from the response. If it is not there, default to
// our presets.
const $wrapper = response.selector ? $(response.selector) : $(ajax.wrapper);
const method = response.method || ajax.method;
const effect = ajax.getEffect(response);
let settings;
// We don't know what response.data contains: it might be a string of text
// without HTML, so don't rely on jQuery correctly interpreting
// $(response.data) as new HTML rather than a CSS selector. Also, if
// response.data contains top-level text nodes, they get lost with either
// $(response.data) or $('<div></div>').replaceWith(response.data).
const $newContentWrapped = $('<div></div>').html(response.data);
let $newContent = $newContentWrapped.contents();
// For legacy reasons, the effects processing code assumes that
// $newContent consists of a single top-level element. Also, it has not
// been sufficiently tested whether attachBehaviors() can be successfully
// called with a context object that includes top-level text nodes.
// However, to give developers full control of the HTML appearing in the
// page, and to enable Ajax content to be inserted in places where <div>
// elements are not allowed (e.g., within <table>, <tr>, and <span>
// parents), we check if the new content satisfies the requirement
// of a single top-level element, and only use the container <div> created
// above when it doesn't. For more information, please see
// https://www.drupal.org/node/736066.
if ($newContent.length !== 1 || $newContent.get(0).nodeType !== 1) {
$newContent = $newContentWrapped;
}
// Apply any settings from the returned JSON if available.
const settings = response.settings || ajax.settings || drupalSettings;
// Parse response.data into an element collection.
let $newContent = $($.parseHTML(response.data, document, true));
// For backward compatibility, in some cases a wrapper will be added. This
// behavior will be removed before Drupal 9.0.0. If different behavior is
// needed, the theme functions can be overriden.
// @see https://www.drupal.org/node/2940704
$newContent = Drupal.theme('ajaxWrapperNewContent', $newContent, ajax, response);
// If removing content from the wrapper, detach behaviors first.
switch (method) {
......@@ -1097,8 +1136,10 @@
case 'replaceAll':
case 'empty':
case 'remove':
settings = response.settings || ajax.settings || drupalSettings;
Drupal.detachBehaviors($wrapper.get(0), settings);
break;
default:
break;
}
// Add the new content to the page.
......@@ -1111,10 +1152,11 @@
// Determine which effect to use and what content will receive the
// effect, then show the new content.
if ($newContent.find('.ajax-new-content').length > 0) {
$newContent.find('.ajax-new-content').hide();
const $ajaxNewContent = $newContent.find('.ajax-new-content');
if ($ajaxNewContent.length) {
$ajaxNewContent.hide();
$newContent.show();
$newContent.find('.ajax-new-content')[effect.showEffect](effect.showSpeed);
$ajaxNewContent[effect.showEffect](effect.showSpeed);
}
else if (effect.showEffect !== 'show') {
$newContent[effect.showEffect](effect.showSpeed);
......@@ -1123,10 +1165,13 @@
// Attach all JavaScript behaviors to the new content, if it was
// successfully added to the page, this if statement allows
// `#ajax['wrapper']` to be optional.
if ($newContent.parents('html').length > 0) {
// Apply any settings from the returned JSON if available.
settings = response.settings || ajax.settings || drupalSettings;
Drupal.attachBehaviors($newContent.get(0), settings);
if ($newContent.parents('html').length) {
// Attach behaviors to all element nodes.
$newContent.each((index, element) => {
if (element.nodeType === Node.ELEMENT_NODE) {
Drupal.attachBehaviors(element, settings);
}
});
}
},
......
......@@ -490,20 +490,28 @@ function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr
throw new Drupal.AjaxError(xmlhttprequest, uri, customMessage);
};
Drupal.theme.ajaxWrapperNewContent = function ($newContent, ajax, response) {
return (response.effect || ajax.effect) !== 'none' && $newContent.filter(function (i) {
return !($newContent[i].nodeName === '#comment' || $newContent[i].nodeName === '#text' && /^(\s|\n|\r)*$/.test($newContent[i].textContent));
}).length > 1 ? Drupal.theme('ajaxWrapperMultipleRootElements', $newContent) : $newContent;
};
Drupal.theme.ajaxWrapperMultipleRootElements = function ($elements) {
return $('<div></div>').append($elements);
};
Drupal.AjaxCommands = function () {};
Drupal.AjaxCommands.prototype = {
insert: function insert(ajax, response, status) {
insert: function insert(ajax, response) {
var $wrapper = response.selector ? $(response.selector) : $(ajax.wrapper);
var method = response.method || ajax.method;
var effect = ajax.getEffect(response);
var settings = void 0;
var $newContentWrapped = $('<div></div>').html(response.data);
var $newContent = $newContentWrapped.contents();
var settings = response.settings || ajax.settings || drupalSettings;
if ($newContent.length !== 1 || $newContent.get(0).nodeType !== 1) {
$newContent = $newContentWrapped;
}
var $newContent = $($.parseHTML(response.data, document, true));
$newContent = Drupal.theme('ajaxWrapperNewContent', $newContent, ajax, response);
switch (method) {
case 'html':
......@@ -511,8 +519,10 @@ function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr
case 'replaceAll':
case 'empty':
case 'remove':
settings = response.settings || ajax.settings || drupalSettings;
Drupal.detachBehaviors($wrapper.get(0), settings);
break;
default:
break;
}
$wrapper[method]($newContent);
......@@ -521,17 +531,21 @@ function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr
$newContent.hide();
}
if ($newContent.find('.ajax-new-content').length > 0) {
$newContent.find('.ajax-new-content').hide();
var $ajaxNewContent = $newContent.find('.ajax-new-content');
if ($ajaxNewContent.length) {
$ajaxNewContent.hide();
$newContent.show();
$newContent.find('.ajax-new-content')[effect.showEffect](effect.showSpeed);
$ajaxNewContent[effect.showEffect](effect.showSpeed);
} else if (effect.showEffect !== 'show') {
$newContent[effect.showEffect](effect.showSpeed);
}
if ($newContent.parents('html').length > 0) {
settings = response.settings || ajax.settings || drupalSettings;
Drupal.attachBehaviors($newContent.get(0), settings);
if ($newContent.parents('html').length) {
$newContent.each(function (index, element) {
if (element.nodeType === Node.ELEMENT_NODE) {
Drupal.attachBehaviors(element, settings);
}
});
}
},
remove: function remove(ajax, response, status) {
......
......@@ -27,7 +27,8 @@ public function selectCallback($form, FormStateInterface $form_state) {
*/
public function dateCallback($form, FormStateInterface $form_state) {
$response = new AjaxResponse();
$response->addCommand(new HtmlCommand('#ajax_date_value', $form_state->getValue('date')));
$date = $form_state->getValue('date');
$response->addCommand(new HtmlCommand('#ajax_date_value', sprintf('<div>%s</div>', $date)));
$response->addCommand(new DataCommand('#ajax_date_value', 'form_state_value_date', $form_state->getValue('date')));
return $response;
}
......@@ -39,7 +40,7 @@ public function datetimeCallback($form, FormStateInterface $form_state) {
$datetime = $form_state->getValue('datetime')['date'] . ' ' . $form_state->getValue('datetime')['time'];
$response = new AjaxResponse();
$response->addCommand(new HtmlCommand('#ajax_datetime_value', $datetime));
$response->addCommand(new HtmlCommand('#ajax_datetime_value', sprintf('<div>%s</div>', $datetime)));
$response->addCommand(new DataCommand('#ajax_datetime_value', 'form_state_value_datetime', $datetime));
return $response;
}
......
ajax_insert:
js:
js/insert-ajax.js: {}
dependencies:
- core/drupal.ajax
order:
drupalSettings:
ajax: test
......
......@@ -6,6 +6,14 @@ ajax_test.dialog_contents:
requirements:
_access: 'TRUE'
ajax_test.ajax_render_types:
path: '/ajax-test/dialog-contents-types/{type}'
defaults:
_title: 'AJAX Dialog contents routing'
_controller: '\Drupal\ajax_test\Controller\AjaxTestController::renderTypes'
requirements:
_access: 'TRUE'
ajax_test.dialog_form:
path: '/ajax-test/dialog-form'
defaults:
......@@ -21,6 +29,20 @@ ajax_test.dialog:
requirements:
_access: 'TRUE'
ajax_test.insert_links_block_wrapper:
path: '/ajax-test/insert-block-wrapper'
defaults:
_controller: '\Drupal\ajax_test\Controller\AjaxTestController::insertLinksBlockWrapper'
requirements:
_access: 'TRUE'
ajax_test.insert_links_inline_wrapper:
path: '/ajax-test/insert-inline-wrapper'
defaults:
_controller: '\Drupal\ajax_test\Controller\AjaxTestController::insertLinksInlineWrapper'
requirements:
_access: 'TRUE'
ajax_test.dialog_close:
path: '/ajax-test/dialog-close'
defaults:
......
/**
* @file
* Drupal behavior to attach click event handlers to ajax-insert and
* ajax-insert-inline links for testing ajax requests.
*/
(function ($, window, Drupal) {
Drupal.behaviors.insertTest = {
attach(context) {
$('.ajax-insert').once('ajax-insert').on('click', (event) => {
event.preventDefault();
const ajaxSettings = {
url: event.currentTarget.getAttribute('href'),
wrapper: 'ajax-target',
base: false,
element: false,
method: event.currentTarget.getAttribute('data-method'),
effect: event.currentTarget.getAttribute('data-effect'),
};
const myAjaxObject = Drupal.ajax(ajaxSettings);
myAjaxObject.execute();
});
$('.ajax-insert-inline').once('ajax-insert').on('click', (event) => {
event.preventDefault();
const ajaxSettings = {
url: event.currentTarget.getAttribute('href'),
wrapper: 'ajax-target-inline',
base: false,
element: false,
method: event.currentTarget.getAttribute('data-method'),
effect: event.currentTarget.getAttribute('data-effect'),
};
const myAjaxObject = Drupal.ajax(ajaxSettings);
myAjaxObject.execute();
});
$(context).addClass('processed');
},
};
}(jQuery, window, Drupal));
/**
* DO NOT EDIT THIS FILE.
* See the following change record for more information,
* https://www.drupal.org/node/2815083
* @preserve
**/
(function ($, window, Drupal) {
Drupal.behaviors.insertTest = {
attach: function attach(context) {
$('.ajax-insert').once('ajax-insert').on('click', function (event) {
event.preventDefault();
var ajaxSettings = {
url: event.currentTarget.getAttribute('href'),
wrapper: 'ajax-target',
base: false,
element: false,
method: event.currentTarget.getAttribute('data-method'),
effect: event.currentTarget.getAttribute('data-effect')
};
var myAjaxObject = Drupal.ajax(ajaxSettings);
myAjaxObject.execute();
});
$('.ajax-insert-inline').once('ajax-insert').on('click', function (event) {
event.preventDefault();
var ajaxSettings = {
url: event.currentTarget.getAttribute('href'),
wrapper: 'ajax-target-inline',
base: false,
element: false,
method: event.currentTarget.getAttribute('data-method'),
effect: event.currentTarget.getAttribute('data-effect')
};
var myAjaxObject = Drupal.ajax(ajaxSettings);
myAjaxObject.execute();
});
$(context).addClass('processed');
}
};
})(jQuery, window, Drupal);
\ No newline at end of file
......@@ -42,6 +42,101 @@ public static function dialogContents() {
return $content;
}
/**
* Example content for testing the wrapper of the response.
*
* @param string $type
* Type of response.
*
* @return array
* Renderable array of AJAX response contents.
*/
public function renderTypes($type) {
return [
'#title' => '<em>AJAX Dialog & contents</em>',
'content' => [
'#type' => 'inline_template',
'#template' => $this->getRenderTypes()[$type]['render'],
],
];
}
/**
* Returns a render array of links that directly Drupal.ajax().
*
* @return array
* Renderable array of AJAX response contents.
*/
public function insertLinksBlockWrapper() {
$methods = [
'html',
'replaceWith',
];
$build['links'] = [
'ajax_target' => [
'#markup' => '<div class="ajax-target-wrapper"><div id="ajax-target">Target</div></div>',
],
'links' => [
'#theme' => 'links',
'#attached' => ['library' => ['ajax_test/ajax_insert']],
],
];
foreach ($methods as $method) {
foreach ($this->getRenderTypes() as $type => $item) {
$class = 'ajax-insert';
$build['links']['links']['#links']["$method-$type"] = [
'title' => "Link $method $type",
'url' => Url::fromRoute('ajax_test.ajax_render_types', ['type' => $type]),
'attributes' => [
'class' => [$class],
'data-method' => $method,
'data-effect' => $item['effect'],
],
];
}
}
return $build;
}
/**
* Returns a render array of links that directly Drupal.ajax().
*
* @return array
* Renderable array of AJAX response contents.
*/
public function insertLinksInlineWrapper() {
$methods = [
'html',
'replaceWith',
];
$build['links'] = [
'ajax_target' => [
'#markup' => '<div class="ajax-target-wrapper"><span id="ajax-target-inline">Target inline</span></div>',
],
'links' => [
'#theme' => 'links',
'#attached' => ['library' => ['ajax_test/ajax_insert']],
],
];
foreach ($methods as $method) {
foreach ($this->getRenderTypes() as $type => $item) {
$class = 'ajax-insert-inline';
$build['links']['links']['#links']["$method-$type"] = [
'title' => "Link $method $type",
'url' => Url::fromRoute('ajax_test.ajax_render_types', ['type' => $type]),
'attributes' => [
'class' => [$class],
'data-method' => $method,
'data-effect' => $item['effect'],
],
];
}
}
return $build;
}
/**
* Returns a render array that will be rendered by AjaxRenderer.
*
......@@ -222,4 +317,41 @@ public function dialogClose() {
return $response;
}
/**
* Render types.
*
* @return array
* Render types.
*/
protected function getRenderTypes() {
$render_single_root = [
'pre-wrapped-div' => '<div class="pre-wrapped">pre-wrapped<script> var test;</script></div>',
'pre-wrapped-span' => '<span class="pre-wrapped">pre-wrapped<script> var test;</script></span>',
'pre-wrapped-whitespace' => ' <div class="pre-wrapped-whitespace">pre-wrapped-whitespace</div>' . "\r\n",
'not-wrapped' => 'not-wrapped',
'comment-string-not-wrapped' => '<!-- COMMENT -->comment-string-not-wrapped',
'comment-not-wrapped' => '<!-- COMMENT --><div class="comment-not-wrapped">comment-not-wrapped</div>',
'svg' => '<svg xmlns="http://www.w3.org/2000/svg" width="10" height="10"><rect x="0" y="0" height="10" width="10" fill="green"/></svg>',
'empty' => '',
];
$render_multiple_root = [
'mixed' => ' foo <!-- COMMENT --> foo bar<div class="a class"><p>some string</p></div> additional not wrapped strings, <!-- ANOTHER COMMENT --> <p>final string</p>',
'top-level-only' => '<div>element #1</div><div>element #2</div>',
'top-level-only-pre-whitespace' => ' <div>element #1</div><div>element #2</div> ',
'top-level-only-middle-whitespace-span' => '<span>element #1</span> <span>element #2</span>',
'top-level-only-middle-whitespace-div' => '<div>element #1</div> <div>element #2</div>',
];
$render_info = [];
foreach ($render_single_root as $key => $render) {
$render_info[$key] = ['render' => $render, 'effect' => 'fade'];
}
foreach ($render_multiple_root as $key => $render) {
$render_info[$key] = ['render' => $render, 'effect' => 'none'];
$render_info["$key--effect"] = ['render' => $render, 'effect' => 'fade'];
}
return $render_info;
}
}
......@@ -54,8 +54,8 @@ public function testSimpleAJAXFormValue() {
// Wait for the DOM to update. The HtmlCommand will update
// #ajax_selected_color to reflect the color change.
$green_div = $this->assertSession()->waitForElement('css', "#ajax_selected_color div:contains('green')");
$this->assertNotNull($green_div, 'DOM update: The selected color DIV is green.');
$green_span = $this->assertSession()->waitForElement('css', "#ajax_selected_color:contains('green')");
$this->assertNotNull($green_span, 'DOM update: The selected color SPAN is green.');
// Confirm the operation of the UpdateBuildIdCommand.
$build_id_first_ajax = $this->getFormBuildId();
......@@ -66,8 +66,8 @@ public function testSimpleAJAXFormValue() {
$session->getPage()->selectFieldOption('select', 'red');
// Wait for the DOM to update.
$red_div = $this->assertSession()->waitForElement('css', "#ajax_selected_color div:contains('red')");
$this->assertNotNull($red_div, 'DOM update: The selected color DIV is red.');
$red_span = $this->assertSession()->waitForElement('css', "#ajax_selected_color:contains('red')");
$this->assertNotNull($red_span, 'DOM update: The selected color SPAN is red.');
// Confirm the operation of the UpdateBuildIdCommand.
$build_id_second_ajax = $this->getFormBuildId();
......@@ -84,8 +84,8 @@ public function testSimpleAJAXFormValue() {
$session->getPage()->selectFieldOption('select', 'green');
// Wait for the DOM to update.
$green_div2 = $this->assertSession()->waitForElement('css', "#ajax_selected_color div:contains('green')");
$this->assertNotNull($green_div2, 'DOM update: After reload - the selected color DIV is green.');
$green_span2 = $this->assertSession()->waitForElement('css', "#ajax_selected_color:contains('green')");
$this->assertNotNull($green_span2, 'DOM update: After reload - the selected color SPAN is green.');
$build_id_from_cache_first_ajax = $this->getFormBuildId();
$this->assertNotEquals($build_id_from_cache_initial, $build_id_from_cache_first_ajax, 'Build id is changed in the simpletest-DOM on first AJAX submission');
......@@ -96,8 +96,8 @@ public function testSimpleAJAXFormValue() {
$session->getPage()->selectFieldOption('select', 'red');
// Wait for the DOM to update.
$red_div2 = $this->assertSession()->waitForElement('css', "#ajax_selected_color div:contains('red')");
$this->assertNotNull($red_div2, 'DOM update: After reload - the selected color DIV is red.');
$red_span2 = $this->assertSession()->waitForElement('css', "#ajax_selected_color:contains('red')");
$this->assertNotNull($red_span2, 'DOM update: After reload - the selected color SPAN is red.');
$build_id_from_cache_second_ajax = $this->getFormBuildId();
$this->assertNotEquals($build_id_from_cache_first_ajax, $build_id_from_cache_second_ajax, 'Build id changes on subsequent AJAX submissions');
......
......@@ -82,4 +82,119 @@ public function testDrupalSettingsCachingRegression() {
$this->assertNotContains($fake_library, $libraries);
}
/**
* Tests that various AJAX responses with DOM elements are correctly inserted.
*
* After inserting DOM elements, Drupal JavaScript behaviors should be
* reattached and all top-level elements of type Node.ELEMENT_NODE need to be
* part of the context.
*/
public function testInsertAjaxResponse() {
$render_single_root = [
'pre-wrapped-div' => '<div class="pre-wrapped">pre-wrapped<script> var test;</script></div>',
'pre-wrapped-span' => '<span class="pre-wrapped">pre-wrapped<script> var test;</script></span>',
'pre-wrapped-whitespace' => ' <div class="pre-wrapped-whitespace">pre-wrapped-whitespace</div>' . "\n",
'not-wrapped' => 'not-wrapped',
'comment-string-not-wrapped' => '<!-- COMMENT -->comment-string-not-wrapped',
'comment-not-wrapped' => '<!-- COMMENT --><div class="comment-not-wrapped">comment-not-wrapped</div>',
'svg' => '<svg xmlns="http://www.w3.org/2000/svg" width="10" height="10"><rect x="0" y="0" height="10" width="10" fill="green"/></svg>',
'empty' => '',
];
$render_multiple_root_unwrapper = [
'mixed' => ' foo <!-- COMMENT --> foo bar<div class="a class"><p>some string</p></div> additional not wrapped strings, <!-- ANOTHER COMMENT --> <p>final string</p>',
'top-level-only' => '<div>element #1</div><div>element #2</div>',
'top-level-only-pre-whitespace' => ' <div>element #1</div><div>element #2</div> ',
'top-level-only-middle-whitespace-span' => '<span>element #1</span> <span>element #2</span>',
'top-level-only-middle-whitespace-div' => '<div>element #1</div> <div>element #2</div>',
];
// This is temporary behavior for BC reason.
$render_multiple_root_wrapper = [];
foreach ($render_multiple_root_unwrapper as $key => $render) {
$render_multiple_root_wrapper["$key--effect"] = '<div>' . $render . '</div>';
}
$expected_renders = array_merge(
$render_single_root,
$render_multiple_root_wrapper,
$render_multiple_root_unwrapper
);
// Checking default process of wrapping Ajax content.
foreach ($expected_renders as $render_type => $expected) {
$this->assertInsert($render_type, $expected);
}
// Checking custom ajaxWrapperMultipleRootElements wrapping.
$custom_wrapper_multiple_root = <<<JS
(function($, Drupal){
Drupal.theme.ajaxWrapperMultipleRootElements = function (elements) {
return $('<div class="my-favorite-div"></div>').append(elements);
};
}(jQuery, Drupal));
JS;
$expected = '<div class="my-favorite-div"><span>element #1</span> <span>element #2</span></div>';
$this->assertInsert('top-level-only-middle-whitespace-span--effect', $expected, $custom_wrapper_multiple_root);
// Checking custom ajaxWrapperNewContent wrapping.
$custom_wrapper_new_content = <<<JS
(function($, Drupal){
Drupal.theme.ajaxWrapperNewContent = function (elements) {
return $('<div class="div-wrapper-forever"></div>').append(elements);
};
}(jQuery, Drupal));
JS;
$expected = '<div class="div-wrapper-forever"></div>';
$this->assertInsert('empty', $expected, $custom_wrapper_new_content);
}
/**
* Assert insert.
*
* @param string $render_type
* Render type.
* @param string $expected
* Expected result.
* @param string $script
* Script for additional theming.
*/
public function assertInsert($render_type, $expected, $script = '') {
// Check insert to block element.
$this->drupalGet('ajax-test/insert-block-wrapper');
$this->getSession()->executeScript($script);
$this->clickLink("Link html $render_type");
$this->assertWaitPageContains('<div class="ajax-target-wrapper"><div id="ajax-target">' . $expected . '</div></div>');
$this->drupalGet('ajax-test/insert-block-wrapper');
$this->getSession()->executeScript($script);
$this->clickLink("Link replaceWith $render_type");
$this->assertWaitPageContains('<div class="ajax-target-wrapper">' . $expected . '</div>');
// Check insert to inline element.
$this->drupalGet('ajax-test/insert-inline-wrapper');
$this->getSession()->executeScript($script);
$this->clickLink("Link html $render_type");
$this->assertWaitPageContains('<div class="ajax-target-wrapper"><span id="ajax-target-inline">' . $expected . '</span></div>');
$this->drupalGet('ajax-test/insert-inline-wrapper');
$this->getSession()->executeScript($script);
$this->clickLink("Link replaceWith $render_type");
$this->assertWaitPageContains('<div class="ajax-target-wrapper">' . $expected . '</div>');
}
/**
* Asserts that page contains an expected value after waiting.
*
* @param string $expected
* A needle text.
*/
protected function assertWaitPageContains($expected) {
$page = $this->getSession()->getPage();
$this->assertTrue($page->waitFor(10, function () use ($page, $expected) {
// Clear content from empty styles and "processed" classes after effect.
$content = str_replace([' class="processed"', ' processed', ' style=""'], '', $page->getContent());
return stripos($content, $expected) !== FALSE;
}), "Page contains expected value: $expected");
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.