diff --git a/core/core.api.php b/core/core.api.php index 9c358a6e98..6f539ccbd8 100644 --- a/core/core.api.php +++ b/core/core.api.php @@ -413,7 +413,7 @@ * \Drupal\Core\Cache\CacheBackendInterface. * * The Cache API is used to store data that takes a long time to compute. - * Caching can either be permanent or valid only for a certain time span, and + * Caching can either be permanent or valid only for a certain timespan, and * the cache can contain any type of data. * * To use the Cache API: @@ -558,7 +558,7 @@ * This also is the case when you define your own entity types: you'll get the * exact same cache tag invalidation as any of the built-in entity types, with * the ability to override any of the default behavior if needed. - * See \Drupal\Core\Cache\CacheableDependencyInterface::getCacheTags(), + * See \Drupal\Core\Cache\CacheableDepenencyInterface::getCacheTags(), * \Drupal\Core\Entity\EntityTypeInterface::getListCacheTags(), * \Drupal\Core\Entity\Entity::invalidateTagsOnSave() and * \Drupal\Core\Entity\Entity::invalidateTagsOnDelete(). @@ -569,7 +569,7 @@ * logged-in user who is viewing a page, the language the page is being rendered * in, the theme being used, etc. When caching the output of such a calculation, * you must cache each variation separately, along with information about which - * variation of the contextual data was used in the calculation. The next time + * variation of the contextual data was used in the calculatation. The next time * the computed data is needed, if the context matches that for an existing * cached data set, the cached data can be reused; if no context matches, a new * data set can be calculated and cached for later use. @@ -2003,12 +2003,12 @@ function hook_data_type_info_alter(&$data_types) { * Alter cron queue information before cron runs. * * Called by \Drupal\Core\Cron to allow modules to alter cron queue settings - * before any jobs are processed. + * before any jobs are processesed. * * @param array $queues * An array of cron queue information. * - * @see \Drupal\Core\Queue\QueueWorkerInterface + * @see \Drupal\Core\QueueWorker\QueueWorkerInterface * @see \Drupal\Core\Annotation\QueueWorker * @see \Drupal\Core\Cron */ diff --git a/core/core.libraries.yml b/core/core.libraries.yml index 0b9d87b29c..5eb4a1fbbe 100644 --- a/core/core.libraries.yml +++ b/core/core.libraries.yml @@ -233,6 +233,14 @@ drupal.machine-name: - core/drupalSettings - core/drupal.form +drupal.message: + version: VERSION + js: + misc/message.js: {} + dependencies: + - core/drupal + - core/drupal.announce + drupal.progress: version: VERSION js: diff --git a/core/core.services.yml b/core/core.services.yml index ced39ccd59..b5cd022294 100644 --- a/core/core.services.yml +++ b/core/core.services.yml @@ -1618,10 +1618,7 @@ services: - { name: service_collector, tag: twig.loader, call: addLoader, required: TRUE } twig.loader.filesystem: class: Drupal\Core\Template\Loader\FilesystemLoader - # We use '.' instead of '@app.root' as the path for non-namespaced template - # files so that they match the relative paths of templates loaded via the - # theme registry or via Twig namespaces. - arguments: ['.', '@module_handler', '@theme_handler'] + arguments: ['@app.root', '@module_handler', '@theme_handler'] tags: - { name: twig.loader, priority: 100 } twig.loader.theme_registry: diff --git a/core/lib/Drupal/Core/Config/ConfigInstaller.php b/core/lib/Drupal/Core/Config/ConfigInstaller.php index 24d735b99d..d7788161cb 100644 --- a/core/lib/Drupal/Core/Config/ConfigInstaller.php +++ b/core/lib/Drupal/Core/Config/ConfigInstaller.php @@ -315,11 +315,10 @@ protected function createConfiguration($collection, array $config_to_create) { $entity_storage = $this->configManager ->getEntityManager() ->getStorage($entity_type); - - $id = $entity_storage->getIDFromConfigName($name, $entity_storage->getEntityType()->getConfigPrefix()); // It is possible that secondary writes can occur during configuration // creation. Updates of such configuration are allowed. if ($this->getActiveStorages($collection)->exists($name)) { + $id = $entity_storage->getIDFromConfigName($name, $entity_storage->getEntityType()->getConfigPrefix()); $entity = $entity_storage->load($id); $entity = $entity_storage->updateFromStorageRecord($entity, $new_config->get()); } @@ -328,9 +327,6 @@ protected function createConfiguration($collection, array $config_to_create) { } if ($entity->isInstallable()) { $entity->trustData()->save(); - if ($id !== $entity->id()) { - trigger_error(sprintf('The configuration name "%s" does not match the ID "%s"', $name, $entity->id()), E_USER_WARNING); - } } } else { diff --git a/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/EntityReferenceItem.php b/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/EntityReferenceItem.php index 5cbe365d14..e1b6c1bf5f 100644 --- a/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/EntityReferenceItem.php +++ b/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/EntityReferenceItem.php @@ -272,10 +272,6 @@ public function preSave() { * {@inheritdoc} */ public static function generateSampleValue(FieldDefinitionInterface $field_definition) { - // An associative array keyed by the reference type, target type, and - // bundle. - static $recursion_tracker = []; - $manager = \Drupal::service('plugin.manager.entity_reference_selection'); // Instead of calling $manager->getSelectionHandler($field_definition) @@ -304,25 +300,9 @@ public static function generateSampleValue(FieldDefinitionInterface $field_defin // Attempt to create a sample entity, avoiding recursion. $entity_storage = \Drupal::entityTypeManager()->getStorage($options['target_type']); - if ($entity_storage instanceof ContentEntityStorageInterface) { + if ($options['target_type'] !== $field_definition->getTargetEntityTypeId() && $entity_storage instanceof ContentEntityStorageInterface) { $bundle = static::getRandomBundle($entity_type, $options['handler_settings']); - - // Track the generated entity by reference type, target type, and bundle. - $key = $field_definition->getTargetEntityTypeId() . ':' . $options['target_type'] . ':' . $bundle; - - // If entity generation was attempted but did not finish, do not continue. - if (isset($recursion_tracker[$key])) { - return []; - } - - // Mark this as an attempt at generation. - $recursion_tracker[$key] = TRUE; - - // Mark the sample entity as being a preview. - $values['entity'] = $entity_storage->createWithSampleValues($bundle, ['in_preview' => TRUE]); - - // Remove the indicator once the entity is successfully generated. - unset($recursion_tracker[$key]); + $values['entity'] = $entity_storage->createWithSampleValues($bundle); return $values; } } diff --git a/core/lib/Drupal/Core/Render/Element/StatusMessages.php b/core/lib/Drupal/Core/Render/Element/StatusMessages.php index 578d28ffbe..a64d34c73a 100644 --- a/core/lib/Drupal/Core/Render/Element/StatusMessages.php +++ b/core/lib/Drupal/Core/Render/Element/StatusMessages.php @@ -84,7 +84,7 @@ public static function renderMessages($type = NULL) { if ($messages) { // Render the messages. - $render = [ + $render[] = [ '#theme' => 'status_messages', // @todo Improve when https://www.drupal.org/node/2278383 lands. '#message_list' => $messages, @@ -95,6 +95,10 @@ public static function renderMessages($type = NULL) { ], ]; } + $render[] = [ + '#markup' => '', + ]; + return $render; } diff --git a/core/lib/Drupal/Core/Render/Renderer.php b/core/lib/Drupal/Core/Render/Renderer.php index 1c36184430..5e1b8d1b8d 100644 --- a/core/lib/Drupal/Core/Render/Renderer.php +++ b/core/lib/Drupal/Core/Render/Renderer.php @@ -380,7 +380,7 @@ protected function doRender(&$elements, $is_root_call = FALSE) { } // All render elements support #markup and #plain_text. - if (isset($elements['#markup']) || isset($elements['#plain_text'])) { + if (!empty($elements['#markup']) || !empty($elements['#plain_text'])) { $elements = $this->ensureMarkupIsSafe($elements); } @@ -744,7 +744,11 @@ protected function xssFilterAdminIfUnsafe($string) { * @see \Drupal\Component\Utility\Xss::filterAdmin() */ protected function ensureMarkupIsSafe(array $elements) { - if (isset($elements['#plain_text'])) { + if (empty($elements['#markup']) && empty($elements['#plain_text'])) { + return $elements; + } + + if (!empty($elements['#plain_text'])) { $elements['#markup'] = Markup::create(Html::escape($elements['#plain_text'])); } elseif (!($elements['#markup'] instanceof MarkupInterface)) { diff --git a/core/lib/Drupal/Core/Routing/RequestFormatRouteFilter.php b/core/lib/Drupal/Core/Routing/RequestFormatRouteFilter.php index 8d14dd52cf..dbfb801b78 100644 --- a/core/lib/Drupal/Core/Routing/RequestFormatRouteFilter.php +++ b/core/lib/Drupal/Core/Routing/RequestFormatRouteFilter.php @@ -4,7 +4,6 @@ use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpKernel\Exception\NotAcceptableHttpException; -use Symfony\Component\Routing\Route; use Symfony\Component\Routing\RouteCollection; /** @@ -18,14 +17,7 @@ class RequestFormatRouteFilter implements FilterInterface { public function filter(RouteCollection $collection, Request $request) { // Determine the request format. $default_format = static::getDefaultFormat($collection); - // If the request does not specify a format then use the default. - if (is_null($request->getRequestFormat(NULL))) { - $format = $default_format; - $request->setRequestFormat($default_format); - } - else { - $format = $request->getRequestFormat($default_format); - } + $format = $request->getRequestFormat($default_format); $routes_with_requirement = []; $routes_without_requirement = []; @@ -68,9 +60,7 @@ public function filter(RouteCollection $collection, Request $request) { * * By default, use 'html' as the default format. But when there's only a * single route match, and that route specifies a '_format' requirement - * listing a single format, then use that as the default format. Also, if - * there are multiple routes which all require the same single format then - * use it. + * listing a single format, then use that as the default format. * * @param \Symfony\Component\Routing\RouteCollection $collection * The route collection to filter. @@ -79,20 +69,15 @@ public function filter(RouteCollection $collection, Request $request) { * The default format. */ protected static function getDefaultFormat(RouteCollection $collection) { - // Get the set of formats across all routes in the collection. - $all_formats = array_reduce($collection->all(), function (array $carry, Route $route) { - // Routes without a '_format' requirement are assumed to require HTML. - $route_formats = !$route->hasRequirement('_format') - ? ['html'] - : explode('|', $route->getRequirement('_format')); - return array_merge($carry, $route_formats); - }, []); - $formats = array_unique(array_filter($all_formats)); - - // The default format is 'html' unless ALL routes require the same format. - return count($formats) === 1 - ? reset($formats) - : 'html'; + $default_format = 'html'; + if ($collection->count() === 1) { + $only_route = $collection->getIterator()->current(); + $required_format = $only_route->getRequirement('_format'); + if (strpos($required_format, '|') === FALSE) { + $default_format = $required_format; + } + } + return $default_format; } } diff --git a/core/lib/Drupal/Core/StackMiddleware/NegotiationMiddleware.php b/core/lib/Drupal/Core/StackMiddleware/NegotiationMiddleware.php index f246cce462..e3864610f4 100644 --- a/core/lib/Drupal/Core/StackMiddleware/NegotiationMiddleware.php +++ b/core/lib/Drupal/Core/StackMiddleware/NegotiationMiddleware.php @@ -46,9 +46,7 @@ public function handle(Request $request, $type = self::MASTER_REQUEST, $catch = } // Determine the request format using the negotiator. - if ($requested_format = $this->getContentType($request)) { - $request->setRequestFormat($requested_format); - } + $request->setRequestFormat($this->getContentType($request)); return $this->app->handle($request, $type, $catch); } @@ -90,8 +88,8 @@ protected function getContentType(Request $request) { return $request->query->get('_format'); } - // No format was specified in the request. - return NULL; + // Do HTML last so that it always wins. + return 'html'; } } diff --git a/core/misc/dropbutton/dropbutton.es6.js b/core/misc/dropbutton/dropbutton.es6.js index e654673ec1..73ff363feb 100644 --- a/core/misc/dropbutton/dropbutton.es6.js +++ b/core/misc/dropbutton/dropbutton.es6.js @@ -214,7 +214,7 @@ * @param {object} options * Options object. * @param {string} [options.title] - * The button text. + * The HTML anchor title attribute and text for the inner span element. * * @return {string} * A string representing a DOM fragment. diff --git a/core/misc/message.es6.js b/core/misc/message.es6.js new file mode 100644 index 0000000000..26581ad853 --- /dev/null +++ b/core/misc/message.es6.js @@ -0,0 +1,240 @@ +/** + * @file + * Message API. + */ +((Drupal) => { + /** + * @typedef {object} Drupal.Message~messageDefinition + */ + + /** + * Constructs a new instance of the Drupal.Message object. + * + * This provides a uniform interface for adding and removing messages to a + * specific location on the page. + * + * @param {HTMLElement} messageWrapper + * The zone where to add messages. If no element is provided an attempt is + * made to determine a default location. + * + * @return {Drupal.Message~messageDefinition} + * Object to add and remove messages. + */ + + Drupal.Message = class { + constructor(messageWrapper = null) { + this.messageWrapper = messageWrapper; + } + + /** + * Attempt to determine the default location for + * inserting JavaScript messages or create one if needed. + * + * @return {HTMLElement} + * The default destination for JavaScript messages. + */ + static defaultWrapper() { + let wrapper = document.querySelector('[data-drupal-messages]'); + if (!wrapper) { + wrapper = document.querySelector('[data-drupal-messages-fallback]'); + wrapper.removeAttribute('data-drupal-messages-fallback'); + wrapper.setAttribute('data-drupal-messages', ''); + wrapper.removeAttribute('class'); + } + return wrapper.innerHTML === '' ? Drupal.Message.messageInternalWrapper(wrapper) : wrapper.firstElementChild; + } + + /** + * Provide an object containing the available message types. + * + * @return {Object} + * An object containing message type strings. + */ + static getMessageTypeLabels() { + return { + status: Drupal.t('Status message'), + error: Drupal.t('Error message'), + warning: Drupal.t('Warning message'), + }; + } + + /** + * Sequentially adds a message to the message area. + * + * @name Drupal.Message~messageDefinition.add + * + * @param {string} message + * The message to display + * @param {object} [options] + * The context of the message, used for removing messages again. + * @param {string} [options.id] + * The message ID, it can be a simple value: `'filevalidationerror'` + * or several values separated by a space: `'mymodule formvalidation'` + * which can be used as a sort of tag for message deletion. + * @param {string} [options.type=status] + * Message type, can be either 'status', 'error' or 'warning'. + * @param {string} [options.announce] + * Screen-reader version of the message if necessary. To prevent a message + * being sent to Drupal.announce() this should be `''`. + * @param {string} [options.priority] + * Priority of the message for Drupal.announce(). + * + * @return {string} + * ID of message. + */ + add(message, options = {}) { + if (!this.messageWrapper) { + this.messageWrapper = Drupal.Message.defaultWrapper(); + } + if (!options.hasOwnProperty('type')) { + options.type = 'status'; + } + + if (typeof message !== 'string') { + throw new Error('Message must be a string.'); + } + + // Send message to screen reader. + Drupal.Message.announce(message, options); + /** + * Use the provided index for the message or generate a unique key to + * allow message deletion. + */ + options.id = options.id ? + String(options.id) : + `${options.type}-${Math.random().toFixed(15).replace('0.', '')}`; + + // Throw an error if an unexpected message type is used. + if (!(Drupal.Message.getMessageTypeLabels().hasOwnProperty(options.type))) { + throw new Error(`The message type, ${options.type}, is not present in Drupal.Message.getMessageTypeLabels().`); + } + + this.messageWrapper.appendChild(Drupal.theme('message', { text: message }, options)); + + return options.id; + } + + /** + * Select a message based on id. + * + * @name Drupal.Message~messageDefinition.select + * + * @param {string} id + * The message id to delete from the area. + * + * @return {Element} + * Element found. + */ + select(id) { + return this.messageWrapper.querySelector(`[data-drupal-message-id^="${id}"]`); + } + + /** + * Removes messages from the message area. + * + * @name Drupal.Message~messageDefinition.remove + * + * @param {string} id + * Index of the message to remove, as returned by + * {@link Drupal.Message~messageDefinition.add}. + * + * @return {number} + * Number of removed messages. + */ + remove(id) { + return this.messageWrapper.removeChild(this.select(id)); + } + + /** + * Removes all messages from the message area. + * + * @name Drupal.Message~messageDefinition.clear + */ + clear() { + Array.prototype.forEach.call(this.messageWrapper.querySelectorAll('[data-drupal-message-id]'), (message) => { + this.messageWrapper.removeChild(message); + }); + } + + /** + * Helper to call Drupal.announce() with the right parameters. + * + * @param {string} message + * Displayed message. + * @param {object} options + * Additional data. + * @param {string} [options.announce] + * Screen-reader version of the message if necessary. To prevent a message + * being sent to Drupal.announce() this should be `''`. + * @param {string} [options.priority] + * Priority of the message for Drupal.announce(). + * @param {string} [options.type] + * Message type, can be either 'status', 'error' or 'warning'. + */ + static announce(message, options) { + if (!options.priority && (options.type === 'warning' || options.type === 'error')) { + options.priority = 'assertive'; + } + /** + * If screen reader message is not disabled announce screen reader + * specific text or fallback to the displayed message. + */ + if (options.announce !== '') { + Drupal.announce(options.announce || message, options.priority); + } + } + + /** + * Function for creating the internal message wrapper element. + * + * @param {HTMLElement} messageWrapper + * The message wrapper. + * + * @return {HTMLElement} + * The internal wrapper DOM element. + */ + static messageInternalWrapper(messageWrapper) { + const innerWrapper = document.createElement('div'); + innerWrapper.setAttribute('class', 'messages__wrapper'); + messageWrapper.insertAdjacentElement('afterbegin', innerWrapper); + return innerWrapper; + } + }; + + /** + * Theme function for a message. + * + * @param {object} message + * The message object. + * @param {string} message.text + * The message text. + * @param {object} options + * The message context. + * @param {string} options.type + * The message type. + * @param {string} options.id + * ID of the message, for reference. + * + * @return {HTMLElement} + * A DOM Node. + */ + Drupal.theme.message = ({ text }, options) => { + const messagesTypes = Drupal.Message.getMessageTypeLabels(); + const messageWraper = document.createElement('div'); + const messageText = document.createElement('h2'); + messageText.setAttribute('class', 'visually-hidden'); + + messageWraper.setAttribute('class', `messages messages--${options.type}`); + messageWraper.setAttribute('role', options.type === 'error' ? 'alert' : 'status'); + messageWraper.setAttribute('data-drupal-message-id', options.id); + messageWraper.setAttribute('data-drupal-message-type', options.type); + + messageWraper.setAttribute('aria-label', messagesTypes[options.type]); + messageText.innerHTML = messagesTypes[options.type]; + + messageWraper.innerHTML = ` ${text}`; + messageWraper.insertAdjacentElement('afterbegin', messageText); + + return messageWraper; + }; +})(Drupal); diff --git a/core/misc/message.js b/core/misc/message.js new file mode 100644 index 0000000000..34664e3be4 --- /dev/null +++ b/core/misc/message.js @@ -0,0 +1,135 @@ +/** +* DO NOT EDIT THIS FILE. +* See the following change record for more information, +* https://www.drupal.org/node/2815083 +* @preserve +**/ +var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + +(function (Drupal) { + + Drupal.Message = function () { + function _class() { + var messageWrapper = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : null; + + _classCallCheck(this, _class); + + this.messageWrapper = messageWrapper; + } + + _createClass(_class, [{ + key: 'add', + value: function add(message) { + var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; + + if (!this.messageWrapper) { + this.messageWrapper = Drupal.Message.defaultWrapper(); + } + if (!options.hasOwnProperty('type')) { + options.type = 'status'; + } + + if (typeof message !== 'string') { + throw new Error('Message must be a string.'); + } + + Drupal.Message.announce(message, options); + + options.id = options.id ? String(options.id) : options.type + '-' + Math.random().toFixed(15).replace('0.', ''); + + if (!Drupal.Message.getMessageTypeLabels().hasOwnProperty(options.type)) { + throw new Error('The message type, ' + options.type + ', is not present in Drupal.Message.getMessageTypeLabels().'); + } + + this.messageWrapper.appendChild(Drupal.theme('message', { text: message }, options)); + + return options.id; + } + }, { + key: 'select', + value: function select(id) { + return this.messageWrapper.querySelector('[data-drupal-message-id^="' + id + '"]'); + } + }, { + key: 'remove', + value: function remove(id) { + return this.messageWrapper.removeChild(this.select(id)); + } + }, { + key: 'clear', + value: function clear() { + var _this = this; + + Array.prototype.forEach.call(this.messageWrapper.querySelectorAll('[data-drupal-message-id]'), function (message) { + _this.messageWrapper.removeChild(message); + }); + } + }], [{ + key: 'defaultWrapper', + value: function defaultWrapper() { + var wrapper = document.querySelector('[data-drupal-messages]'); + if (!wrapper) { + wrapper = document.querySelector('[data-drupal-messages-fallback]'); + wrapper.removeAttribute('data-drupal-messages-fallback'); + wrapper.setAttribute('data-drupal-messages', ''); + wrapper.removeAttribute('class'); + } + return wrapper.innerHTML === '' ? Drupal.Message.messageInternalWrapper(wrapper) : wrapper.firstElementChild; + } + }, { + key: 'getMessageTypeLabels', + value: function getMessageTypeLabels() { + return { + status: Drupal.t('Status message'), + error: Drupal.t('Error message'), + warning: Drupal.t('Warning message') + }; + } + }, { + key: 'announce', + value: function announce(message, options) { + if (!options.priority && (options.type === 'warning' || options.type === 'error')) { + options.priority = 'assertive'; + } + + if (options.announce !== '') { + Drupal.announce(options.announce || message, options.priority); + } + } + }, { + key: 'messageInternalWrapper', + value: function messageInternalWrapper(messageWrapper) { + var innerWrapper = document.createElement('div'); + innerWrapper.setAttribute('class', 'messages__wrapper'); + messageWrapper.insertAdjacentElement('afterbegin', innerWrapper); + return innerWrapper; + } + }]); + + return _class; + }(); + + Drupal.theme.message = function (_ref, options) { + var text = _ref.text; + + var messagesTypes = Drupal.Message.getMessageTypeLabels(); + var messageWraper = document.createElement('div'); + var messageText = document.createElement('h2'); + messageText.setAttribute('class', 'visually-hidden'); + + messageWraper.setAttribute('class', 'messages messages--' + options.type); + messageWraper.setAttribute('role', options.type === 'error' ? 'alert' : 'status'); + messageWraper.setAttribute('data-drupal-message-id', options.id); + messageWraper.setAttribute('data-drupal-message-type', options.type); + + messageWraper.setAttribute('aria-label', messagesTypes[options.type]); + messageText.innerHTML = messagesTypes[options.type]; + + messageWraper.innerHTML = ' ' + text; + messageWraper.insertAdjacentElement('afterbegin', messageText); + + return messageWraper; + }; +})(Drupal); \ No newline at end of file diff --git a/core/modules/big_pipe/tests/modules/big_pipe_test/src/BigPipePlaceholderTestCases.php b/core/modules/big_pipe/tests/modules/big_pipe_test/src/BigPipePlaceholderTestCases.php index f274356139..962bde55f1 100644 --- a/core/modules/big_pipe/tests/modules/big_pipe_test/src/BigPipePlaceholderTestCases.php +++ b/core/modules/big_pipe/tests/modules/big_pipe_test/src/BigPipePlaceholderTestCases.php @@ -88,11 +88,11 @@ public static function cases(ContainerInterface $container = NULL, AccountInterf 'command' => 'insert', 'method' => 'replaceWith', 'selector' => '[data-big-pipe-placeholder-id="callback=Drupal%5CCore%5CRender%5CElement%5CStatusMessages%3A%3ArenderMessages&args%5B0%5D&token=_HAdUpwWmet0TOTe2PSiJuMntExoshbm1kh2wQzzzAA"]', - 'data' => ' ' . "\n ", + 'data' => '
' . "\n" . ' ' . "\n" . '
' . "\n
", 'settings' => NULL, ], ]; - $status_messages->embeddedHtmlResponse = '' . "\n \n"; + $status_messages->embeddedHtmlResponse = '
' . "\n" . ' ' . "\n" . '
' . "\n
"; } // 2. Real-world example of HTML attribute value placeholder: form action. diff --git a/core/modules/comment/comment.install b/core/modules/comment/comment.install index 82b578b489..c244fe4ce8 100644 --- a/core/modules/comment/comment.install +++ b/core/modules/comment/comment.install @@ -5,7 +5,6 @@ * Install, update and uninstall functions for the Comment module. */ -use Drupal\comment\Entity\Comment; use Drupal\Core\Entity\EntityTypeInterface; use Drupal\Core\StringTranslation\PluralTranslatableMarkup; use Drupal\Core\StringTranslation\TranslatableMarkup; @@ -196,14 +195,3 @@ function comment_update_8400() { $entity_definition_update_manager = \Drupal::service('entity.definition_update_manager'); $entity_definition_update_manager->updateFieldStorageDefinition($entity_definition_update_manager->getFieldStorageDefinition('status', 'comment')); } - -/** - * Configure the comment hostname base field to use a default value callback. - */ -function comment_update_8600() { - $entity_definition_update_manager = \Drupal::entityDefinitionUpdateManager(); - /** @var \Drupal\Core\Field\BaseFieldDefinition $field_storage_definition */ - $field_storage_definition = $entity_definition_update_manager->getFieldStorageDefinition('hostname', 'comment'); - $field_storage_definition->setDefaultValueCallback(Comment::class . '::getDefaultHostname'); - $entity_definition_update_manager->updateFieldStorageDefinition($field_storage_definition); -} diff --git a/core/modules/comment/src/Entity/Comment.php b/core/modules/comment/src/Entity/Comment.php index e0745d3da0..5f5a611565 100644 --- a/core/modules/comment/src/Entity/Comment.php +++ b/core/modules/comment/src/Entity/Comment.php @@ -143,6 +143,10 @@ public function preSave(EntityStorageInterface $storage) { $this->threadLock = $lock_name; } $this->setThread($thread); + if (!$this->getHostname()) { + // Ensure a client host from the current request. + $this->setHostname(\Drupal::request()->getClientIP()); + } } // The entity fields for name and mail have no meaning if the user is not // Anonymous. Set them to NULL to make it clearer that they are not used. @@ -287,8 +291,7 @@ public static function baseFieldDefinitions(EntityTypeInterface $entity_type) { ->setLabel(t('Hostname')) ->setDescription(t("The comment author's hostname.")) ->setTranslatable(TRUE) - ->setSetting('max_length', 128) - ->setDefaultValueCallback(static::class . '::getDefaultHostname'); + ->setSetting('max_length', 128); $fields['created'] = BaseFieldDefinition::create('created') ->setLabel(t('Created')) @@ -569,14 +572,4 @@ public static function getDefaultStatus() { return \Drupal::currentUser()->hasPermission('skip comment approval') ? CommentInterface::PUBLISHED : CommentInterface::NOT_PUBLISHED; } - /** - * Returns the default value for entity hostname base field. - * - * @return string - * The client host name. - */ - public static function getDefaultHostname() { - return \Drupal::request()->getClientIP(); - } - } diff --git a/core/modules/comment/tests/src/Functional/Update/CommentHostnameUpdateTest.php b/core/modules/comment/tests/src/Functional/Update/CommentHostnameUpdateTest.php deleted file mode 100644 index 7d75b6e6dd..0000000000 --- a/core/modules/comment/tests/src/Functional/Update/CommentHostnameUpdateTest.php +++ /dev/null @@ -1,45 +0,0 @@ -databaseDumpFiles = [ - __DIR__ . '/../../../../../system/tests/fixtures/update/drupal-8-rc1.bare.standard.php.gz', - ]; - } - - /** - * Tests comment_update_8600(). - * - * @see comment_update_8600 - */ - public function testCommentUpdate8600() { - /** @var \Drupal\Core\Entity\EntityDefinitionUpdateManagerInterface $manager */ - $manager = $this->container->get('entity.definition_update_manager'); - - /** @var \Drupal\Core\Field\BaseFieldDefinition $definition */ - $definition = $manager->getFieldStorageDefinition('hostname', 'comment'); - // Check that 'hostname' base field doesn't have a default value callback. - $this->assertNull($definition->getDefaultValueCallback()); - - $this->runUpdates(); - - $definition = $manager->getFieldStorageDefinition('hostname', 'comment'); - // Check that 'hostname' base field default value callback was set. - $this->assertEquals(Comment::class . '::getDefaultHostname', $definition->getDefaultValueCallback()); - } - -} diff --git a/core/modules/comment/tests/src/Kernel/CommentHostnameTest.php b/core/modules/comment/tests/src/Kernel/CommentHostnameTest.php deleted file mode 100644 index dc4f37b63f..0000000000 --- a/core/modules/comment/tests/src/Kernel/CommentHostnameTest.php +++ /dev/null @@ -1,46 +0,0 @@ - '203.0.113.1']); - /** @var \Symfony\Component\HttpFoundation\RequestStack $stack */ - $stack = $this->container->get('request_stack'); - $stack->push($request); - - CommentType::create([ - 'id' => 'foo', - 'target_entity_type_id' => 'entity_test', - ])->save(); - $comment = Comment::create(['comment_type' => 'foo']); - - // Check that the hostname was set correctly. - $this->assertEquals('203.0.113.1', $comment->getHostname()); - } - -} diff --git a/core/modules/config/tests/config_test/config/install/config_test.dynamic.isinstallable.yml b/core/modules/config/tests/config_test/config/install/config_test.dynamic.isinstallable.default.yml similarity index 100% rename from core/modules/config/tests/config_test/config/install/config_test.dynamic.isinstallable.yml rename to core/modules/config/tests/config_test/config/install/config_test.dynamic.isinstallable.default.yml diff --git a/core/modules/config/tests/config_test_id_mismatch/config/install/config_test.dynamic.no_id_match.yml b/core/modules/config/tests/config_test_id_mismatch/config/install/config_test.dynamic.no_id_match.yml deleted file mode 100644 index 879a53b5b9..0000000000 --- a/core/modules/config/tests/config_test_id_mismatch/config/install/config_test.dynamic.no_id_match.yml +++ /dev/null @@ -1,5 +0,0 @@ -id: does_not_match -label: Default -weight: 0 -protected_property: Default -langcode: en diff --git a/core/modules/config/tests/config_test_id_mismatch/config_test_id_mismatch.info.yml b/core/modules/config/tests/config_test_id_mismatch/config_test_id_mismatch.info.yml deleted file mode 100644 index 7cd2e0926a..0000000000 --- a/core/modules/config/tests/config_test_id_mismatch/config_test_id_mismatch.info.yml +++ /dev/null @@ -1,7 +0,0 @@ -name: 'Configuration test ID mismatch' -type: module -package: Testing -version: VERSION -core: 8.x -dependencies: - - drupal:config_test diff --git a/core/modules/field/tests/src/Kernel/EntityReference/EntityReferenceItemTest.php b/core/modules/field/tests/src/Kernel/EntityReference/EntityReferenceItemTest.php index 627fdba5dc..c9446c7453 100644 --- a/core/modules/field/tests/src/Kernel/EntityReference/EntityReferenceItemTest.php +++ b/core/modules/field/tests/src/Kernel/EntityReference/EntityReferenceItemTest.php @@ -3,7 +3,6 @@ namespace Drupal\Tests\field\Kernel\EntityReference; use Drupal\comment\Entity\Comment; -use Drupal\comment\Entity\CommentType; use Drupal\Component\Render\FormattableMarkup; use Drupal\Core\Field\FieldItemListInterface; use Drupal\Core\Field\FieldItemInterface; @@ -15,7 +14,6 @@ use Drupal\field\Entity\FieldConfig; use Drupal\field\Entity\FieldStorageConfig; use Drupal\field\Tests\EntityReference\EntityReferenceTestTrait; -use Drupal\node\Entity\NodeType; use Drupal\node\NodeInterface; use Drupal\taxonomy\TermInterface; use Drupal\Tests\field\Kernel\FieldKernelTestBase; @@ -91,14 +89,6 @@ protected function setUp() { ]); $this->term->save(); - NodeType::create([ - 'type' => $this->randomMachineName(), - ])->save(); - CommentType::create([ - 'id' => $this->randomMachineName(), - 'target_entity_type_id' => 'node', - ])->save(); - $this->entityStringId = EntityTestStringId::create([ 'id' => $this->randomMachineName(), ]); @@ -112,7 +102,6 @@ protected function setUp() { $this->createEntityReferenceField('entity_test', 'entity_test', 'field_test_user', 'Test user entity reference', 'user'); $this->createEntityReferenceField('entity_test', 'entity_test', 'field_test_comment', 'Test comment entity reference', 'comment'); $this->createEntityReferenceField('entity_test', 'entity_test', 'field_test_file', 'Test file entity reference', 'file'); - $this->createEntityReferenceField('entity_test_string_id', 'entity_test_string_id', 'field_test_entity_test', 'Test content entity reference with string ID', 'entity_test'); } /** @@ -222,19 +211,6 @@ public function testGenerateSampleValue() { $this->entityValidateAndSave($entity); } - /** - * Tests the ::generateSampleValue() method when it has a circular reference. - */ - public function testGenerateSampleValueCircularReference() { - // Delete the existing entity. - $this->entityStringId->delete(); - - $entity_storage = \Drupal::entityTypeManager()->getStorage('entity_test'); - $entity = $entity_storage->createWithSampleValues('entity_test'); - $this->assertInstanceOf(EntityTestStringId::class, $entity->field_test_entity_test_string_id->entity); - $this->assertInstanceOf(EntityTest::class, $entity->field_test_entity_test_string_id->entity->field_test_entity_test->entity); - } - /** * Tests referencing content entities with string IDs. */ diff --git a/core/modules/layout_builder/tests/modules/layout_builder_views_test/config/install/views.view.test_block_view.yml b/core/modules/layout_builder/tests/modules/layout_builder_views_test/config/install/views.view.test_block_view.yml deleted file mode 100644 index c666e89d68..0000000000 --- a/core/modules/layout_builder/tests/modules/layout_builder_views_test/config/install/views.view.test_block_view.yml +++ /dev/null @@ -1,177 +0,0 @@ -langcode: en -status: true -dependencies: - module: - - node - - user -id: test_block_view -label: 'Test Block View' -module: views -description: '' -tag: '' -base_table: node_field_data -base_field: nid -core: 8.x -display: - default: - display_plugin: default - id: default - display_title: Master - position: 0 - display_options: - access: - type: perm - options: - perm: 'access content' - cache: - type: tag - options: { } - query: - type: views_query - options: - disable_sql_rewrite: false - distinct: false - replica: false - query_comment: '' - query_tags: { } - exposed_form: - type: basic - options: - submit_button: Apply - reset_button: false - reset_button_label: Reset - exposed_sorts_label: 'Sort by' - expose_sort_order: true - sort_asc_label: Asc - sort_desc_label: Desc - pager: - type: some - options: - items_per_page: 5 - offset: 0 - style: - type: default - row: - type: fields - fields: - title: - id: title - table: node_field_data - field: title - settings: - link_to_entity: true - plugin_id: field - relationship: none - group_type: group - admin_label: '' - label: '' - exclude: false - alter: - alter_text: false - text: '' - make_link: false - path: '' - absolute: false - external: false - replace_spaces: false - path_case: none - trim_whitespace: false - alt: '' - rel: '' - link_class: '' - prefix: '' - suffix: '' - target: '' - nl2br: false - max_length: 0 - word_boundary: true - ellipsis: true - more_link: false - more_link_text: '' - more_link_path: '' - strip_tags: false - trim: false - preserve_tags: '' - html: false - element_type: '' - element_class: '' - element_label_type: '' - element_label_class: '' - element_label_colon: true - element_wrapper_type: '' - element_wrapper_class: '' - element_default_classes: true - empty: '' - hide_empty: false - empty_zero: false - hide_alter_empty: true - click_sort_column: value - type: string - group_column: value - group_columns: { } - group_rows: true - delta_limit: 0 - delta_offset: 0 - delta_reversed: false - delta_first_last: false - multi_type: separator - separator: ', ' - field_api_classes: false - filters: - status: - value: '1' - table: node_field_data - field: status - plugin_id: boolean - entity_type: node - entity_field: status - id: status - expose: - operator: '' - group: 1 - sorts: - created: - id: created - table: node_field_data - field: created - order: DESC - entity_type: node - entity_field: created - plugin_id: date - relationship: none - group_type: group - admin_label: '' - exposed: false - expose: - label: '' - granularity: second - title: 'Test Block View' - header: { } - footer: { } - empty: { } - relationships: { } - arguments: { } - display_extenders: { } - cache_metadata: - max-age: -1 - contexts: - - 'languages:language_content' - - 'languages:language_interface' - - 'user.node_grants:view' - - user.permissions - tags: { } - block_1: - display_plugin: block - id: block_1 - display_title: Block - position: 1 - display_options: - display_extenders: { } - cache_metadata: - max-age: -1 - contexts: - - 'languages:language_content' - - 'languages:language_interface' - - 'user.node_grants:view' - - user.permissions - tags: { } diff --git a/core/modules/layout_builder/tests/modules/layout_builder_views_test/layout_builder_views_test.info.yml b/core/modules/layout_builder/tests/modules/layout_builder_views_test/layout_builder_views_test.info.yml deleted file mode 100644 index b471a0a636..0000000000 --- a/core/modules/layout_builder/tests/modules/layout_builder_views_test/layout_builder_views_test.info.yml +++ /dev/null @@ -1,8 +0,0 @@ -name: 'Layout Builder Views Test' -type: module -description: 'Support module for testing.' -package: Testing -version: VERSION -core: 8.x -dependencies: - - views diff --git a/core/modules/layout_builder/tests/src/Functional/LayoutBuilderTest.php b/core/modules/layout_builder/tests/src/Functional/LayoutBuilderTest.php index 40c0503cea..b99c273f55 100644 --- a/core/modules/layout_builder/tests/src/Functional/LayoutBuilderTest.php +++ b/core/modules/layout_builder/tests/src/Functional/LayoutBuilderTest.php @@ -2,9 +2,7 @@ namespace Drupal\Tests\layout_builder\Functional; -use Drupal\node\Entity\Node; use Drupal\Tests\BrowserTestBase; -use Drupal\views\Entity\View; /** * Tests the Layout Builder UI. @@ -17,9 +15,7 @@ class LayoutBuilderTest extends BrowserTestBase { * {@inheritdoc} */ public static $modules = [ - 'views', 'layout_builder', - 'layout_builder_views_test', 'layout_test', 'block', 'node', @@ -353,40 +349,4 @@ public function testLayoutBuilderChooseBlocksAlter() { $assert_session->linkNotExists('Sticky at top of lists'); } - /** - * Tests that deleting a View block used in Layout Builder works. - */ - public function testDeletedView() { - $assert_session = $this->assertSession(); - $page = $this->getSession()->getPage(); - - $this->drupalLogin($this->drupalCreateUser([ - 'configure any layout', - 'administer node display', - ])); - - $field_ui_prefix = 'admin/structure/types/manage/bundle_with_section_field'; - // Enable overrides. - $this->drupalPostForm("$field_ui_prefix/display/default", ['layout[allow_custom]' => TRUE], 'Save'); - $this->drupalGet('node/1'); - - $assert_session->linkExists('Layout'); - $this->clickLink('Layout'); - $this->clickLink('Add Block'); - $this->clickLink('Test Block View'); - $page->pressButton('Add Block'); - - $assert_session->pageTextContains('Test Block View'); - $assert_session->elementExists('css', '.block-views-blocktest-block-view-block-1'); - $this->clickLink('Save Layout'); - $assert_session->pageTextContains('Test Block View'); - $assert_session->elementExists('css', '.block-views-blocktest-block-view-block-1'); - - View::load('test_block_view')->delete(); - $this->drupalGet('node/1'); - // Node can be loaded after deleting the View. - $assert_session->pageTextContains(Node::load(1)->getTitle()); - $assert_session->pageTextNotContains('Test Block View'); - } - } diff --git a/core/modules/rest/tests/modules/rest_test/rest_test.services.yml b/core/modules/rest/tests/modules/rest_test/rest_test.services.yml index 85b418a6c6..d316cf6072 100644 --- a/core/modules/rest/tests/modules/rest_test/rest_test.services.yml +++ b/core/modules/rest/tests/modules/rest_test/rest_test.services.yml @@ -12,7 +12,3 @@ services: public: false tags: - { name: page_cache_request_policy } - rest_test.encoder.foobar: - class: Drupal\serialization\Encoder\JsonEncoder - tags: - - { name: encoder, format: foobar } diff --git a/core/modules/rest/tests/src/Functional/EntityResource/EntityResourceTestBase.php b/core/modules/rest/tests/src/Functional/EntityResource/EntityResourceTestBase.php index b5d7fbb7d4..2ab45273d5 100644 --- a/core/modules/rest/tests/src/Functional/EntityResource/EntityResourceTestBase.php +++ b/core/modules/rest/tests/src/Functional/EntityResource/EntityResourceTestBase.php @@ -156,22 +156,12 @@ /** * Provides an entity resource. - * - * @param bool $single_format - * Provisions a single-format entity REST resource. Defaults to FALSE. */ - protected function provisionEntityResource($single_format = FALSE) { - if ($existing = $this->resourceConfigStorage->load(static::$resourceConfigId)) { - $existing->delete(); - } - - $format = $single_format - ? [static::$format] - : [static::$format, 'foobar']; + protected function provisionEntityResource() { // It's possible to not have any authentication providers enabled, when // testing public (anonymous) usage of a REST resource. $auth = isset(static::$auth) ? [static::$auth] : []; - $this->provisionResource($format, $auth); + $this->provisionResource([static::$format], $auth); } /** @@ -444,6 +434,20 @@ public function testGet() { } $this->provisionEntityResource(); + // Simulate the developer again forgetting the ?_format query string. + $url->setOption('query', []); + + // DX: 406 when ?_format is missing, except when requesting a canonical HTML + // route. + $response = $this->request('GET', $url, $request_options); + if ($has_canonical_url && (!static::$auth || static::$auth === 'cookie')) { + $this->assertSame(403, $response->getStatusCode()); + } + else { + $this->assert406Response($response); + } + + $url->setOption('query', ['_format' => static::$format]); // DX: forgetting authentication: authentication provider-specific error // response. @@ -468,44 +472,10 @@ public function testGet() { unset($request_options[RequestOptions::HEADERS]['REST-test-auth-global']); $request_options = NestedArray::mergeDeep($request_options, $this->getAuthenticationRequestOptions('GET')); - // First: single format. Drupal will automatically pick the only format. - $this->provisionEntityResource(TRUE); - $expected_403_cacheability = $this->getExpectedUnauthorizedAccessCacheability(); - // DX: 403 because unauthorized single-format route, ?_format is omittable. - $url->setOption('query', []); - $response = $this->request('GET', $url, $request_options); - if ($has_canonical_url) { - $this->assertSame(403, $response->getStatusCode()); - $this->assertSame(['text/html; charset=UTF-8'], $response->getHeader('Content-Type')); - } - else { - $this->assertResourceErrorResponse(403, FALSE, $response, $expected_403_cacheability->getCacheTags(), $expected_403_cacheability->getCacheContexts(), static::$auth ? FALSE : 'MISS', 'MISS'); - } - $this->assertSame(static::$auth ? [] : ['MISS'], $response->getHeader('X-Drupal-Cache')); - // DX: 403 because unauthorized. - $url->setOption('query', ['_format' => static::$format]); - $response = $this->request('GET', $url, $request_options); - $this->assertResourceErrorResponse(403, FALSE, $response, $expected_403_cacheability->getCacheTags(), $expected_403_cacheability->getCacheContexts(), static::$auth ? FALSE : 'MISS', $has_canonical_url ? 'MISS' : 'HIT'); - - // Then, what we'll use for the remainder of the test: multiple formats. - $this->provisionEntityResource(); - // DX: 406 because despite unauthorized, ?_format is not omittable. - $url->setOption('query', []); - $response = $this->request('GET', $url, $request_options); - if ($has_canonical_url) { - $this->assertSame(403, $response->getStatusCode()); - $this->assertSame(['HIT'], $response->getHeader('X-Drupal-Dynamic-Cache')); - } - else { - $this->assertSame(406, $response->getStatusCode()); - $this->assertSame(['UNCACHEABLE'], $response->getHeader('X-Drupal-Dynamic-Cache')); - } - $this->assertSame(['text/html; charset=UTF-8'], $response->getHeader('Content-Type')); - $this->assertSame(static::$auth ? [] : ['MISS'], $response->getHeader('X-Drupal-Cache')); - // DX: 403 because unauthorized. - $url->setOption('query', ['_format' => static::$format]); + // DX: 403 when unauthorized. $response = $this->request('GET', $url, $request_options); - $this->assertResourceErrorResponse(403, $this->getExpectedUnauthorizedAccessMessage('GET'), $response, $expected_403_cacheability->getCacheTags(), $expected_403_cacheability->getCacheContexts(), static::$auth ? FALSE : 'MISS', 'HIT'); + $expected_403_cacheability = $this->getExpectedUnauthorizedAccessCacheability(); + $this->assertResourceErrorResponse(403, $this->getExpectedUnauthorizedAccessMessage('GET'), $response, $expected_403_cacheability->getCacheTags(), $expected_403_cacheability->getCacheContexts(), static::$auth ? FALSE : 'MISS', 'MISS'); $this->assertArrayNotHasKey('Link', $response->getHeaders()); $this->setUpAuthorization('GET'); diff --git a/core/modules/system/src/Tests/JsMessageTestCases.php b/core/modules/system/src/Tests/JsMessageTestCases.php new file mode 100644 index 0000000000..05e1040dd3 --- /dev/null +++ b/core/modules/system/src/Tests/JsMessageTestCases.php @@ -0,0 +1,32 @@ + {% for type, messages in message_list %}
{% if type == 'error' %}
{% endif %} - {% if status_headings[type] %} -

