diff --git a/core/.eslintrc.json b/core/.eslintrc.json index 369539d27f..ed22445ab5 100644 --- a/core/.eslintrc.json +++ b/core/.eslintrc.json @@ -13,6 +13,7 @@ "domready": true, "jQuery": true, "_": true, + "loadjs": true, "matchMedia": true, "Backbone": true, "Modernizr": true, diff --git a/core/assets/vendor/loadjs/loadjs.min.js b/core/assets/vendor/loadjs/loadjs.min.js new file mode 100644 index 0000000000..bc398a6b08 --- /dev/null +++ b/core/assets/vendor/loadjs/loadjs.min.js @@ -0,0 +1,267 @@ +loadjs = (function () { + /** + * Global dependencies. + * @global {Object} document - DOM + */ + + var devnull = function() {}, + bundleIdCache = {}, + bundleResultCache = {}, + bundleCallbackQueue = {}; + + + /** + * Subscribe to bundle load event. + * @param {string[]} bundleIds - Bundle ids + * @param {Function} callbackFn - The callback function + */ + function subscribe(bundleIds, callbackFn) { + // listify + bundleIds = bundleIds.push ? bundleIds : [bundleIds]; + + var depsNotFound = [], + i = bundleIds.length, + numWaiting = i, + fn, + bundleId, + r, + q; + + // define callback function + fn = function (bundleId, pathsNotFound) { + if (pathsNotFound.length) depsNotFound.push(bundleId); + + numWaiting--; + if (!numWaiting) callbackFn(depsNotFound); + }; + + // register callback + while (i--) { + bundleId = bundleIds[i]; + + // execute callback if in result cache + r = bundleResultCache[bundleId]; + if (r) { + fn(bundleId, r); + continue; + } + + // add to callback queue + q = bundleCallbackQueue[bundleId] = bundleCallbackQueue[bundleId] || []; + q.push(fn); + } + } + + + /** + * Publish bundle load event. + * @param {string} bundleId - Bundle id + * @param {string[]} pathsNotFound - List of files not found + */ + function publish(bundleId, pathsNotFound) { + // exit if id isn't defined + if (!bundleId) return; + + var q = bundleCallbackQueue[bundleId]; + + // cache result + bundleResultCache[bundleId] = pathsNotFound; + + // exit if queue is empty + if (!q) return; + + // empty callback queue + while (q.length) { + q[0](bundleId, pathsNotFound); + q.splice(0, 1); + } + } + + + /** + * Load individual file. + * @param {string} path - The file path + * @param {Function} callbackFn - The callback function + */ + function loadFile(path, callbackFn, args, numTries) { + var doc = document, + async = args.async, + maxTries = (args.numRetries || 0) + 1, + beforeCallbackFn = args.before || devnull, + isCss, + e; + + numTries = numTries || 0; + + if (/(^css!|\.css$)/.test(path)) { + isCss = true; + + // css + e = doc.createElement('link'); + e.rel = 'stylesheet'; + e.href = path.replace(/^css!/, ''); // remove "css!" prefix + } else { + // javascript + e = doc.createElement('script'); + e.src = path; + e.async = async === undefined ? true : async; + } + + e.onload = e.onerror = e.onbeforeload = function (ev) { + var result = ev.type[0]; + + // Note: The following code isolates IE using `hideFocus` and treats empty + // stylesheets as failures to get around lack of onerror support + if (isCss && 'hideFocus' in e) { + try { + if (!e.sheet.cssText.length) result = 'e'; + } catch (x) { + // sheets objects created from load errors don't allow access to + // `cssText` + result = 'e'; + } + } + + // handle retries in case of load failure + if (result == 'e') { + // increment counter + numTries += 1; + + // exit function and try again + if (numTries < maxTries) { + return loadFile(path, callbackFn, args, numTries); + } + } + + // execute callback + callbackFn(path, result, ev.defaultPrevented); + }; + + // add to document (unless callback returns `false`) + if (beforeCallbackFn(path, e) !== false) doc.head.appendChild(e); + } + + + /** + * Load multiple files. + * @param {string[]} paths - The file paths + * @param {Function} callbackFn - The callback function + */ + function loadFiles(paths, callbackFn, args) { + // listify paths + paths = paths.push ? paths : [paths]; + + var numWaiting = paths.length, + x = numWaiting, + pathsNotFound = [], + fn, + i; + + // define callback function + fn = function(path, result, defaultPrevented) { + // handle error + if (result == 'e') pathsNotFound.push(path); + + // handle beforeload event. If defaultPrevented then that means the load + // will be blocked (ex. Ghostery/ABP on Safari) + if (result == 'b') { + if (defaultPrevented) pathsNotFound.push(path); + else return; + } + + numWaiting--; + if (!numWaiting) callbackFn(pathsNotFound); + }; + + // load scripts + for (i=0; i < x; i++) loadFile(paths[i], fn, args); + } + + + /** + * Initiate script load and register bundle. + * @param {(string|string[])} paths - The file paths + * @param {(string|Function)} [arg1] - The bundleId or success callback + * @param {Function} [arg2] - The success or error callback + * @param {Function} [arg3] - The error callback + */ + function loadjs(paths, arg1, arg2) { + var bundleId, + args; + + // bundleId (if string) + if (arg1 && arg1.trim) bundleId = arg1; + + // args (default is {}) + args = (bundleId ? arg2 : arg1) || {}; + + // throw error if bundle is already defined + if (bundleId) { + if (bundleId in bundleIdCache) { + throw "LoadJS"; + } else { + bundleIdCache[bundleId] = true; + } + } + + // load scripts + loadFiles(paths, function (pathsNotFound) { + // success and error callbacks + if (pathsNotFound.length) (args.error || devnull)(pathsNotFound); + else (args.success || devnull)(); + + // publish bundle load event + publish(bundleId, pathsNotFound); + }, args); + } + + + /** + * Execute callbacks when dependencies have been satisfied. + * @param {(string|string[])} deps - List of bundle ids + * @param {Object} args - success/error arguments + */ + loadjs.ready = function ready(deps, args) { + // subscribe to bundle load event + subscribe(deps, function (depsNotFound) { + // execute callbacks + if (depsNotFound.length) (args.error || devnull)(depsNotFound); + else (args.success || devnull)(); + }); + + return loadjs; + }; + + + /** + * Manually satisfy bundle dependencies. + * @param {string} bundleId - The bundle id + */ + loadjs.done = function done(bundleId) { + publish(bundleId, []); + }; + + + /** + * Reset loadjs dependencies statuses + */ + loadjs.reset = function reset() { + bundleIdCache = {}; + bundleResultCache = {}; + bundleCallbackQueue = {}; + }; + + + /** + * Determine if bundle has already been defined + * @param String} bundleId - The bundle id + */ + loadjs.isDefined = function isDefined(bundleId) { + return bundleId in bundleIdCache; + }; + + +// export + return loadjs; + +})(); diff --git a/core/core.libraries.yml b/core/core.libraries.yml index 10254fd2d9..fa04996533 100644 --- a/core/core.libraries.yml +++ b/core/core.libraries.yml @@ -95,6 +95,7 @@ drupal.ajax: - core/drupalSettings - core/drupal.progress - core/jquery.once + - core/loadjs drupal.announce: version: VERSION @@ -846,6 +847,16 @@ jquery.ui.widget: dependencies: - core/jquery.ui +loadjs: + remote: https://github.com/muicss/loadjs + version: 3.5.1 + license: + name: MIT + url: https://github.com/muicss/loadjs/blob/master/LICENSE.txt + gpl-compatible: true + js: + assets/vendor/loadjs/loadjs.min.js: { minified: true } + matchmedia: remote: https://github.com/paulirish/matchMedia.js version: &matchmedia_version 0.2.0 diff --git a/core/lib/Drupal/Core/Ajax/AddJsCommand.php b/core/lib/Drupal/Core/Ajax/AddJsCommand.php new file mode 100644 index 0000000000..5ca58fd7b0 --- /dev/null +++ b/core/lib/Drupal/Core/Ajax/AddJsCommand.php @@ -0,0 +1,69 @@ +selector = $selector; + $this->styles = $scripts; + $this->method = $method; + } + + /** + * {@inheritdoc} + */ + public function render() { + + return [ + 'command' => 'add_js', + 'selector' => $this->selector, + 'data' => $this->styles, + 'method' => $this->method, + ]; + } + +} diff --git a/core/lib/Drupal/Core/Ajax/AjaxResponseAttachmentsProcessor.php b/core/lib/Drupal/Core/Ajax/AjaxResponseAttachmentsProcessor.php index ee5208b078..269e62ba9d 100644 --- a/core/lib/Drupal/Core/Ajax/AjaxResponseAttachmentsProcessor.php +++ b/core/lib/Drupal/Core/Ajax/AjaxResponseAttachmentsProcessor.php @@ -174,11 +174,13 @@ protected function buildAttachmentsCommands(AjaxResponse $response, Request $req } if ($js_assets_header) { $js_header_render_array = $this->jsCollectionRenderer->render($js_assets_header); - $resource_commands[] = new PrependCommand('head', $this->renderer->renderPlain($js_header_render_array)); + $scripts_attributes = array_map(function ($render_array) { return $render_array['#attributes']; }, $js_header_render_array); + $resource_commands[] = new AddJsCommand('head', $scripts_attributes, 'insertBefore'); } if ($js_assets_footer) { $js_footer_render_array = $this->jsCollectionRenderer->render($js_assets_footer); - $resource_commands[] = new AppendCommand('body', $this->renderer->renderPlain($js_footer_render_array)); + $scripts_attributes = array_map(function ($render_array) { return $render_array['#attributes']; }, $js_footer_render_array); + $resource_commands[] = new AddJsCommand('body', $scripts_attributes, 'appendChild'); } foreach (array_reverse($resource_commands) as $resource_command) { $response->addCommand($resource_command, TRUE); diff --git a/core/misc/ajax.es6.js b/core/misc/ajax.es6.js index 6248e46efe..91410bf2aa 100644 --- a/core/misc/ajax.es6.js +++ b/core/misc/ajax.es6.js @@ -11,7 +11,8 @@ * included to provide Ajax capabilities. */ -(function ($, window, Drupal, drupalSettings) { +(function ($, window, Drupal, drupalSettings, loadjs) { + /** * Attaches the Ajax behavior to each Ajax form element. * @@ -349,6 +350,11 @@ $.extend(this, defaults, element_settings); + /** + * @type {null|Promise} + */ + this.ajaxDeferred = null; + /** * @type {Drupal.AjaxCommands} */ @@ -424,7 +430,6 @@ // Set the options for the ajaxSubmit function. // The 'this' variable will not persist inside of the options object. const ajax = this; - /** * Options for the jQuery.ajax function. * @@ -497,7 +502,16 @@ return ajax.success(response, status); }, complete(xmlhttprequest, status) { - ajax.ajaxing = false; + if ((ajax.ajaxDeferred !== null && ajax.ajaxDeferred.then !== null) && + (typeof ajax.ajaxDeferred === 'object' && typeof ajax.ajaxDeferred.then === 'function')) { + ajax.ajaxDeferred.then(() => { + ajax.ajaxing = false; + }); + } + else { + ajax.ajaxing = false; + } + if (status === 'error' || status === 'parsererror') { return ajax.error(xmlhttprequest, ajax.url); } @@ -839,6 +853,8 @@ * XMLHttpRequest status. */ Drupal.Ajax.prototype.success = function (response, status) { + this.ajaxDeferred = $.Deferred(); + // Remove the progress element. if (this.progress.element) { $(this.progress.element).remove(); @@ -857,14 +873,30 @@ // Track if any command is altering the focus so we can avoid changing the // focus set by the Ajax command. let focusChanged = false; - for (const i in response) { - if (response.hasOwnProperty(i) && response[i].command && this.commands[response[i].command]) { - this.commands[response[i].command](this, response[i], status); - if (response[i].command === 'invoke' && response[i].method === 'focus') { + const responseKeys = Object.keys(response); + responseKeys.reduce((deferredCommand, key, currentIndex) => deferredCommand.then(() => { + const command = response[key].command; + if (command && this.commands[command]) { + if (command === 'invoke' && response[key].method === 'focus') { focusChanged = true; } + + const result = this.commands[command](this, response[key], status); + if (typeof result === 'object' && typeof result.then === 'function') { + // handle a promise + result.done(() => { + if (currentIndex + 1 === responseKeys.length) { + this.ajaxDeferred.resolve(); + } + }); + return result; + } } - } + + if (currentIndex + 1 === responseKeys.length) { + this.ajaxDeferred.resolve(); + } + }), $.Deferred().resolve()); // If the focus hasn't be changed by the ajax commands, try to refocus the // triggering element or one of its parents if that element does not exist @@ -1337,5 +1369,44 @@ } while (match); } }, + add_js(ajax, response) { + const deferred = $.Deferred(); + const scriptsSrc = response.data.map((script) => { + // loadjs required unique ID + const uniqueBundleID = script.src + ajax.instanceIndex; + loadjs(script.src, uniqueBundleID, { + async: !!script.async, + before(path, scriptEl) { + let selector = 'body'; + if (response.selector) { + selector = response.selector; + } + if (script.defer) { + scriptEl.defer = true; + } + /** + * To avoid synchronous XMLHttpRequest on the main thread and break + * load dependency, it should not use jQuery. + */ + const parentEl = document.querySelector(selector); + if (response.method === 'insertBefore') { + parentEl.insertBefore(scriptEl, parentEl.firstChild); + } + else { + parentEl[response.method](scriptEl); + } + // return `false` to bypass loadjs default DOM insertion mechanism + return false; + }, + }); + return uniqueBundleID; + }); + loadjs.ready(scriptsSrc, { + success() { + deferred.resolve(); + }, + }); + return deferred.promise(); + }, }; -}(jQuery, window, Drupal, drupalSettings)); +})(jQuery, window, Drupal, drupalSettings, loadjs); diff --git a/core/misc/ajax.js b/core/misc/ajax.js index 5ea52425be..c2f4b72241 100644 --- a/core/misc/ajax.js +++ b/core/misc/ajax.js @@ -4,9 +4,11 @@ * https://www.drupal.org/node/2815083 * @preserve **/ +var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; + function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } else { return Array.from(arr); } } -(function ($, window, Drupal, drupalSettings) { +(function ($, window, Drupal, drupalSettings, loadjs) { Drupal.behaviors.AJAX = { attach: function attach(context, settings) { function loadAjaxBehavior(base) { @@ -158,6 +160,8 @@ function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr $.extend(this, defaults, element_settings); + this.ajaxDeferred = null; + this.commands = new Drupal.AjaxCommands(); this.instanceIndex = false; @@ -222,7 +226,14 @@ function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr return ajax.success(response, status); }, complete: function complete(xmlhttprequest, status) { - ajax.ajaxing = false; + if (ajax.ajaxDeferred !== null && ajax.ajaxDeferred.then !== null && _typeof(ajax.ajaxDeferred) === 'object' && typeof ajax.ajaxDeferred.then === 'function') { + ajax.ajaxDeferred.then(function () { + ajax.ajaxing = false; + }); + } else { + ajax.ajaxing = false; + } + if (status === 'error' || status === 'parsererror') { return ajax.error(xmlhttprequest, ajax.url); } @@ -391,6 +402,10 @@ function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr }; Drupal.Ajax.prototype.success = function (response, status) { + var _this = this; + + this.ajaxDeferred = $.Deferred(); + if (this.progress.element) { $(this.progress.element).remove(); } @@ -402,14 +417,31 @@ function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr var elementParents = $(this.element).parents('[data-drupal-selector]').addBack().toArray(); var focusChanged = false; - for (var i in response) { - if (response.hasOwnProperty(i) && response[i].command && this.commands[response[i].command]) { - this.commands[response[i].command](this, response[i], status); - if (response[i].command === 'invoke' && response[i].method === 'focus') { - focusChanged = true; + var responseKeys = Object.keys(response); + responseKeys.reduce(function (deferredCommand, key, currentIndex) { + return deferredCommand.then(function () { + var command = response[key].command; + if (command && _this.commands[command]) { + if (command === 'invoke' && response[key].method === 'focus') { + focusChanged = true; + } + + var result = _this.commands[command](_this, response[key], status); + if ((typeof result === 'undefined' ? 'undefined' : _typeof(result)) === 'object' && typeof result.then === 'function') { + result.done(function () { + if (currentIndex + 1 === responseKeys.length) { + _this.ajaxDeferred.resolve(); + } + }); + return result; + } } - } - } + + if (currentIndex + 1 === responseKeys.length) { + _this.ajaxDeferred.resolve(); + } + }); + }, $.Deferred().resolve()); if (!focusChanged && this.element && !$(this.element).data('disable-refocus')) { var target = false; @@ -586,6 +618,40 @@ function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr document.styleSheets[0].addImport(match[1]); } while (match); } + }, + add_js: function add_js(ajax, response) { + var deferred = $.Deferred(); + var scriptsSrc = response.data.map(function (script) { + var uniqueBundleID = script.src + ajax.instanceIndex; + loadjs(script.src, uniqueBundleID, { + async: !!script.async, + before: function before(path, scriptEl) { + var selector = 'body'; + if (response.selector) { + selector = response.selector; + } + if (script.defer) { + scriptEl.defer = true; + } + + var parentEl = document.querySelector(selector); + if (response.method === 'insertBefore') { + parentEl.insertBefore(scriptEl, parentEl.firstChild); + } else { + parentEl[response.method](scriptEl); + } + + return false; + } + }); + return uniqueBundleID; + }); + loadjs.ready(scriptsSrc, { + success: function success() { + deferred.resolve(); + } + }); + return deferred.promise(); } }; -})(jQuery, window, Drupal, drupalSettings); \ No newline at end of file +})(jQuery, window, Drupal, drupalSettings, loadjs); \ No newline at end of file diff --git a/core/modules/big_pipe/tests/src/FunctionalJavascript/BigPipeRegressionTest.php b/core/modules/big_pipe/tests/src/FunctionalJavascript/BigPipeRegressionTest.php index b4da7b0a9b..653f32f8a5 100644 --- a/core/modules/big_pipe/tests/src/FunctionalJavascript/BigPipeRegressionTest.php +++ b/core/modules/big_pipe/tests/src/FunctionalJavascript/BigPipeRegressionTest.php @@ -105,10 +105,11 @@ public function testCommentForm_2698811() { $comment->save(); } $this->drupalGet($node->toUrl()->toString()); + $this->assertSession()->assertWaitOnAjaxRequest(); // Confirm that CKEditor loaded. $javascript = << 0; + return typeof CKEDITOR !== 'undefined' && typeof CKEDITOR.instances !== 'undefined' && Object.keys(CKEDITOR.instances).length > 0; }()); JS; $this->assertJsCondition($javascript); diff --git a/core/modules/quickedit/js/quickedit.es6.js b/core/modules/quickedit/js/quickedit.es6.js index 84fee16cdf..f44b1f98f0 100644 --- a/core/modules/quickedit/js/quickedit.es6.js +++ b/core/modules/quickedit/js/quickedit.es6.js @@ -518,8 +518,8 @@ }); // Implement a scoped insert AJAX command: calls the callback after all AJAX // command functions have been executed (hence the deferred calling). - const realInsert = Drupal.AjaxCommands.prototype.insert; - loadEditorsAjax.commands.insert = function (ajax, response, status) { + const realInsert = Drupal.AjaxCommands.prototype.add_js; + loadEditorsAjax.commands.add_js = function (ajax, response, status) { _.defer(callback); realInsert(ajax, response, status); }; diff --git a/core/modules/quickedit/js/quickedit.js b/core/modules/quickedit/js/quickedit.js index 5c2cd95af0..c057cb427b 100644 --- a/core/modules/quickedit/js/quickedit.js +++ b/core/modules/quickedit/js/quickedit.js @@ -263,8 +263,8 @@ submit: { 'editors[]': missingEditors } }); - var realInsert = Drupal.AjaxCommands.prototype.insert; - loadEditorsAjax.commands.insert = function (ajax, response, status) { + var realInsert = Drupal.AjaxCommands.prototype.add_js; + loadEditorsAjax.commands.add_js = function (ajax, response, status) { _.defer(callback); realInsert(ajax, response, status); }; diff --git a/core/modules/quickedit/src/Tests/QuickEditLoadingTest.php b/core/modules/quickedit/src/Tests/QuickEditLoadingTest.php index 41f26a841f..cc0ba0fde7 100644 --- a/core/modules/quickedit/src/Tests/QuickEditLoadingTest.php +++ b/core/modules/quickedit/src/Tests/QuickEditLoadingTest.php @@ -198,7 +198,7 @@ public function testUserWithPermission() { // First command: settings. $this->assertIdentical('settings', $ajax_commands[0]['command'], 'The first AJAX command is a settings command.'); // Second command: insert libraries into DOM. - $this->assertIdentical('insert', $ajax_commands[1]['command'], 'The second AJAX command is an append command.'); + $this->assertIdentical('add_js', $ajax_commands[1]['command'], 'The second AJAX command is an append command.'); $this->assertTrue(in_array('quickedit/quickedit.inPlaceEditor.form', explode(',', $ajax_commands[0]['settings']['ajaxPageState']['libraries'])), 'The quickedit.inPlaceEditor.form library is loaded.'); // Retrieving the form for this field should result in a 200 response, diff --git a/core/modules/system/src/Tests/Ajax/DialogTest.php b/core/modules/system/src/Tests/Ajax/DialogTest.php index ec947825fe..f9f4edfd9b 100644 --- a/core/modules/system/src/Tests/Ajax/DialogTest.php +++ b/core/modules/system/src/Tests/Ajax/DialogTest.php @@ -148,9 +148,9 @@ public function testDialog() { $this->assertTrue(in_array('core/drupal.dialog.ajax', explode(',', $ajax_result[0]['settings']['ajaxPageState']['libraries'])), 'core/drupal.dialog.ajax library is added to the page.'); $dialog_css_exists = strpos($ajax_result[1]['data'], 'dialog.css') !== FALSE; $this->assertTrue($dialog_css_exists, 'jQuery UI dialog CSS added to the page.'); - $dialog_js_exists = strpos($ajax_result[2]['data'], 'dialog-min.js') !== FALSE; + $dialog_js_exists = strpos(json_encode($ajax_result[2]['data']), 'dialog-min.js') !== FALSE; $this->assertTrue($dialog_js_exists, 'jQuery UI dialog JS added to the page.'); - $dialog_js_exists = strpos($ajax_result[2]['data'], 'dialog.ajax.js') !== FALSE; + $dialog_js_exists = strpos(json_encode($ajax_result[2]['data']), 'dialog.ajax.js') !== FALSE; $this->assertTrue($dialog_js_exists, 'Drupal dialog JS added to the page.'); // Check that the response matches the expected value. diff --git a/core/modules/system/src/Tests/Ajax/FrameworkTest.php b/core/modules/system/src/Tests/Ajax/FrameworkTest.php index 14bae06442..66821a3aa8 100644 --- a/core/modules/system/src/Tests/Ajax/FrameworkTest.php +++ b/core/modules/system/src/Tests/Ajax/FrameworkTest.php @@ -4,9 +4,8 @@ use Drupal\Core\Ajax\AddCssCommand; use Drupal\Core\Ajax\AlertCommand; -use Drupal\Core\Ajax\AppendCommand; +use Drupal\Core\Ajax\AddJsCommand; use Drupal\Core\Ajax\HtmlCommand; -use Drupal\Core\Ajax\PrependCommand; use Drupal\Core\Ajax\SettingsCommand; use Drupal\Core\Asset\AttachedAssets; @@ -48,8 +47,8 @@ public function testOrder() { list($js_assets_header, $js_assets_footer) = $asset_resolver->getJsAssets($assets, FALSE); $js_header_render_array = $js_collection_renderer->render($js_assets_header); $js_footer_render_array = $js_collection_renderer->render($js_assets_footer); - $expected_commands[2] = new PrependCommand('head', $js_header_render_array); - $expected_commands[3] = new AppendCommand('body', $js_footer_render_array); + $expected_commands[2] = new AddJsCommand('head', $js_header_render_array, 'insertBefore'); + $expected_commands[3] = new AddJsCommand('body', $js_footer_render_array); $expected_commands[4] = new HtmlCommand('body', 'Hello, world!'); // Load any page with at least one CSS file, at least one JavaScript file @@ -68,8 +67,8 @@ public function testOrder() { $commands = $this->drupalPostAjaxForm(NULL, [], NULL, 'ajax-test/order', [], [], NULL, []); $this->assertCommand(array_slice($commands, 0, 1), $expected_commands[0]->render(), 'Settings command is first.'); $this->assertCommand(array_slice($commands, 1, 1), $expected_commands[1]->render(), 'CSS command is second (and CSS files are ordered correctly).'); - $this->assertCommand(array_slice($commands, 2, 1), $expected_commands[2]->render(), 'Header JS command is third.'); - $this->assertCommand(array_slice($commands, 3, 1), $expected_commands[3]->render(), 'Footer JS command is fourth.'); + $this->assertEqual(array_slice($commands, 2, 1)[0]['data'][0]['src'], $expected_commands[2]->render()['data'][0]['#attributes']['src'], 'Header JS command is third.'); + $this->assertEqual(array_slice($commands, 3, 1)[0]['data'][0]['src'], $expected_commands[3]->render()['data'][0]['#attributes']['src'], 'Footer JS command is fourth.'); $this->assertCommand(array_slice($commands, 4, 1), $expected_commands[4]->render(), 'HTML command is fifth.'); } @@ -173,8 +172,8 @@ public function testLazyLoad() { // unexpected JavaScript code, such as a jQuery.extend() that would // potentially clobber rather than properly merge settings, didn't // accidentally get added. - $this->assertTrue(in_array($expected['library_2'], $new_libraries), format_string('Page state now has the %library library.', ['%library' => $expected['library_2']])); - $this->assertCommand(array_slice($commands, 2, 1), ['data' => $expected_js_html], format_string('Page now has the %library library.', ['%library' => $expected['library_2']])); + $system_js_exists = strpos($commands[2]['data'][0]['src'], 'modules/system/js/system.js') !== FALSE; + $this->assertTrue($system_js_exists, format_string('Page now has the %library library.', ['%library' => $expected['library_2']])); } /**