{{ status_headings[type] }}

- {% endif %} - {% if messages|length > 1 %} - - {% else %} - {{ messages|first }} - {% endif %} + {% if status_headings[type] %} +

{{ status_headings[type] }}

+ {% endif %} + {% if messages|length > 1 %} + + {% else %} + {{ messages|first }} + {% endif %} {% if type == 'error' %}
{% endif %}
{% endfor %} + diff --git a/core/modules/system/tests/modules/default_format_test/default_format_test.info.yml b/core/modules/system/tests/modules/default_format_test/default_format_test.info.yml deleted file mode 100644 index 0a02a0688a..0000000000 --- a/core/modules/system/tests/modules/default_format_test/default_format_test.info.yml +++ /dev/null @@ -1,6 +0,0 @@ -name: 'Default format test' -type: module -description: 'Support module for testing default route format.' -package: Testing -version: VERSION -core: 8.x diff --git a/core/modules/system/tests/modules/default_format_test/default_format_test.routing.yml b/core/modules/system/tests/modules/default_format_test/default_format_test.routing.yml deleted file mode 100644 index 368dcb1946..0000000000 --- a/core/modules/system/tests/modules/default_format_test/default_format_test.routing.yml +++ /dev/null @@ -1,28 +0,0 @@ -default_format_test.machine: - path: '/default_format_test/machine' - defaults: - # Same controller + method! - _controller: '\Drupal\default_format_test\DefaultFormatTestController::content' - requirements: - _access: 'TRUE' - _format: 'json' - -default_format_test.human: - path: '/default_format_test/human' - defaults: - # Same controller + method! - _controller: '\Drupal\default_format_test\DefaultFormatTestController::content' - requirements: - _access: 'TRUE' - _format: 'html' - -# Route definition identical to default_format_test.machine, only different name. -# @see \Drupal\FunctionalTests\Routing\DefaultFormatTest::testMultiple -default_format_test.machine.alias: - path: '/default_format_test/machine' - defaults: - # Same controller + method! - _controller: '\Drupal\default_format_test\DefaultFormatTestController::content' - requirements: - _access: 'TRUE' - _format: 'json' diff --git a/core/modules/system/tests/modules/default_format_test/src/DefaultFormatTestController.php b/core/modules/system/tests/modules/default_format_test/src/DefaultFormatTestController.php deleted file mode 100644 index 6e52eea342..0000000000 --- a/core/modules/system/tests/modules/default_format_test/src/DefaultFormatTestController.php +++ /dev/null @@ -1,15 +0,0 @@ -getRequestFormat(); - return new CacheableResponse('format:' . $format, 200, ['Content-Type' => $request->getMimeType($format)]); - } - -} diff --git a/core/modules/system/tests/modules/js_message_test/js/js_message_test.es6.js b/core/modules/system/tests/modules/js_message_test/js/js_message_test.es6.js new file mode 100644 index 0000000000..2eb41a21c9 --- /dev/null +++ b/core/modules/system/tests/modules/js_message_test/js/js_message_test.es6.js @@ -0,0 +1,92 @@ +/** + * @file + * Testing behavior for JSMessageTest. + */ + +(($, { behaviors }, { testMessages }) => { + // Message types + const indexes = {}; + testMessages.types.forEach((type) => { + indexes[type] = []; + }); + + // Message storage. + const messageObjects = { + default: { + zone: new Drupal.Message(), + indexes, + }, + multiple: [], + }; + + testMessages.selectors + .filter(Boolean) + .forEach((selector) => { + messageObjects[selector] = { + zone: new Drupal.Message(document.querySelector(selector)), + indexes, + }; + }); + + /** + * @type {Drupal~behavior} + * + * @prop {Drupal~behaviorAttach} attach + * Add click listeners that show and remove links with context and type. + */ + behaviors.js_message_test = { + attach() { + $('[data-drupal-messages-area]').once('messages-details').on('click', '[data-action]', (e) => { + const $target = $(e.currentTarget); + const type = $target.attr('data-type'); + const area = $target.closest('[data-drupal-messages-area]').attr('data-drupal-messages-area') || 'default'; + const message = messageObjects[area].zone; + const action = $target.attr('data-action'); + + if (action === 'add') { + messageObjects[area].indexes[type].push( + message.add(`This is a message of the type, ${type}. You be the the judge of its importance.`, { type }), + ); + } + else if (action === 'remove') { + message.remove(messageObjects[area].indexes[type].pop()); + } + }); + $('[data-action="add-multiple"]').once('add-multiple').on('click', () => { + /** + * Add several of different types to make sure message type doesn't + * cause issues in the API. + */ + [0, 1, 2, 3, 4, 5].forEach((i) => { + messageObjects.multiple.push( + messageObjects.default.zone.add( + `This is message number ${i} of the type, ${testMessages.types[i % testMessages.types.length]}. You be the the judge of its importance.`, + { type: testMessages.types[i % testMessages.types.length] }, + ), + ); + }); + }); + $('[data-action="remove-multiple"]').once('remove-multiple').on('click', () => { + messageObjects.multiple + .forEach(messageIndex => messageObjects.default.zone.remove(messageIndex)); + messageObjects.multiple = []; + }); + $('[data-action="add-multiple-error"]').once('add-multiple-error').on('click', () => { + // Use the same number of elements to facilitate things on the PHP side. + [0, 1, 2, 3, 4, 5].forEach(i => messageObjects.default.zone.add(`Msg-${i}`, { type: 'error' })); + messageObjects.default.zone.add(`Msg-${testMessages.types.length * 2}`, { type: 'status' }); + }); + $('[data-action="remove-type"]').once('remove-type').on('click', () => { + Array.prototype.map + .call(document.querySelectorAll('[data-drupal-message-id^="error"]'), element => element.getAttribute('data-drupal-message-id')) + .forEach(id => messageObjects.default.zone.remove(id)); + }); + $('[data-action="clear-all"]').once('clear-all').on('click', () => { + messageObjects.default.zone.clear(); + }); + $('[data-action="id-no-status"]').once('id-no-status').on('click', () => { + messageObjects.default.zone.add('Msg-id-no-status', { id: 'my-special-id' }); + }); + }, + }; +})(jQuery, Drupal, drupalSettings); diff --git a/core/modules/system/tests/modules/js_message_test/js/js_message_test.js b/core/modules/system/tests/modules/js_message_test/js/js_message_test.js new file mode 100644 index 0000000000..5f48c826ac --- /dev/null +++ b/core/modules/system/tests/modules/js_message_test/js/js_message_test.js @@ -0,0 +1,79 @@ +/** +* DO NOT EDIT THIS FILE. +* See the following change record for more information, +* https://www.drupal.org/node/2815083 +* @preserve +**/ + +(function ($, _ref, _ref2) { + var behaviors = _ref.behaviors; + var testMessages = _ref2.testMessages; + + var indexes = {}; + testMessages.types.forEach(function (type) { + indexes[type] = []; + }); + + var messageObjects = { + default: { + zone: new Drupal.Message(), + indexes: indexes + }, + multiple: [] + }; + + testMessages.selectors.filter(Boolean).forEach(function (selector) { + messageObjects[selector] = { + zone: new Drupal.Message(document.querySelector(selector)), + indexes: indexes + }; + }); + + behaviors.js_message_test = { + attach: function attach() { + $('[data-drupal-messages-area]').once('messages-details').on('click', '[data-action]', function (e) { + var $target = $(e.currentTarget); + var type = $target.attr('data-type'); + var area = $target.closest('[data-drupal-messages-area]').attr('data-drupal-messages-area') || 'default'; + var message = messageObjects[area].zone; + var action = $target.attr('data-action'); + + if (action === 'add') { + messageObjects[area].indexes[type].push(message.add('This is a message of the type, ' + type + '. You be the the judge of its importance.', { type: type })); + } else if (action === 'remove') { + message.remove(messageObjects[area].indexes[type].pop()); + } + }); + $('[data-action="add-multiple"]').once('add-multiple').on('click', function () { + [0, 1, 2, 3, 4, 5].forEach(function (i) { + messageObjects.multiple.push(messageObjects.default.zone.add('This is message number ' + i + ' of the type, ' + testMessages.types[i % testMessages.types.length] + '. You be the the judge of its importance.', { type: testMessages.types[i % testMessages.types.length] })); + }); + }); + $('[data-action="remove-multiple"]').once('remove-multiple').on('click', function () { + messageObjects.multiple.forEach(function (messageIndex) { + return messageObjects.default.zone.remove(messageIndex); + }); + messageObjects.multiple = []; + }); + $('[data-action="add-multiple-error"]').once('add-multiple-error').on('click', function () { + [0, 1, 2, 3, 4, 5].forEach(function (i) { + return messageObjects.default.zone.add('Msg-' + i, { type: 'error' }); + }); + messageObjects.default.zone.add('Msg-' + testMessages.types.length * 2, { type: 'status' }); + }); + $('[data-action="remove-type"]').once('remove-type').on('click', function () { + Array.prototype.map.call(document.querySelectorAll('[data-drupal-message-id^="error"]'), function (element) { + return element.getAttribute('data-drupal-message-id'); + }).forEach(function (id) { + return messageObjects.default.zone.remove(id); + }); + }); + $('[data-action="clear-all"]').once('clear-all').on('click', function () { + messageObjects.default.zone.clear(); + }); + $('[data-action="id-no-status"]').once('id-no-status').on('click', function () { + messageObjects.default.zone.add('Msg-id-no-status', { id: 'my-special-id' }); + }); + } + }; +})(jQuery, Drupal, drupalSettings); \ No newline at end of file diff --git a/core/modules/system/tests/modules/js_message_test/js_message_test.info.yml b/core/modules/system/tests/modules/js_message_test/js_message_test.info.yml new file mode 100644 index 0000000000..e8bc73b065 --- /dev/null +++ b/core/modules/system/tests/modules/js_message_test/js_message_test.info.yml @@ -0,0 +1,6 @@ +name: 'JS Message test module' +type: module +description: 'Module for the JSMessageTest test.' +package: Testing +version: VERSION +core: 8.x diff --git a/core/modules/system/tests/modules/js_message_test/js_message_test.libraries.yml b/core/modules/system/tests/modules/js_message_test/js_message_test.libraries.yml new file mode 100644 index 0000000000..57501a9ce0 --- /dev/null +++ b/core/modules/system/tests/modules/js_message_test/js_message_test.libraries.yml @@ -0,0 +1,8 @@ +show_message: + version: VERSION + js: + js/js_message_test.js: {} + dependencies: + - core/drupalSettings + - core/drupal.message + - core/jquery.once diff --git a/core/modules/system/tests/modules/js_message_test/js_message_test.routing.yml b/core/modules/system/tests/modules/js_message_test/js_message_test.routing.yml new file mode 100644 index 0000000000..4c325e8461 --- /dev/null +++ b/core/modules/system/tests/modules/js_message_test/js_message_test.routing.yml @@ -0,0 +1,7 @@ +js_message_test.links: + path: '/js_message_test_link' + defaults: + _controller: '\Drupal\js_message_test\Controller\JSMessageTestController::messageLinks' + _title: 'JsMessageLinks' + requirements: + _access: 'TRUE' diff --git a/core/modules/system/tests/modules/js_message_test/src/Controller/JSMessageTestController.php b/core/modules/system/tests/modules/js_message_test/src/Controller/JSMessageTestController.php new file mode 100644 index 0000000000..57d38284f7 --- /dev/null +++ b/core/modules/system/tests/modules/js_message_test/src/Controller/JSMessageTestController.php @@ -0,0 +1,139 @@ + 'details', + '#open' => TRUE, + '#title' => "Message area: $messagesSelector", + '#attributes' => [ + 'data-drupal-messages-area' => $messagesSelector, + ], + ]; + foreach (JsMessageTestCases::getTypes() as $type) { + $buttons[$messagesSelector]["add-$type"] = [ + '#type' => 'html_tag', + '#tag' => 'button', + '#value' => "Add $type", + '#attributes' => [ + 'type' => 'button', + 'id' => "add-$messagesSelector-$type", + 'data-type' => $type, + 'data-action' => 'add', + ], + ]; + $buttons[$messagesSelector]["remove-$type"] = [ + '#type' => 'html_tag', + '#tag' => 'button', + '#value' => "Remove $type", + '#attributes' => [ + 'type' => 'button', + 'id' => "remove-$messagesSelector-$type", + 'data-type' => $type, + 'data-action' => 'remove', + ], + ]; + } + } + // Add alternative message area. + $buttons[JsMessageTestCases::getMessagesSelectors()[1]]['messages-other-area'] = [ + '#type' => 'html_tag', + '#tag' => 'div', + '#attributes' => [ + 'data-drupal-messages-other' => TRUE, + ], + ]; + $buttons['add-multiple'] = [ + '#type' => 'html_tag', + '#tag' => 'button', + '#value' => "Add multiple", + '#attributes' => [ + 'type' => 'button', + 'id' => 'add-multiple', + 'data-action' => 'add-multiple', + ], + ]; + $buttons['remove-multiple'] = [ + '#type' => 'html_tag', + '#tag' => 'button', + '#value' => "Remove multiple", + '#attributes' => [ + 'type' => 'button', + 'id' => 'remove-multiple', + 'data-action' => 'remove-multiple', + ], + ]; + $buttons['add-multiple-error'] = [ + '#type' => 'html_tag', + '#tag' => 'button', + '#value' => "Add multiple 'error' and one 'status'", + '#attributes' => [ + 'type' => 'button', + 'id' => 'add-multiple-error', + 'data-action' => 'add-multiple-error', + ], + ]; + $buttons['remove-type'] = [ + '#type' => 'html_tag', + '#tag' => 'button', + '#value' => "Remove 'error' type", + '#attributes' => [ + 'type' => 'button', + 'id' => 'remove-type', + 'data-action' => 'remove-type', + ], + ]; + $buttons['clear-all'] = [ + '#type' => 'html_tag', + '#tag' => 'button', + '#value' => "Clear all", + '#attributes' => [ + 'type' => 'button', + 'id' => 'clear-all', + 'data-action' => 'clear-all', + ], + ]; + + $buttons['id-no-status'] = [ + '#type' => 'html_tag', + '#tag' => 'button', + '#value' => "Id no status", + '#attributes' => [ + 'type' => 'button', + 'id' => 'id-no-status', + 'data-action' => 'id-no-status', + ], + ]; + + return $buttons + [ + '#attached' => [ + 'library' => [ + 'js_message_test/show_message', + ], + 'drupalSettings' => [ + 'testMessages' => [ + 'selectors' => JsMessageTestCases::getMessagesSelectors(), + 'types' => JsMessageTestCases::getTypes(), + ], + ], + ], + ]; + } + +} diff --git a/core/modules/system/tests/themes/test_messages/templates/status-messages.html.twig b/core/modules/system/tests/themes/test_messages/templates/status-messages.html.twig new file mode 100644 index 0000000000..42f105d3df --- /dev/null +++ b/core/modules/system/tests/themes/test_messages/templates/status-messages.html.twig @@ -0,0 +1,41 @@ +{# +/** + * @file + * Test templates file with extra messages div. + */ +#} +
+{% block messages %} +{% for type, messages in message_list %} + {% + set classes = [ + 'messages', + 'messages--' ~ type, + ] + %} +
+ {% if type == 'error' %} +
+ {% endif %} + {% if status_headings[type] %} +

{{ status_headings[type] }}

+ {% endif %} + {% if messages|length > 1 %} +
    + {% for message in messages %} +
  • {{ message }}
  • + {% endfor %} +
+ {% else %} + {{ messages|first }} + {% endif %} + {% if type == 'error' %} +
+ {% endif %} +
+ {# Remove type specific classes. #} + {% set attributes = attributes.removeClass(classes) %} +{% endfor %} +{% endblock messages %} +
+
diff --git a/core/modules/system/tests/themes/test_messages/test_messages.info.yml b/core/modules/system/tests/themes/test_messages/test_messages.info.yml new file mode 100644 index 0000000000..ea88438d98 --- /dev/null +++ b/core/modules/system/tests/themes/test_messages/test_messages.info.yml @@ -0,0 +1,6 @@ +name: 'Theme test messages' +type: theme +description: 'Test theme which provides another div for messages.' +version: VERSION +core: 8.x +base theme: classy diff --git a/core/modules/taxonomy/tests/src/Functional/Views/TaxonomyDefaultArgumentTest.php b/core/modules/taxonomy/tests/src/Functional/Views/TaxonomyDefaultArgumentTest.php index d2eb21625d..82646c03c0 100644 --- a/core/modules/taxonomy/tests/src/Functional/Views/TaxonomyDefaultArgumentTest.php +++ b/core/modules/taxonomy/tests/src/Functional/Views/TaxonomyDefaultArgumentTest.php @@ -2,6 +2,11 @@ namespace Drupal\Tests\taxonomy\Functional\Views; +use Drupal\field\Entity\FieldConfig; +use Drupal\views\Views; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\HttpKernelInterface; + /** * Tests the representative node relationship for terms. * @@ -16,6 +21,73 @@ class TaxonomyDefaultArgumentTest extends TaxonomyTestBase { */ public static $testViews = ['taxonomy_default_argument_test']; + /** + * Tests the relationship. + */ + public function testNodePath() { + $view = Views::getView('taxonomy_default_argument_test'); + + $request = Request::create($this->nodes[0]->url()); + $request->server->set('SCRIPT_NAME', $GLOBALS['base_path'] . 'index.php'); + $request->server->set('SCRIPT_FILENAME', 'index.php'); + + $response = $this->container->get('http_kernel') + ->handle($request, HttpKernelInterface::SUB_REQUEST); + $view->setRequest($request); + $view->setResponse($response); + + $view->initHandlers(); + $expected = implode(',', [$this->term1->id(), $this->term2->id()]); + $this->assertEqual($expected, $view->argument['tid']->getDefaultArgument()); + $view->destroy(); + } + + public function testNodePathWithViewSelection() { + // Change the term entity reference field to use a view as selection plugin. + \Drupal::service('module_installer')->install(['entity_reference_test']); + + $field_name = 'field_' . $this->vocabulary->id(); + $field = FieldConfig::loadByName('node', 'article', $field_name); + $field->setSetting('handler', 'views'); + $field->setSetting('handler_settings', [ + 'view' => [ + 'view_name' => 'test_entity_reference', + 'display_name' => 'entity_reference_1', + ], + ]); + $field->save(); + + $view = Views::getView('taxonomy_default_argument_test'); + + $request = Request::create($this->nodes[0]->url()); + $request->server->set('SCRIPT_NAME', $GLOBALS['base_path'] . 'index.php'); + $request->server->set('SCRIPT_FILENAME', 'index.php'); + + $response = $this->container->get('http_kernel')->handle($request, HttpKernelInterface::SUB_REQUEST); + $view->setRequest($request); + $view->setResponse($response); + + $view->initHandlers(); + $expected = implode(',', [$this->term1->id(), $this->term2->id()]); + $this->assertEqual($expected, $view->argument['tid']->getDefaultArgument()); + } + + public function testTermPath() { + $view = Views::getView('taxonomy_default_argument_test'); + + $request = Request::create($this->term1->url()); + $request->server->set('SCRIPT_NAME', $GLOBALS['base_path'] . 'index.php'); + $request->server->set('SCRIPT_FILENAME', 'index.php'); + + $response = $this->container->get('http_kernel')->handle($request, HttpKernelInterface::SUB_REQUEST); + $view->setRequest($request); + $view->setResponse($response); + $view->initHandlers(); + + $expected = $this->term1->id(); + $this->assertEqual($expected, $view->argument['tid']->getDefaultArgument()); + } + /** * Tests escaping of page title when the taxonomy plugin provides it. */ diff --git a/core/modules/taxonomy/tests/src/Kernel/Views/TaxonomyDefaultArgumentTest.php b/core/modules/taxonomy/tests/src/Kernel/Views/TaxonomyDefaultArgumentTest.php deleted file mode 100644 index 3b9ba26d87..0000000000 --- a/core/modules/taxonomy/tests/src/Kernel/Views/TaxonomyDefaultArgumentTest.php +++ /dev/null @@ -1,93 +0,0 @@ -server->set('SCRIPT_NAME', $GLOBALS['base_path'] . 'index.php'); - $request->server->set('SCRIPT_FILENAME', 'index.php'); - - $response = $this->container->get('http_kernel') - ->handle($request, HttpKernelInterface::SUB_REQUEST); - - $view->setRequest($request); - $view->setResponse($response); - $view->initHandlers(); - - return $view; - } - - /** - * Tests the relationship. - */ - public function testNodePath() { - $view = $this->initViewWithRequest($this->nodes[0]->url()); - - $expected = implode(',', [$this->term1->id(), $this->term2->id()]); - $this->assertEqual($expected, $view->argument['tid']->getDefaultArgument()); - $view->destroy(); - } - - public function testNodePathWithViewSelection() { - // Change the term entity reference field to use a view as selection plugin. - \Drupal::service('module_installer')->install(['entity_reference_test']); - - $field_name = 'field_' . $this->vocabulary->id(); - $field = FieldConfig::loadByName('node', 'article', $field_name); - $field->setSetting('handler', 'views'); - $field->setSetting('handler_settings', [ - 'view' => [ - 'view_name' => 'test_entity_reference', - 'display_name' => 'entity_reference_1', - ], - ]); - $field->save(); - - $view = $this->initViewWithRequest($this->nodes[0]->url()); - - $expected = implode(',', [$this->term1->id(), $this->term2->id()]); - $this->assertEqual($expected, $view->argument['tid']->getDefaultArgument()); - } - - public function testTermPath() { - $view = $this->initViewWithRequest($this->term1->url()); - - $expected = $this->term1->id(); - $this->assertEqual($expected, $view->argument['tid']->getDefaultArgument()); - } - -} diff --git a/core/modules/taxonomy/tests/src/Kernel/Views/TaxonomyTestBase.php b/core/modules/taxonomy/tests/src/Kernel/Views/TaxonomyTestBase.php deleted file mode 100644 index a9f4c474be..0000000000 --- a/core/modules/taxonomy/tests/src/Kernel/Views/TaxonomyTestBase.php +++ /dev/null @@ -1,184 +0,0 @@ -installConfig(['node', 'filter']); - $this->installEntitySchema('user'); - $this->installEntitySchema('taxonomy_term'); - $this->mockStandardInstall(); - - if ($import_test_views) { - ViewTestData::createTestViews(get_class($this), ['taxonomy_test_views']); - } - - $this->term1 = $this->createTerm(); - $this->term2 = $this->createTerm(); - - $node = []; - $node['type'] = 'article'; - $node['field_views_testing_tags'][]['target_id'] = $this->term1->id(); - $node['field_views_testing_tags'][]['target_id'] = $this->term2->id(); - $this->nodes[] = $this->drupalCreateNode($node); - $this->nodes[] = $this->drupalCreateNode($node); - } - - /** - * Provides a workaround for the inability to use the standard profile. - * - * @see https://www.drupal.org/node/1708692 - */ - protected function mockStandardInstall() { - $this->drupalCreateContentType([ - 'type' => 'article', - ]); - - // Create the vocabulary for the tag field. - $this->vocabulary = Vocabulary::create([ - 'name' => 'Views testing tags', - 'vid' => 'views_testing_tags', - ]); - $this->vocabulary->save(); - $field_name = 'field_' . $this->vocabulary->id(); - - $handler_settings = [ - 'target_bundles' => [ - $this->vocabulary->id() => $this->vocabulary->id(), - ], - 'auto_create' => TRUE, - ]; - - $this->installEntitySchema('node'); - $this->createEntityReferenceField('node', 'article', $field_name, 'Tags', 'taxonomy_term', 'default', $handler_settings, FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED); - $entity_type_manager = $this->container->get('entity_type.manager'); - $entity_type_manager - ->getStorage('entity_form_display') - ->load('node.article.default') - ->setComponent($field_name, [ - 'type' => 'entity_reference_autocomplete_tags', - 'weight' => -4, - ]) - ->save(); - - $view_modes = [ - 'default', - 'teaser', - ]; - foreach ($view_modes as $view_mode) { - $entity_type_manager - ->getStorage('entity_view_display') - ->load("node.article.{$view_mode}") - ->setComponent($field_name, [ - 'type' => 'entity_reference_label', - 'weight' => 10, - ]) - ->save(); - } - } - - /** - * Creates and returns a taxonomy term. - * - * @param array $settings - * (optional) An array of values to override the following default - * properties of the term: - * - name: A random string. - * - description: A random string. - * - format: First available text format. - * - vid: Vocabulary ID of self::$vocabulary object. - * - langcode: LANGCODE_NOT_SPECIFIED. - * Defaults to an empty array. - * - * @return \Drupal\taxonomy\Entity\Term - * The created taxonomy term. - */ - protected function createTerm(array $settings = []) { - $filter_formats = filter_formats(); - $format = array_pop($filter_formats); - $settings += [ - 'name' => $this->randomMachineName(), - 'description' => $this->randomMachineName(), - // Use the first available text format. - 'format' => $format->id(), - 'vid' => $this->vocabulary->id(), - 'langcode' => LanguageInterface::LANGCODE_NOT_SPECIFIED, - ]; - $term = Term::create($settings); - $term->save(); - return $term; - } - -} diff --git a/core/modules/tour/tests/src/Unit/Plugin/tour/tip/TipPluginTextTest.php b/core/modules/tour/tests/src/Unit/Plugin/tour/tip/TipPluginTextTest.php index 65a6d51457..29ea50e468 100644 --- a/core/modules/tour/tests/src/Unit/Plugin/tour/tip/TipPluginTextTest.php +++ b/core/modules/tour/tests/src/Unit/Plugin/tour/tip/TipPluginTextTest.php @@ -15,13 +15,10 @@ class TipPluginTextTest extends UnitTestCase { * Tests that getAriaId returns unique id per plugin instance. * * @see \Drupal\tour\Plugin\tour\tip\TipPluginText::getAriaId() - * @runTestsInSeparateProcesses - * This test calls \Drupal\Component\Utility\Html::getUniqueId() which uses a - * static list. Run this test in a separate process to prevent side effects. */ public function testGetAriaId() { - $id_instance_one = 'one'; - $id_instance_two = 'two'; + $id_instance_one = $this->getRandomGenerator()->word(4, TRUE); + $id_instance_two = $this->getRandomGenerator()->word(4, TRUE); $config_instance_one = [ 'id' => $id_instance_one, ]; diff --git a/core/modules/views/src/Plugin/views/display/Block.php b/core/modules/views/src/Plugin/views/display/Block.php index e210ee3231..5e833d752a 100644 --- a/core/modules/views/src/Plugin/views/display/Block.php +++ b/core/modules/views/src/Plugin/views/display/Block.php @@ -2,8 +2,6 @@ namespace Drupal\views\Plugin\views\display; -use Drupal\Component\Plugin\Discovery\CachedDiscoveryInterface; -use Drupal\Core\Block\BlockManagerInterface; use Drupal\Core\Entity\EntityManagerInterface; use Drupal\Core\Form\FormStateInterface; use Drupal\views\Plugin\Block\ViewsBlock; @@ -44,13 +42,6 @@ class Block extends DisplayPluginBase { */ protected $entityManager; - /** - * The block manager. - * - * @var \Drupal\Core\Block\BlockManagerInterface - */ - protected $blockManager; - /** * Constructs a new Block instance. * @@ -62,14 +53,11 @@ class Block extends DisplayPluginBase { * The plugin implementation definition. * @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager * The entity manager. - * @param \Drupal\Core\Block\BlockManagerInterface $block_manager - * The block manager. */ - public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityManagerInterface $entity_manager, BlockManagerInterface $block_manager) { + public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityManagerInterface $entity_manager) { parent::__construct($configuration, $plugin_id, $plugin_definition); $this->entityManager = $entity_manager; - $this->blockManager = $block_manager; } /** @@ -80,8 +68,7 @@ public static function create(ContainerInterface $container, array $configuratio $configuration, $plugin_id, $plugin_definition, - $container->get('entity.manager'), - $container->get('plugin.manager.block') + $container->get('entity.manager') ); } @@ -377,9 +364,6 @@ public function remove() { $block->delete(); } } - if ($this->blockManager instanceof CachedDiscoveryInterface) { - $this->blockManager->clearCachedDefinitions(); - } } } diff --git a/core/modules/views_ui/tests/src/Functional/UITestBase.php b/core/modules/views_ui/tests/src/Functional/UITestBase.php index db85773714..684b91f89d 100644 --- a/core/modules/views_ui/tests/src/Functional/UITestBase.php +++ b/core/modules/views_ui/tests/src/Functional/UITestBase.php @@ -76,8 +76,8 @@ protected function drupalGet($path, array $options = [], array $headers = []) { $url = $this->buildUrl($path, $options); // Ensure that each nojs page is accessible via ajax as well. - if (strpos($url, '/nojs/') !== FALSE) { - $url = preg_replace('|/nojs/|', '/ajax/', $url, 1); + if (strpos($url, 'nojs') !== FALSE) { + $url = str_replace('nojs', 'ajax', $url); $result = $this->drupalGet($url, $options); $this->assertSession()->statusCodeEquals(200); $this->assertEquals('application/json', $this->getSession()->getResponseHeader('Content-Type')); diff --git a/core/profiles/demo_umami/themes/umami/templates/components/messages/status-messages.html.twig b/core/profiles/demo_umami/themes/umami/templates/components/messages/status-messages.html.twig index a5094701b4..1a65511a3d 100644 --- a/core/profiles/demo_umami/themes/umami/templates/components/messages/status-messages.html.twig +++ b/core/profiles/demo_umami/themes/umami/templates/components/messages/status-messages.html.twig @@ -19,6 +19,7 @@ * - class: HTML classes. */ #} +
{% block messages %} {% for type, messages in message_list %} {% @@ -53,3 +54,4 @@ {% set attributes = attributes.removeClass(classes) %} {% endfor %} {% endblock messages %} +
diff --git a/core/tests/Drupal/FunctionalJavascriptTests/Core/JsMessageTest.php b/core/tests/Drupal/FunctionalJavascriptTests/Core/JsMessageTest.php new file mode 100644 index 0000000000..14b4e8b625 --- /dev/null +++ b/core/tests/Drupal/FunctionalJavascriptTests/Core/JsMessageTest.php @@ -0,0 +1,119 @@ +install(['test_messages']); + $theme_config = \Drupal::configFactory()->getEditable('system.theme'); + $theme_config->set('default', 'test_messages'); + $theme_config->save(); + } + + /** + * Test click on links to show messages and remove messages. + */ + public function testAddRemoveMessages() { + $web_assert = $this->assertSession(); + $this->drupalGet('js_message_test_link'); + + $current_messages = []; + foreach (JsMessageTestCases::getMessagesSelectors() as $messagesSelector) { + $web_assert->elementExists('css', $messagesSelector); + foreach (JsMessageTestCases::getTypes() as $type) { + $this->click('[id="add-' . $messagesSelector . '-' . $type . '"]'); + $selector = "$messagesSelector .messages.messages--$type"; + $msg_element = $web_assert->waitForElementVisible('css', $selector); + $this->assertNotEmpty($msg_element, "Message element visible: $selector"); + $web_assert->elementContains('css', $selector, "This is a message of the type, $type. You be the the judge of its importance."); + $current_messages[$selector] = ucfirst($type) . " message This is a message of the type, $type. You be the the judge of its importance."; + $this->assertCurrentMessages($current_messages, $messagesSelector); + } + // Remove messages 1 by 1 and confirm the messages are expected. + foreach (JsMessageTestCases::getTypes() as $type) { + $this->click('[id="remove-' . $messagesSelector . '-' . $type . '"]'); + $selector = "$messagesSelector .messages.messages--$type"; + // The message for this selector should not be on the page. + unset($current_messages[$selector]); + $this->assertCurrentMessages($current_messages, $messagesSelector); + } + } + + $messagesSelector = JsMessageTestCases::getMessagesSelectors()[0]; + $current_messages = []; + $types = JsMessageTestCases::getTypes(); + $nb_messages = count($types) * 2; + for ($i = 0; $i < $nb_messages; $i++) { + $current_messages[] = ucfirst($types[$i % count($types)]) . " message This is message number $i of the type, {$types[$i % count($types)]}. You be the the judge of its importance."; + } + // Test adding multiple messages at once. + // @see processMessages() + $this->click('[id="add-multiple"]'); + $this->assertCurrentMessages($current_messages, $messagesSelector); + $this->click('[id="remove-multiple"]'); + $this->assertCurrentMessages([], $messagesSelector); + + $current_messages = []; + for ($i = 0; $i < $nb_messages; $i++) { + $current_messages[] = "Error message Msg-$i"; + } + // The last message is of a different type and shouldn't get cleared. + $last_message = 'Status message Msg-' . count($current_messages); + $current_messages[] = $last_message; + $this->click('[id="add-multiple-error"]'); + $this->assertCurrentMessages($current_messages, $messagesSelector); + $this->click('[id="remove-type"]'); + $this->assertCurrentMessages([$last_message], $messagesSelector); + $this->click('[id="clear-all"]'); + $this->assertCurrentMessages([], $messagesSelector); + + // Confirm that when adding a message with an "id" specified but no status + // that it receives the default status. + $this->click('[id="id-no-status"]'); + $no_status_msg = 'Status message Msg-id-no-status'; + $this->assertCurrentMessages([$no_status_msg], $messagesSelector); + $web_assert->elementTextContains('css', "$messagesSelector .messages--status[data-drupal-message-id=\"my-special-id\"]", $no_status_msg); + + } + + /** + * Asserts that currently shown messages match expected messages. + * + * @param array $expected_messages + * Expected messages. + * @param string $messagesSelector + * The css selector for the containing messages element. + */ + protected function assertCurrentMessages(array $expected_messages, $messagesSelector) { + $expected_messages = array_values($expected_messages); + $current_messages = []; + if ($message_divs = $this->getSession()->getPage()->findAll('css', "$messagesSelector .messages")) { + foreach ($message_divs as $message_div) { + /** @var \Behat\Mink\Element\NodeElement $message_div */ + $current_messages[] = $message_div->getText(); + } + } + $this->assertEquals($expected_messages, $current_messages); + } + +} diff --git a/core/tests/Drupal/FunctionalTests/Routing/DefaultFormatTest.php b/core/tests/Drupal/FunctionalTests/Routing/DefaultFormatTest.php deleted file mode 100644 index bf695d658b..0000000000 --- a/core/tests/Drupal/FunctionalTests/Routing/DefaultFormatTest.php +++ /dev/null @@ -1,32 +0,0 @@ -drupalGet('/default_format_test/human'); - $this->assertSame('format:html', $this->getSession()->getPage()->getContent()); - $this->assertSame('MISS', $this->drupalGetHeader('X-Drupal-Cache')); - - $this->drupalGet('/default_format_test/machine'); - $this->assertSame('format:json', $this->getSession()->getPage()->getContent()); - $this->assertSame('MISS', $this->drupalGetHeader('X-Drupal-Cache')); - } - - public function testMultipleRoutesWithSameSingleFormat() { - $this->drupalGet('/default_format_test/machine'); - $this->assertSame('format:json', $this->getSession()->getPage()->getContent()); - } - -} diff --git a/core/tests/Drupal/KernelTests/Core/Config/ConfigInstallTest.php b/core/tests/Drupal/KernelTests/Core/Config/ConfigInstallTest.php index 5efd20bbb1..9e473c29ff 100644 --- a/core/tests/Drupal/KernelTests/Core/Config/ConfigInstallTest.php +++ b/core/tests/Drupal/KernelTests/Core/Config/ConfigInstallTest.php @@ -253,14 +253,6 @@ public function testLanguage() { ); } - /** - * Tests installing configuration where the filename and ID do not match. - */ - public function testIdMisMatch() { - $this->setExpectedException(\PHPUnit_Framework_Error_Warning::class, 'The configuration name "config_test.dynamic.no_id_match" does not match the ID "does_not_match"'); - $this->installModules(['config_test_id_mismatch']); - } - /** * Installs a module. * diff --git a/core/tests/Drupal/KernelTests/Core/Theme/TwigEnvironmentTest.php b/core/tests/Drupal/KernelTests/Core/Theme/TwigEnvironmentTest.php index c95b0545b9..c1be007b16 100644 --- a/core/tests/Drupal/KernelTests/Core/Theme/TwigEnvironmentTest.php +++ b/core/tests/Drupal/KernelTests/Core/Theme/TwigEnvironmentTest.php @@ -111,35 +111,6 @@ public function testTemplateNotFoundException() { } } - /** - * Ensures that templates resolve to the same class name and cache file. - */ - public function testTemplateClassname() { - /** @var \Drupal\Core\Template\TwigEnvironment $environment */ - $environment = \Drupal::service('twig'); - - // Test using an include template path. - $name_include = 'container.html.twig'; - $class_include = $environment->getTemplateClass($name_include); - $key_include = $environment->getCache()->generateKey($name_include, $class_include); - - // Test using a namespaced template path. - $name_namespaced = '@system/container.html.twig'; - $class_namespaced = $environment->getTemplateClass($name_namespaced); - $key_namespaced = $environment->getCache()->generateKey($name_namespaced, $class_namespaced); - - // Test using a direct filesystem template path. - $name_direct = 'core/modules/system/templates/container.html.twig'; - $class_direct = $environment->getTemplateClass($name_direct); - $key_direct = $environment->getCache()->generateKey($name_direct, $class_direct); - - // All three should be equal for both cases. - $this->assertEqual($class_include, $class_namespaced); - $this->assertEqual($class_namespaced, $class_direct); - $this->assertEqual($key_include, $key_namespaced); - $this->assertEqual($key_namespaced, $key_direct); - } - /** * Ensures that cacheFilename() varies by extensions + deployment identifier. */ diff --git a/core/tests/Drupal/Tests/Core/Render/RendererTest.php b/core/tests/Drupal/Tests/Core/Render/RendererTest.php index 54037c57e9..adfc890cb0 100644 --- a/core/tests/Drupal/Tests/Core/Render/RendererTest.php +++ b/core/tests/Drupal/Tests/Core/Render/RendererTest.php @@ -89,26 +89,6 @@ public function providerTestRenderBasic() { ['#markup' => 'foo'], 'foo', ]; - // Basic #markup based renderable array with value '0'. - $data[] = [ - ['#markup' => '0'], - '0', - ]; - // Basic #markup based renderable array with value 0. - $data[] = [ - ['#markup' => 0], - '0', - ]; - // Basic #markup based renderable array with value ''. - $data[] = [ - ['#markup' => ''], - '', - ]; - // Basic #markup based renderable array with value NULL. - $data[] = [ - ['#markup' => NULL], - '', - ]; // Basic #plain_text based renderable array. $data[] = [ ['#plain_text' => 'foo'], @@ -124,26 +104,6 @@ public function providerTestRenderBasic() { ['#plain_text' => Markup::create('foo')], '<em>foo</em>', ]; - // #plain_text based renderable array with value '0'. - $data[] = [ - ['#plain_text' => '0'], - '0', - ]; - // #plain_text based renderable array with value 0. - $data[] = [ - ['#plain_text' => 0], - '0', - ]; - // #plain_text based renderable array with value ''. - $data[] = [ - ['#plain_text' => ''], - '', - ]; - // #plain_text based renderable array with value NULL. - $data[] = [ - ['#plain_text' => NULL], - '', - ]; // Renderable child element. $data[] = [ ['child' => ['#markup' => 'bar']], diff --git a/core/tests/Drupal/Tests/Core/StackMiddleware/NegotiationMiddlewareTest.php b/core/tests/Drupal/Tests/Core/StackMiddleware/NegotiationMiddlewareTest.php index 7b5217d4c5..10d2df8b8b 100644 --- a/core/tests/Drupal/Tests/Core/StackMiddleware/NegotiationMiddlewareTest.php +++ b/core/tests/Drupal/Tests/Core/StackMiddleware/NegotiationMiddlewareTest.php @@ -68,10 +68,10 @@ public function testFormatViaQueryParameter() { * * @covers ::getContentType */ - public function testUnknowContentTypeReturnsNull() { + public function testUnknowContentTypeReturnsHtmlByDefault() { $request = new Request(); - $this->assertNull($this->contentNegotiation->getContentType($request)); + $this->assertSame('html', $this->contentNegotiation->getContentType($request)); } /** @@ -83,7 +83,7 @@ public function testUnknowContentTypeButAjaxRequest() { $request = new Request(); $request->headers->set('X-Requested-With', 'XMLHttpRequest'); - $this->assertNull($this->contentNegotiation->getContentType($request)); + $this->assertSame('html', $this->contentNegotiation->getContentType($request)); } /** @@ -98,7 +98,7 @@ public function testHandle() { $request->setFormat()->shouldNotBeCalled(); // Request format will be set with default format. - $request->setRequestFormat()->shouldNotBeCalled(); + $request->setRequestFormat('html')->shouldBeCalled(); // Some getContentType calls we don't really care about but have to mock. $request_data = $this->prophesize(ParameterBag::class); @@ -127,7 +127,7 @@ public function testSetFormat() { $request->setFormat('david', 'geeky/david')->shouldBeCalled(); // Some calls we don't care about. - $request->setRequestFormat()->shouldNotBeCalled(); + $request->setRequestFormat('html')->shouldBeCalled(); $request_data = $this->prophesize(ParameterBag::class); $request_data->get('ajax_iframe_upload', FALSE)->shouldBeCalled(); $request_mock = $request->reveal(); diff --git a/core/themes/bartik/css/components/messages.css b/core/themes/bartik/css/components/messages.css index 15a550d0b9..7018da3a00 100644 --- a/core/themes/bartik/css/components/messages.css +++ b/core/themes/bartik/css/components/messages.css @@ -4,10 +4,15 @@ */ .messages__wrapper { - padding: 20px 0 5px 8px; + padding: 0 0 0 8px; font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; - margin: 8px 0; } [dir="rtl"] .messages__wrapper { - padding: 20px 8px 5px 0; + padding: 0 8px 0 0; +} +.messages:first-child { + margin-top: 28px; +} +.messages:last-child { + margin-bottom: 13px; } diff --git a/core/themes/classy/templates/misc/status-messages.html.twig b/core/themes/classy/templates/misc/status-messages.html.twig index 2115b76f53..7dda6c040c 100644 --- a/core/themes/classy/templates/misc/status-messages.html.twig +++ b/core/themes/classy/templates/misc/status-messages.html.twig @@ -19,6 +19,7 @@ * - class: HTML classes. */ #} +
{% block messages %} {% for type, messages in message_list %} {% @@ -51,3 +52,4 @@ {% set attributes = attributes.removeClass(classes) %} {% endfor %} {% endblock messages %} +
diff --git a/core/themes/stable/templates/misc/status-messages.html.twig b/core/themes/stable/templates/misc/status-messages.html.twig index 41d3fbd318..969631d2d0 100644 --- a/core/themes/stable/templates/misc/status-messages.html.twig +++ b/core/themes/stable/templates/misc/status-messages.html.twig @@ -19,6 +19,7 @@ * - class: HTML classes. */ #} +
{% for type, messages in message_list %}
{% if type == 'error' %} @@ -41,3 +42,4 @@ {% endif %}
{% endfor %} +