Update to Drupal 8.0.0-beta15. For more information, see: https://www.drupal.org/node/2563023

This commit is contained in:
Pantheon Automation 2015-09-04 13:20:09 -07:00 committed by Greg Anderson
parent 2720a9ec4b
commit f3791f1da3
1898 changed files with 54300 additions and 11481 deletions

View file

@ -36,3 +36,6 @@ views.argument_validator_entity:
multiple:
type: integer
label: 'Multiple arguments'
views.argument_validator.entity:*:
type: views.argument_validator_entity

View file

@ -41,10 +41,6 @@ views.sort_expose.date:
views.sort_expose.standard:
type: views_sort_expose
label: 'Standard sort expose settings'
mapping:
order:
type: string
label: 'Order'
views.sort_expose.random:
type: views.sort_expose.standard

View file

@ -8,10 +8,12 @@
"use strict";
/**
* Attaches the AJAX behavior to Views exposed filter forms and key View
* links.
* Attaches the AJAX behavior to exposed filters forms and key View links.
*
* @type {Drupal~behavior}
*
* @prop {Drupal~behaviorAttach} attach
* Attaches ajaxView functionality to relevant elements.
*/
Drupal.behaviors.ViewsAjaxView = {};
Drupal.behaviors.ViewsAjaxView.attach = function () {
@ -41,7 +43,9 @@
* @constructor
*
* @param {object} settings
* Settings object for the ajax view.
* @param {string} settings.view_dom_id
* The DOM id of the view.
*/
Drupal.views.ajaxView = function (settings) {
var selector = '.js-view-dom-id-' + settings.view_dom_id;
@ -142,8 +146,10 @@
/**
* Attach the ajax behavior to a singe link.
*
* @param {string} id
* @param {string} [id]
* The ID of the link.
* @param {HTMLElement} link
* The link element.
*/
Drupal.views.ajaxView.prototype.attachPagerLinkAjax = function (id, link) {
var $link = $(link);
@ -168,10 +174,14 @@
};
/**
* Views scroll to top ajax command.
*
* @param {Drupal.Ajax} [ajax]
* A {@link Drupal.ajax} object.
* @param {object} response
* Ajax response.
* @param {string} response.selector
* Selector to use.
*/
Drupal.AjaxCommands.prototype.viewsScrollTop = function (ajax, response) {
// Scroll to the top of the view. This will allow users

View file

@ -16,8 +16,10 @@
* Helper function to parse a querystring.
*
* @param {string} query
* The querystring to parse.
*
* @return {object}
* A map of query parameters.
*/
Drupal.Views.parseQueryString = function (query) {
var args = {};
@ -41,9 +43,12 @@
* Helper function to return a view's arguments based on a path.
*
* @param {string} href
* The href to check.
* @param {string} viewPath
* The views path to check.
*
* @return {object}
* An object containing `view_args` and `view_path`.
*/
Drupal.Views.parseViewArgs = function (href, viewPath) {
var returnObj = {};
@ -61,8 +66,10 @@
* Strip off the protocol plus domain from an href.
*
* @param {string} href
* The href to strip.
*
* @return {string}
* The href without the protocol and domain.
*/
Drupal.Views.pathPortion = function (href) {
// Remove e.g. http://example.com if present.
@ -78,8 +85,10 @@
* Return the Drupal path portion of an href.
*
* @param {string} href
* The href to check.
*
* @return {string}
* An internal path.
*/
Drupal.Views.getPath = function (href) {
href = Drupal.Views.pathPortion(href);

View file

@ -8,8 +8,12 @@
"use strict";
/**
* Attaches contextual region classes to views elements.
*
* @type {Drupal~behavior}
*
* @prop {Drupal~behaviorAttach} attach
* Adds class `contextual-region` to views elements.
*/
Drupal.behaviors.viewsContextualLinks = {
attach: function (context) {

View file

@ -38,6 +38,12 @@ class View extends RenderElement {
* View element pre render callback.
*/
public static function preRenderViewElement($element) {
// Allow specific Views displays to explicitly perform pre-rendering, for
// those displays that need to be able to know the fully built render array.
if (!empty($element['#pre_rendered'])) {
return $element;
}
if (!isset($element['#view'])) {
$view = Views::getView($element['#name']);
}
@ -58,7 +64,7 @@ class View extends RenderElement {
if ($view && $view->access($element['#display_id'])) {
if (!empty($element['#embed'])) {
$element += $view->preview($element['#display_id'], $element['#arguments']);
$element['view_build'] = $view->preview($element['#display_id'], $element['#arguments']);
}
else {
// Add contextual links to the view. We need to attach them to the dummy

View file

@ -16,7 +16,7 @@ use Drupal\views\ResultRow;
abstract class EntityTranslationRendererBase extends RendererBase {
/**
* Returns the language code associated to the given row.
* Returns the language code associated with the given row.
*
* @param \Drupal\views\ResultRow $row
* The result row.

View file

@ -353,7 +353,7 @@ class EntityViewsData implements EntityHandlerInterface, EntityViewsDataInterfac
case 'timestamp':
case 'created':
case 'changed':
$views_field['field']['id'] = 'date';
$views_field['field']['id'] = 'field';
$views_field['argument']['id'] = 'date';
$views_field['filter']['id'] = 'date';
$views_field['sort']['id'] = 'date';

View file

@ -7,10 +7,10 @@
namespace Drupal\views\Plugin\Block;
use Drupal\Component\Utility\SafeMarkup;
use Drupal\Component\Utility\Xss;
use Drupal\Core\Config\Entity\Query\Query;
use Drupal\Core\Form\FormStateInterface;
use Drupal\views\Element\View;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
@ -33,13 +33,26 @@ class ViewsBlock extends ViewsBlockBase {
if ($output = $this->view->buildRenderable($this->displayID, [], FALSE)) {
// Override the label to the dynamic title configured in the view.
if (empty($this->configuration['views_label']) && $this->view->getTitle()) {
// @todo https://www.drupal.org/node/2527360 remove call to SafeMarkup.
$output['#title'] = SafeMarkup::xssFilter($this->view->getTitle(), Xss::getAdminTagList());
$output['#title'] = ['#markup' => $this->view->getTitle(), '#allowed_tags' => Xss::getHtmlTagList()];
}
// Before returning the block output, convert it to a renderable array
// with contextual links.
$this->addContextualLinks($output);
// Block module expects to get a final render array, without another
// top-level #pre_render callback. So, here we make sure that Views'
// #pre_render callback has already been applied.
$output = View::preRenderViewElement($output);
// When view_build is empty, the actual render array output for this View
// is going to be empty. In that case, return just #cache, so that the
// render system knows the reasons (cache contexts & tags) why this Views
// block is empty, and can cache it accordingly.
if (empty($output['view_build'])) {
$output = ['#cache' => $output['#cache']];
}
return $output;
}

View file

@ -7,17 +7,10 @@
namespace Drupal\views\Plugin\EntityReferenceSelection;
use Drupal\Core\Database\Query\SelectInterface;
use Drupal\Core\Entity\EntityManagerInterface;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\Entity\Plugin\EntityReferenceSelection\SelectionBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Entity\EntityReferenceSelection\SelectionInterface;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\Core\Plugin\PluginBase;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\Url;
use Drupal\views\Views;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Plugin implementation of the 'selection' entity_reference.
@ -29,28 +22,7 @@ use Symfony\Component\DependencyInjection\ContainerInterface;
* weight = 0
* )
*/
class ViewsSelection extends PluginBase implements SelectionInterface, ContainerFactoryPluginInterface {
/**
* The entity manager.
*
* @var \Drupal\Core\Entity\EntityManagerInterface
*/
protected $entityManager;
/**
* The module handler service.
*
* @var \Drupal\Core\Extension\ModuleHandlerInterface
*/
protected $moduleHandler;
/**
* The current user.
*
* @var \Drupal\Core\Session\AccountInterface
*/
protected $currentUser;
class ViewsSelection extends SelectionBase {
/**
* The loaded View object.
@ -59,44 +31,6 @@ class ViewsSelection extends PluginBase implements SelectionInterface, Container
*/
protected $view;
/**
* Constructs a new ViewsSelection object.
*
* @param array $configuration
* A configuration array containing information about the plugin instance.
* @param string $plugin_id
* The plugin_id for the plugin instance.
* @param mixed $plugin_definition
* The plugin implementation definition.
* @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
* The entity manager service.
* @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
* The module handler service.
* @param \Drupal\Core\Session\AccountInterface $current_user
* The current user.
*/
public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityManagerInterface $entity_manager, ModuleHandlerInterface $module_handler, AccountInterface $current_user) {
parent::__construct($configuration, $plugin_id, $plugin_definition);
$this->entityManager = $entity_manager;
$this->moduleHandler = $module_handler;
$this->currentUser = $current_user;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
return new static(
$configuration,
$plugin_id,
$plugin_definition,
$container->get('entity.manager'),
$container->get('module_handler'),
$container->get('current_user')
);
}
/**
* {@inheritdoc}
*/
@ -161,16 +95,6 @@ class ViewsSelection extends PluginBase implements SelectionInterface, Container
return $form;
}
/**
* {@inheritdoc}
*/
public function validateConfigurationForm(array &$form, FormStateInterface $form_state) { }
/**
* {@inheritdoc}
*/
public function submitConfigurationForm(array &$form, FormStateInterface $form_state) { }
/**
* Initializes a view.
*
@ -227,7 +151,7 @@ class ViewsSelection extends PluginBase implements SelectionInterface, Container
$return = array();
if ($result) {
foreach($this->view->result as $row) {
foreach ($this->view->result as $row) {
$entity = $row->_entity;
$return[$entity->bundle()][$entity->id()] = $entity->label();
}
@ -259,18 +183,6 @@ class ViewsSelection extends PluginBase implements SelectionInterface, Container
return $result;
}
/**
* {@inheritdoc}
*/
public function validateAutocompleteInput($input, &$element, FormStateInterface $form_state, $form, $strict = TRUE) {
return NULL;
}
/**
* {@inheritdoc}
*/
public function entityQueryAlter(SelectInterface $query) {}
/**
* Element validate; Check View is valid.
*/
@ -300,4 +212,11 @@ class ViewsSelection extends PluginBase implements SelectionInterface, Container
$form_state->setValueForElement($element, $value);
}
/**
* {@inheritdoc}
*/
protected function buildEntityQuery($match = NULL, $match_operator = 'CONTAINS') {
throw new \BadMethodCallException('The Views selection plugin does not use the Entity Query system for entity selection.');
}
}

View file

@ -365,6 +365,12 @@ abstract class PluginBase extends ComponentPluginBase implements ContainerFactor
if (strpos($token, '{{') !== FALSE) {
// Twig wants a token replacement array stripped of curly-brackets.
$token = trim(str_replace(array('{', '}'), '', $token));
// We need to validate tokens are valid Twig variables. Twig uses the
// same variable naming rules as PHP.
// @see http://php.net/manual/en/language.variables.basics.php
assert('preg_match(\'/^[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*$/\', $token) === 1', 'Tokens need to be valid Twig variables.');
$twig_tokens[$token] = $replacement;
}
else {
@ -580,7 +586,7 @@ abstract class PluginBase extends ComponentPluginBase implements ContainerFactor
// Add real languages.
foreach ($languages as $id => $language) {
$list[$id] = $this->t($language->getName());
$list[$id] = $language->getName();
}
return $list;

View file

@ -9,6 +9,7 @@ namespace Drupal\views\Plugin\views\argument;
use Drupal\Component\Utility\SafeMarkup;
use Drupal\Core\Field\AllowedTagsXssTrait;
use Drupal\Core\Field\FieldFilteredString;
use Drupal\Core\Form\FormStateInterface;
use Drupal\views\ViewExecutable;
use Drupal\views\Plugin\views\display\DisplayPluginBase;
@ -70,7 +71,7 @@ class FieldList extends NumericArgument {
$value = $data->{$this->name_alias};
// If the list element has a human readable name show it,
if (isset($this->allowed_values[$value]) && !empty($this->options['summary']['human'])) {
return $this->fieldFilterXss($this->allowed_values[$value]);
return FieldFilteredString::create($this->allowed_values[$value]);
}
// else fallback to the key.
else {

View file

@ -9,6 +9,7 @@ namespace Drupal\views\Plugin\views\argument;
use Drupal\Component\Utility\SafeMarkup;
use Drupal\Core\Field\AllowedTagsXssTrait;
use Drupal\Core\Field\FieldFilteredString;
use Drupal\Core\Form\FormStateInterface;
use Drupal\views\ViewExecutable;
use Drupal\views\Plugin\views\display\DisplayPluginBase;
@ -72,12 +73,9 @@ class ListString extends StringArgument {
$value = $data->{$this->name_alias};
// If the list element has a human readable name show it,
if (isset($this->allowed_values[$value]) && !empty($this->options['summary']['human'])) {
return $this->caseTransform($this->fieldfilterXss($this->allowed_values[$value]), $this->options['case']);
}
// else fallback to the key.
else {
return $this->caseTransform(SafeMarkup::checkPlain($value), $this->options['case']);
$value = $this->allowed_values[$value];
}
return FieldFilteredString::create($this->caseTransform($value, $this->options['case']));
}
}

View file

@ -317,7 +317,7 @@ class StringArgument extends ArgumentPluginBase {
* Override for specific title lookups.
*/
public function titleQuery() {
return array_map('\Drupal\Component\Utility\SafeMarkup::checkPlain', array_combine($this->value, $this->value));
return $this->value;
}
public function summaryName($data) {

View file

@ -7,7 +7,6 @@
namespace Drupal\views\Plugin\views\display;
use Drupal\Component\Utility\SafeMarkup;
use Drupal\Core\Entity\EntityManagerInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\views\Plugin\Block\ViewsBlock;
@ -149,7 +148,7 @@ class Block extends DisplayPluginBase {
if (empty($block_description)) {
$block_description = $this->t('None');
}
$block_category = SafeMarkup::checkPlain($this->getOption('block_category'));
$block_category = $this->getOption('block_category');
$options['block_description'] = array(
'category' => 'block',

View file

@ -1062,7 +1062,7 @@ abstract class DisplayPluginBase extends PluginBase implements DisplayPluginInte
}
// Use strip tags as there should never be HTML in the path.
// However, we need to preserve special characters like " that
// were removed by SafeMarkup::checkPlain().
// were encoded by \Drupal\Component\Utility\Html::escape().
$tokens["!$count"] = isset($this->view->args[$count - 1]) ? strip_tags(Html::decodeEntities($this->view->args[$count - 1])) : '';
}
@ -1394,7 +1394,7 @@ abstract class DisplayPluginBase extends PluginBase implements DisplayPluginInte
if ($this->defaultableSections($section)) {
views_ui_standard_display_dropdown($form, $form_state, $section);
}
$form['#title'] = SafeMarkup::checkPlain($this->display['display_title']) . ': ';
$form['#title'] = $this->display['display_title'] . ': ';
// Set the 'section' to highlight on the form.
// If it's the item we're looking at is pulling from the default display,

View file

@ -99,7 +99,7 @@ class Feed extends PathPluginBase implements ResponseDisplayPluginInterface {
if (!empty($this->view->live_preview)) {
$output = array(
'#prefix' => '<pre>',
'#markup' => SafeMarkup::checkPlain(drupal_render_root($output)),
'#plain_text' => drupal_render_root($output),
'#suffix' => '</pre>',
);
}

View file

@ -7,7 +7,6 @@
namespace Drupal\views\Plugin\views\display;
use Drupal\Component\Utility\SafeMarkup;
use Drupal\Component\Utility\Xss;
use Drupal\Core\Entity\EntityStorageInterface;
use Drupal\Core\Form\FormStateInterface;
@ -182,8 +181,7 @@ class Page extends PathPluginBase {
// it should be dropped.
if (is_array($render)) {
$render += array(
// @todo https://www.drupal.org/node/2527360 remove call to SafeMarkup.
'#title' => SafeMarkup::xssFilter($this->view->getTitle(), Xss::getAdminTagList()),
'#title' => ['#markup' => $this->view->getTitle(), '#allowed_tags' => Xss::getHtmlTagList()],
);
}
return $render;

View file

@ -7,7 +7,7 @@
namespace Drupal\views\Plugin\views\field;
use Drupal\Component\Utility\Xss as CoreXss;
use Drupal\Component\Utility\Xss;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\EntityManagerInterface;
use Drupal\Core\Field\FieldStorageDefinitionInterface;
@ -670,7 +670,7 @@ class Field extends FieldPluginBase implements CacheablePluginInterface, MultiIt
if (!empty($items)) {
$items = $this->prepareItemsByDelta($items);
if ($this->options['multi_type'] == 'separator' || !$this->options['group_rows']) {
$separator = $this->options['multi_type'] == 'separator' ? CoreXss::filterAdmin($this->options['separator']) : '';
$separator = $this->options['multi_type'] == 'separator' ? Xss::filterAdmin($this->options['separator']) : '';
$build = [
'#type' => 'inline_template',
'#template' => '{{ items | safe_join(separator) }}',
@ -903,7 +903,7 @@ class Field extends FieldPluginBase implements CacheablePluginInterface, MultiIt
protected function documentSelfTokens(&$tokens) {
$field = $this->getFieldDefinition();
foreach ($field->getColumns() as $id => $column) {
$tokens['{{ ' . $this->options['id'] . '-' . $id . ' }}'] = $this->t('Raw @column', array('@column' => $id));
$tokens['{{ ' . $this->options['id'] . '__' . $id . ' }}'] = $this->t('Raw @column', array('@column' => $id));
}
}
@ -913,19 +913,29 @@ class Field extends FieldPluginBase implements CacheablePluginInterface, MultiIt
// Use \Drupal\Component\Utility\Xss::filterAdmin() because it's user data
// and we can't be sure it is safe. We know nothing about the data,
// though, so we can't really do much else.
if (isset($item['raw'])) {
// If $item['raw'] is an array then we can use as is, if it's an object
// we cast it to an array, if it's neither, we can't use it.
$raw = is_array($item['raw']) ? $item['raw'] :
(is_object($item['raw']) ? (array)$item['raw'] : NULL);
}
if (isset($raw) && isset($raw[$id]) && is_scalar($raw[$id])) {
$tokens['{{ ' . $this->options['id'] . '-' . $id . ' }}'] = CoreXss::filterAdmin($raw[$id]);
}
else {
// Make sure that empty values are replaced as well.
$tokens['{{ ' . $this->options['id'] . '-' . $id . ' }}'] = '';
$raw = $item['raw'];
if (is_array($raw)) {
if (isset($raw[$id]) && is_scalar($raw[$id])) {
$tokens['{{ ' . $this->options['id'] . '__' . $id . ' }}'] = Xss::filterAdmin($raw[$id]);
}
else {
// Make sure that empty values are replaced as well.
$tokens['{{ ' . $this->options['id'] . '__' . $id . ' }}'] = '';
}
}
if (is_object($raw)) {
$property = $raw->get($id);
if (!empty($property)) {
$tokens['{{ ' . $this->options['id'] . '__' . $id . ' }}'] = Xss::filterAdmin($property->getValue());
}
else {
// Make sure that empty values are replaced as well.
$tokens['{{ ' . $this->options['id'] . '__' . $id . ' }}'] = '';
}
}
}
}
}

View file

@ -167,8 +167,10 @@ interface FieldHandlerInterface extends ViewsHandlerInterface {
* @param \Drupal\views\ResultRow $values
* The values retrieved from a single row of a view's query result.
*
* @return string
* The rendered output.
* @return string|\Drupal\Component\Utility\SafeStringInterface
* The rendered output. If the output is safe it will be wrapped in an
* object that implements SafeStringInterface. If it is empty or unsafe it
* will be a string.
*
*/
public function render(ResultRow $values);
@ -202,8 +204,10 @@ interface FieldHandlerInterface extends ViewsHandlerInterface {
* @param \Drupal\views\ResultRow $values
* The values retrieved from a single row of a view's query result.
*
* @return string
* The advanced rendered output.
* @return string|\Drupal\Component\Utility\SafeStringInterface
* The advanced rendered output. If the output is safe it will be wrapped in
* an object that implements SafeStringInterface. If it is empty or unsafe
* it will be a string.
*
*/
public function advancedRender(ResultRow $values);
@ -236,29 +240,13 @@ interface FieldHandlerInterface extends ViewsHandlerInterface {
* - ellipsis: Show an ellipsis () at the end of the trimmed string.
* - html: Make sure that the html is correct.
*
* @return string
* The rendered string.
* @return string|\Drupal\Component\Utility\SafeStringInterface
* The rendered output. If the output is safe it will be wrapped in an
* object that implements SafeStringInterface. If it is empty or unsafe it
* will be a string.
*/
public function renderText($alter);
/**
* Trims the field down to the specified length.
*
* @param array $alter
* The alter array of options to use.
* - max_length: Maximum length of the string, the rest gets truncated.
* - word_boundary: Trim only on a word boundary.
* - ellipsis: Show an ellipsis () at the end of the trimmed string.
* - html: Make sure that the html is correct.
*
* @param string $value
* The string which should be trimmed.
*
* @return string
* The rendered trimmed string.
*/
public function renderTrimText($alter, $value);
/**
* Gets the 'render' tokens to use for advanced rendering.
*
@ -280,8 +268,10 @@ interface FieldHandlerInterface extends ViewsHandlerInterface {
* @param \Drupal\views\ResultRow $values
* Holds single row of a view's result set.
*
* @return string|false
* Returns rendered output of the given theme implementation.
* @return string|\Drupal\Component\Utility\SafeStringInterface
* Returns rendered output of the given theme implementation. If the output
* is safe it will be wrapped in an object that implements
* SafeStringInterface. If it is empty or unsafe it will be a string.
*/
function theme(ResultRow $values);

View file

@ -10,16 +10,17 @@ namespace Drupal\views\Plugin\views\field;
use Drupal\Component\Utility\Html;
use Drupal\Component\Utility\NestedArray;
use Drupal\Component\Utility\SafeMarkup;
use Drupal\Component\Utility\SafeStringInterface;
use Drupal\Component\Utility\Unicode;
use Drupal\Component\Utility\UrlHelper;
use Drupal\Component\Utility\Xss;
use Drupal\Core\Cache\Cache;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Render\Renderer;
use Drupal\Core\Render\SafeString;
use Drupal\Core\Url as CoreUrl;
use Drupal\views\Plugin\views\HandlerBase;
use Drupal\views\Plugin\views\display\DisplayPluginBase;
use Drupal\views\Render\ViewsRenderPipelineSafeString;
use Drupal\views\ResultRow;
use Drupal\views\ViewExecutable;
@ -241,7 +242,7 @@ abstract class FieldPluginBase extends HandlerBase implements FieldHandlerInterf
}
}
if ($this->options['element_type']) {
return SafeMarkup::checkPlain($this->options['element_type']);
return $this->options['element_type'];
}
if ($default_empty) {
@ -269,7 +270,7 @@ abstract class FieldPluginBase extends HandlerBase implements FieldHandlerInterf
}
}
if ($this->options['element_label_type']) {
return SafeMarkup::checkPlain($this->options['element_label_type']);
return $this->options['element_label_type'];
}
if ($default_empty) {
@ -289,7 +290,7 @@ abstract class FieldPluginBase extends HandlerBase implements FieldHandlerInterf
}
}
if ($this->options['element_wrapper_type']) {
return SafeMarkup::checkPlain($this->options['element_wrapper_type']);
return $this->options['element_wrapper_type'];
}
if ($default_empty) {
@ -1138,7 +1139,7 @@ abstract class FieldPluginBase extends HandlerBase implements FieldHandlerInterf
else {
$value = $this->render($values);
if (is_array($value)) {
$value = (string) $this->getRenderer()->render($value);
$value = $this->getRenderer()->render($value);
}
$this->last_render = $value;
$this->original_value = $value;
@ -1169,14 +1170,16 @@ abstract class FieldPluginBase extends HandlerBase implements FieldHandlerInterf
}
if (is_array($value)) {
$value = (string) $this->getRenderer()->render($value);
$value = $this->getRenderer()->render($value);
}
// This happens here so that renderAsLink can get the unaltered value of
// this field as a token rather than the altered value.
$this->last_render = $value;
}
if (empty($this->last_render)) {
// String cast is necessary to test emptiness of SafeStringInterface
// objects.
if (empty((string) $this->last_render)) {
if ($this->isValueEmpty($this->last_render, $this->options['empty_zero'], FALSE)) {
$alter = $this->options['alter'];
$alter['alter_text'] = 1;
@ -1185,9 +1188,6 @@ abstract class FieldPluginBase extends HandlerBase implements FieldHandlerInterf
$this->last_render = $this->renderText($alter);
}
}
// @todo Fix this in https://www.drupal.org/node/2280961.
$this->last_render = SafeMarkup::set($this->last_render);
return $this->last_render;
}
@ -1196,6 +1196,10 @@ abstract class FieldPluginBase extends HandlerBase implements FieldHandlerInterf
* {@inheritdoc}
*/
public function isValueEmpty($value, $empty_zero, $no_skip_empty = TRUE) {
// Convert SafeStringInterface to a string for checking.
if ($value instanceof SafeStringInterface) {
$value = (string) $value;
}
if (!isset($value)) {
$empty = TRUE;
}
@ -1213,7 +1217,14 @@ abstract class FieldPluginBase extends HandlerBase implements FieldHandlerInterf
* {@inheritdoc}
*/
public function renderText($alter) {
$value = $this->last_render;
// We need to preserve the safeness of the value regardless of the
// alterations made by this method. Any alterations or replacements made
// within this method need to ensure that at the minimum the result is
// XSS admin filtered. See self::renderAltered() as an example that does.
$value_is_safe = SafeMarkup::isSafe($this->last_render);
// Cast to a string so that empty checks and string functions work as
// expected.
$value = (string) $this->last_render;
if (!empty($alter['alter_text']) && $alter['text'] !== '') {
$tokens = $this->getRenderTokens($alter);
@ -1239,6 +1250,9 @@ abstract class FieldPluginBase extends HandlerBase implements FieldHandlerInterf
if ($alter['phase'] == static::RENDER_TEXT_PHASE_EMPTY && $no_rewrite_for_empty) {
// If we got here then $alter contains the value of "No results text"
// and so there is nothing left to do.
if ($value_is_safe) {
$value = ViewsRenderPipelineSafeString::create($value);
}
return $value;
}
@ -1275,6 +1289,12 @@ abstract class FieldPluginBase extends HandlerBase implements FieldHandlerInterf
if (!empty($alter['nl2br'])) {
$value = nl2br($value);
}
// Preserve whether or not the string is safe. Since $suffix comes from
// \Drupal::l(), it is safe to append.
if ($value_is_safe) {
$value = ViewsRenderPipelineSafeString::create($value . $suffix);
}
$this->last_render_text = $value;
if (!empty($alter['make_link']) && (!empty($alter['path']) || !empty($alter['url']))) {
@ -1284,20 +1304,42 @@ abstract class FieldPluginBase extends HandlerBase implements FieldHandlerInterf
$value = $this->renderAsLink($alter, $value, $tokens);
}
return $value . $suffix;
// Preserve whether or not the string is safe. Since $suffix comes from
// \Drupal::l(), it is safe to append.
if ($value_is_safe) {
return ViewsRenderPipelineSafeString::create($value . $suffix);
}
else {
// If the string is not already marked safe, it is still OK to return it
// because it will be sanitized by Twig.
return $value . $suffix;
}
}
/**
* Render this field as user-defined altered text.
*/
protected function renderAltered($alter, $tokens) {
return SafeString::create($this->viewsTokenReplace($alter['text'], $tokens));
return $this->viewsTokenReplace($alter['text'], $tokens);
}
/**
* {@inheritdoc}
* Trims the field down to the specified length.
*
* @param array $alter
* The alter array of options to use.
* - max_length: Maximum length of the string, the rest gets truncated.
* - word_boundary: Trim only on a word boundary.
* - ellipsis: Show an ellipsis () at the end of the trimmed string.
* - html: Make sure that the html is correct.
*
* @param string $value
* The string which should be trimmed.
*
* @return string
* The rendered trimmed string.
*/
public function renderTrimText($alter, $value) {
protected function renderTrimText($alter, $value) {
if (!empty($alter['strip_tags'])) {
// NOTE: It's possible that some external fields might override the
// element type.
@ -1634,10 +1676,11 @@ abstract class FieldPluginBase extends HandlerBase implements FieldHandlerInterf
* fields as a list. For example, the field that displays all terms
* on a node might have tokens for the tid and the term.
*
* By convention, tokens should follow the format of {{ token-subtoken }}
* By convention, tokens should follow the format of {{ token
* subtoken }}
* where token is the field ID and subtoken is the field. If the
* field ID is terms, then the tokens might be {{ terms-tid }} and
* {{ terms-name }}.
* field ID is terms, then the tokens might be {{ terms__tid }} and
* {{ terms__name }}.
*/
protected function addSelfTokens(&$tokens, $item) { }

View file

@ -766,7 +766,7 @@ abstract class FilterPluginBase extends HandlerBase implements CacheablePluginIn
$value = $this->options['group_info']['identifier'];
$form[$value] = array(
'#title' => SafeMarkup::checkPlain($this->options['group_info']['label']),
'#title' => $this->options['group_info']['label'],
'#type' => $this->options['group_info']['widget'],
'#default_value' => $this->group_info,
'#options' => $groups,

View file

@ -1454,7 +1454,7 @@ class Sql extends QueryPluginBase {
drupal_set_message($e->getMessage(), 'error');
}
else {
throw new DatabaseExceptionWrapper("Exception in {$view->storage->label()}[$view->storage->id()]: {$e->getMessage()}");
throw new DatabaseExceptionWrapper("Exception in {$view->storage->label()}[{$view->storage->id()}]: {$e->getMessage()}");
}
}

View file

@ -220,7 +220,6 @@ abstract class SortPluginBase extends HandlerBase implements CacheablePluginInte
*/
public function defaultExposeOptions() {
$this->options['expose'] = array(
'order' => $this->options['order'],
'label' => $this->definition['title'],
);
}

View file

@ -103,7 +103,8 @@ abstract class Mapping extends StylePluginBase {
// Optionally filter the available fields.
if (isset($mapping[$key]['#filter'])) {
$this->view->initHandlers();
$this::$mapping[$key]['#filter']($field_options);
$filter = $mapping[$key]['#filter'];
$this::$filter($field_options);
unset($mapping[$key]['#filter']);
}

View file

@ -11,10 +11,10 @@ use Drupal\Component\Utility\Html;
use Drupal\Component\Utility\Xss;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Render\Element;
use Drupal\Core\Render\SafeString;
use Drupal\views\Plugin\views\display\DisplayPluginBase;
use Drupal\views\Plugin\views\PluginBase;
use Drupal\views\Plugin\views\wizard\WizardInterface;
use Drupal\views\Render\ViewsRenderPipelineSafeString;
use Drupal\views\ViewExecutable;
/**
@ -708,7 +708,7 @@ abstract class StylePluginBase extends PluginBase {
foreach ($this->rendered_fields[$index] as &$rendered_field) {
// Placeholders and rendered fields have been processed by the
// render system and are therefore safe.
$rendered_field = SafeString::create(str_replace($placeholders, $values, $rendered_field));
$rendered_field = ViewsRenderPipelineSafeString::create(str_replace($placeholders, $values, $rendered_field));
}
}
}
@ -745,7 +745,7 @@ abstract class StylePluginBase extends PluginBase {
* @param string $field
* The ID of the field.
*
* @return \Drupal\Core\Render\SafeString|null
* @return \Drupal\Component\Utility\SafeStringInterface|null
* The output of the field, or NULL if it was empty.
*/
public function getField($index, $field) {

View file

@ -232,7 +232,7 @@ class Table extends StylePluginBase implements CacheablePluginInterface {
$form['caption'] = array(
'#type' => 'textfield',
'#title' => $this->t('Caption for the table'),
'#description' => $this->t('A title which is semantically associated to your table for increased accessibility.'),
'#description' => $this->t('A title semantically associated with your table for increased accessibility.'),
'#default_value' => $this->options['caption'],
'#maxlength' => 255,
);

View file

@ -0,0 +1,28 @@
<?php
/**
* @file
* Contains \Drupal\views\Render\ViewsRenderPipelineSafeString.
*/
namespace Drupal\views\Render;
use Drupal\Component\Utility\SafeStringInterface;
use Drupal\Component\Utility\SafeStringTrait;
/**
* Defines an object that passes safe strings through the Views render system.
*
* This object should only be constructed with a known safe string. If there is
* any risk that the string contains user-entered data that has not been
* filtered first, it must not be used.
*
* @internal
* This object is marked as internal because it should only be used in the
* Views render pipeline.
*
* @see \Drupal\Core\Render\SafeString
*/
final class ViewsRenderPipelineSafeString implements SafeStringInterface, \Countable {
use SafeStringTrait;
}

View file

@ -14,7 +14,7 @@ use Drupal\views\Views;
*
* @group views
*/
class BasicTest extends ViewUnitTestBase {
class BasicTest extends ViewKernelTestBase {
/**
* Views used by this test.

View file

@ -57,7 +57,7 @@ class FieldEntityTest extends ViewTestBase {
$account = entity_create('user', array('name' => $this->randomMachineName(), 'bundle' => 'user'));
$account->save();
$node = entity_create('node', array('uid' => $account->id(), 'type' => 'page'));
$node = entity_create('node', array('uid' => $account->id(), 'type' => 'page', 'title' => $this->randomString()));
$node->save();
$comment = entity_create('comment', array(
'uid' => $account->id(),

View file

@ -60,7 +60,7 @@ class FilterEntityBundleTest extends ViewTestBase {
foreach ($this->entityBundles as $key => $info) {
for ($i = 0; $i < 5; $i++) {
$entity = entity_create('node', array('label' => $this->randomMachineName(), 'uid' => 1, 'type' => $key));
$entity = entity_create('node', array('title' => $this->randomString(), 'uid' => 1, 'type' => $key));
$entity->save();
$this->entities[$key][$entity->id()] = $entity;
$this->entities['count']++;

View file

@ -9,7 +9,7 @@ namespace Drupal\views\Tests\Entity;
use Drupal\language\Entity\ConfigurableLanguage;
use Drupal\node\Entity\NodeType;
use Drupal\views\Tests\ViewUnitTestBase;
use Drupal\views\Tests\ViewKernelTestBase;
use Drupal\views\Views;
/**
@ -18,7 +18,7 @@ use Drupal\views\Views;
* @group views
* @see \Drupal\views\Entity\Render\RendererBase
*/
class RowEntityRenderersTest extends ViewUnitTestBase {
class RowEntityRenderersTest extends ViewKernelTestBase {
/**
* Modules to enable.

View file

@ -10,7 +10,7 @@ namespace Drupal\views\Tests\Entity;
use Drupal\Component\Utility\Unicode;
use Drupal\field\Entity\FieldStorageConfig;
use Drupal\views\Tests\ViewTestData;
use Drupal\views\Tests\ViewUnitTestBase;
use Drupal\views\Tests\ViewKernelTestBase;
use Drupal\views\Views;
/**
@ -18,7 +18,7 @@ use Drupal\views\Views;
*
* @group views
*/
class ViewEntityDependenciesTest extends ViewUnitTestBase {
class ViewEntityDependenciesTest extends ViewKernelTestBase {
/**
* Views used by this test.

View file

@ -11,14 +11,14 @@ use Drupal\Core\Entity\EntityTypeEvent;
use Drupal\Core\Entity\EntityTypeEvents;
use Drupal\system\Tests\Entity\EntityDefinitionTestTrait;
use Drupal\views\Entity\View;
use Drupal\views\Tests\ViewUnitTestBase;
use Drupal\views\Tests\ViewKernelTestBase;
/**
* Tests \Drupal\views\EventSubscriber\ViewsEntitySchemaSubscriber
*
* @group Views
*/
class ViewsEntitySchemaSubscriberIntegrationTest extends ViewUnitTestBase {
class ViewsEntitySchemaSubscriberIntegrationTest extends ViewKernelTestBase {
use EntityDefinitionTestTrait;

View file

@ -74,6 +74,7 @@ class GlossaryTest extends ViewTestBase {
$this->assertPageCacheContextsAndTags(
$url,
[
'timezone',
'languages:' . LanguageInterface::TYPE_CONTENT,
'languages:' . LanguageInterface::TYPE_INTERFACE,
'theme',

View file

@ -11,7 +11,7 @@ use Drupal\block\Entity\Block;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Form\FormState;
use Drupal\views\Entity\View;
use Drupal\views\Tests\ViewUnitTestBase;
use Drupal\views\Tests\ViewKernelTestBase;
use Drupal\views\Views;
/**
@ -20,7 +20,7 @@ use Drupal\views\Views;
* @group views
* @see \Drupal\views\Plugin\views\area\Entity
*/
class AreaEntityTest extends ViewUnitTestBase {
class AreaEntityTest extends ViewKernelTestBase {
/**
* Modules to enable.
@ -128,8 +128,8 @@ class AreaEntityTest extends ViewUnitTestBase {
$preview = $view->preview('default', [$entities[1]->id()]);
$this->setRawContent(\Drupal::service('renderer')->renderRoot($preview));
$view_class = 'js-view-dom-id-' . $view->dom_id;
$header_xpath = '//div[@class = "' . $view_class . '"]/div[1]';
$footer_xpath = '//div[@class = "' . $view_class . '"]/div[3]';
$header_xpath = '//div[@class = "' . $view_class . '"]/header[1]';
$footer_xpath = '//div[@class = "' . $view_class . '"]/footer[1]';
$result = $this->xpath($header_xpath);
$this->assertTrue(strpos(trim((string) $result[0]), $entities[0]->label()) !== FALSE, 'The rendered entity appears in the header of the view.');
@ -164,7 +164,7 @@ class AreaEntityTest extends ViewUnitTestBase {
$preview = $view->preview('default', array($entities[1]->id()));
$this->setRawContent($renderer->renderRoot($preview));
$view_class = 'js-view-dom-id-' . $view->dom_id;
$result = $this->xpath('//div[@class = "' . $view_class . '"]/div[1]');
$result = $this->xpath('//div[@class = "' . $view_class . '"]/header[1]');
$this->assertTrue(strpos(trim((string) $result[0]), $entities[0]->label()) !== FALSE, 'The rendered entity appears in the header of the view.');
$this->assertTrue(strpos(trim((string) $result[0]), 'test') !== FALSE, 'The rendered entity appeared in the right view mode.');
@ -173,7 +173,7 @@ class AreaEntityTest extends ViewUnitTestBase {
$preview = $view->preview('default', array($entities[2]->id()));
$this->setRawContent($renderer->renderRoot($preview));
$view_class = 'js-view-dom-id-' . $view->dom_id;
$result = $this->xpath('//div[@class = "' . $view_class . '"]/div[3]');
$result = $this->xpath('//div[@class = "' . $view_class . '"]/footer[1]');
$this->assertTrue(strpos($result[0], $entities[2]->label()) === FALSE, 'The rendered entity does not appear in the footer of the view.');
// Test the available view mode options.

View file

@ -7,7 +7,7 @@
namespace Drupal\views\Tests\Handler;
use Drupal\views\Tests\ViewUnitTestBase;
use Drupal\views\Tests\ViewKernelTestBase;
use Drupal\views\Views;
/**
@ -16,7 +16,7 @@ use Drupal\views\Views;
* @group views
* @see \Drupal\views\Plugin\views\area\Text
*/
class AreaTextTest extends ViewUnitTestBase {
class AreaTextTest extends ViewKernelTestBase {
public static $modules = array('system', 'user', 'filter');

View file

@ -7,7 +7,7 @@
namespace Drupal\views\Tests\Handler;
use Drupal\views\Tests\ViewUnitTestBase;
use Drupal\views\Tests\ViewKernelTestBase;
use Drupal\views\Views;
/**
@ -16,7 +16,7 @@ use Drupal\views\Views;
* @group views
* @see \Drupal\views\Plugin\views\area\Title
*/
class AreaTitleTest extends ViewUnitTestBase {
class AreaTitleTest extends ViewKernelTestBase {
/**
* Views used by this test.

View file

@ -7,7 +7,7 @@
namespace Drupal\views\Tests\Handler;
use Drupal\views\Tests\ViewUnitTestBase;
use Drupal\views\Tests\ViewKernelTestBase;
use Drupal\views\Views;
/**
@ -16,7 +16,7 @@ use Drupal\views\Views;
* @group views
* @see \Drupal\views\Plugin\views\area\View
*/
class AreaViewTest extends ViewUnitTestBase {
class AreaViewTest extends ViewKernelTestBase {
/**
* Modules to enable.

View file

@ -7,7 +7,7 @@
namespace Drupal\views\Tests\Handler;
use Drupal\views\Tests\ViewUnitTestBase;
use Drupal\views\Tests\ViewKernelTestBase;
use Drupal\views\Views;
/**
@ -16,7 +16,7 @@ use Drupal\views\Views;
* @group views
* @see \Drupal\views\Plugin\views\argument\Date
*/
class ArgumentDateTest extends ViewUnitTestBase {
class ArgumentDateTest extends ViewKernelTestBase {
/**
* Views used by this test.
@ -35,7 +35,7 @@ class ArgumentDateTest extends ViewUnitTestBase {
);
/**
* Overrides \Drupal\views\Tests\ViewUnitTestBase::viewsData().
* Overrides \Drupal\views\Tests\ViewKernelTestBase::viewsData().
*/
public function viewsData() {
$data = parent::viewsData();

View file

@ -7,7 +7,7 @@
namespace Drupal\views\Tests\Handler;
use Drupal\views\Tests\ViewUnitTestBase;
use Drupal\views\Tests\ViewKernelTestBase;
use Drupal\views\Views;
/**
@ -15,7 +15,7 @@ use Drupal\views\Views;
*
* @group views
*/
class ArgumentNullTest extends ViewUnitTestBase {
class ArgumentNullTest extends ViewKernelTestBase {
/**
* Views used by this test.

View file

@ -7,7 +7,7 @@
namespace Drupal\views\Tests\Handler;
use Drupal\views\Tests\ViewUnitTestBase;
use Drupal\views\Tests\ViewKernelTestBase;
use Drupal\views\Views;
/**
@ -15,7 +15,7 @@ use Drupal\views\Views;
*
* @group views
*/
class FieldBooleanTest extends ViewUnitTestBase {
class FieldBooleanTest extends ViewKernelTestBase {
/**
* Views used by this test.

View file

@ -7,7 +7,7 @@
namespace Drupal\views\Tests\Handler;
use Drupal\views\Tests\ViewUnitTestBase;
use Drupal\views\Tests\ViewKernelTestBase;
use Drupal\views\Views;
/**
@ -15,7 +15,7 @@ use Drupal\views\Views;
*
* @group views
*/
class FieldCounterTest extends ViewUnitTestBase {
class FieldCounterTest extends ViewKernelTestBase {
/**
* Modules to enable.

View file

@ -7,7 +7,7 @@
namespace Drupal\views\Tests\Handler;
use Drupal\views\Tests\ViewUnitTestBase;
use Drupal\views\Tests\ViewKernelTestBase;
use Drupal\views\Views;
/**
@ -15,7 +15,7 @@ use Drupal\views\Views;
*
* @group views
*/
class FieldCustomTest extends ViewUnitTestBase {
class FieldCustomTest extends ViewKernelTestBase {
/**
* Views used by this test.

View file

@ -7,7 +7,7 @@
namespace Drupal\views\Tests\Handler;
use Drupal\views\Tests\ViewUnitTestBase;
use Drupal\views\Tests\ViewKernelTestBase;
use Drupal\views\Views;
/**
@ -15,7 +15,7 @@ use Drupal\views\Views;
*
* @group views
*/
class FieldDateTest extends ViewUnitTestBase {
class FieldDateTest extends ViewKernelTestBase {
/**
* Views used by this test.

View file

@ -10,7 +10,8 @@ namespace Drupal\views\Tests\Handler;
use Drupal\Core\Session\AccountInterface;
use Drupal\entity_test\Entity\EntityTest;
use Drupal\simpletest\UserCreationTrait;
use Drupal\views\Tests\ViewUnitTestBase;
use Drupal\user\Entity\Role;
use Drupal\views\Tests\ViewKernelTestBase;
use Drupal\views\Views;
/**
@ -18,7 +19,7 @@ use Drupal\views\Views;
*
* @group views
*/
class FieldEntityLinkTest extends ViewUnitTestBase {
class FieldEntityLinkTest extends ViewKernelTestBase {
use UserCreationTrait;
@ -51,6 +52,7 @@ class FieldEntityLinkTest extends ViewUnitTestBase {
$this->installEntitySchema('user');
$this->installEntitySchema('entity_test');
$this->installConfig(['user']);
// Create some test entities.
for ($i = 0; $i < 5; $i++) {
@ -58,7 +60,11 @@ class FieldEntityLinkTest extends ViewUnitTestBase {
}
// Create and admin user.
$this->adminUser = $this->createUser([], FALSE, TRUE);
$this->adminUser = $this->createUser(['view test entity'], FALSE, TRUE);
Role::load(AccountInterface::ANONYMOUS_ROLE)
->grantPermission('view test entity')
->save();
}
/**

View file

@ -10,13 +10,13 @@ namespace Drupal\views\Tests\Handler;
use Drupal\user\Entity\Role;
use Drupal\user\Entity\User;
use Drupal\views\Entity\View;
use Drupal\views\Tests\ViewUnitTestBase;
use Drupal\views\Tests\ViewKernelTestBase;
use Drupal\views\Views;
/**
* Provides a base class for base field access in views.
*/
abstract class FieldFieldAccessTestBase extends ViewUnitTestBase {
abstract class FieldFieldAccessTestBase extends ViewKernelTestBase {
/**
* Stores an user entity with access to fields.

View file

@ -14,7 +14,7 @@ use Drupal\field\Entity\FieldConfig;
use Drupal\field\Entity\FieldStorageConfig;
use Drupal\user\Entity\User;
use Drupal\views\Plugin\views\field\Field;
use Drupal\views\Tests\ViewUnitTestBase;
use Drupal\views\Tests\ViewKernelTestBase;
use Drupal\views\Views;
/**
@ -23,7 +23,7 @@ use Drupal\views\Views;
* @see \Drupal\views\Plugin\views\field\Field
* @group views
*/
class FieldFieldTest extends ViewUnitTestBase {
class FieldFieldTest extends ViewKernelTestBase {
/**
* {@inheritdoc}
@ -74,7 +74,7 @@ class FieldFieldTest extends ViewUnitTestBase {
$this->installEntitySchema('entity_test_rev');
// Bypass any field access.
$this->adminUser = User::create();
$this->adminUser = User::create(['name' => $this->randomString()]);
$this->adminUser->save();
$this->container->get('current_user')->setAccount($this->adminUser);
@ -531,7 +531,7 @@ class FieldFieldTest extends ViewUnitTestBase {
$executable = Views::getView('test_field_field_test');
$executable->execute();
$this->assertEqual('', $executable->getStyle()->getField(1, 'field_test'));
$this->assertEqual('', $executable->getStyle()->getField(6, 'field_test'));
}
}

View file

@ -7,7 +7,7 @@
namespace Drupal\views\Tests\Handler;
use Drupal\views\Tests\ViewUnitTestBase;
use Drupal\views\Tests\ViewKernelTestBase;
use Drupal\views\Views;
/**
@ -16,7 +16,7 @@ use Drupal\views\Views;
* @group views
* @see CommonXssUnitTest
*/
class FieldFileSizeTest extends ViewUnitTestBase {
class FieldFileSizeTest extends ViewKernelTestBase {
/**
* Views used by this test.
@ -64,9 +64,9 @@ class FieldFileSizeTest extends ViewUnitTestBase {
// Test with the bytes option.
$view->field['age']->options['file_size_display'] = 'bytes';
$this->assertEqual($view->field['age']->advancedRender($view->result[0]), '');
$this->assertEqual($view->field['age']->advancedRender($view->result[1]), 10);
$this->assertEqual($view->field['age']->advancedRender($view->result[2]), 1000);
$this->assertEqual($view->field['age']->advancedRender($view->result[3]), 10000);
$this->assertEqual($view->field['age']->advancedRender($view->result[1]), '10');
$this->assertEqual($view->field['age']->advancedRender($view->result[2]), '1000');
$this->assertEqual($view->field['age']->advancedRender($view->result[3]), '10000');
}
}

View file

@ -0,0 +1,757 @@
<?php
/**
* @file
* Contains \Drupal\views\Tests\Handler\FieldKernelTest.
*/
namespace Drupal\views\Tests\Handler;
use Drupal\Core\Render\RenderContext;
use Drupal\views\Tests\ViewKernelTestBase;
use Drupal\views\Plugin\views\field\FieldPluginBase;
use Drupal\views\Views;
/**
* Tests the generic field handler.
*
* @group views
* @see \Drupal\views\Plugin\views\field\FieldPluginBase
*/
class FieldKernelTest extends ViewKernelTestBase {
public static $modules = array('user');
/**
* Views used by this test.
*
* @var array
*/
public static $testViews = array('test_view', 'test_field_tokens', 'test_field_output');
/**
* Map column names.
*
* @var array
*/
protected $columnMap = array(
'views_test_data_name' => 'name',
);
/**
* Overrides Drupal\views\Tests\ViewTestBase::viewsData().
*/
protected function viewsData() {
$data = parent::viewsData();
$data['views_test_data']['job']['field']['id'] = 'test_field';
$data['views_test_data']['job']['field']['click sortable'] = FALSE;
$data['views_test_data']['id']['field']['click sortable'] = TRUE;
return $data;
}
/**
* Tests that the render function is called.
*/
public function testRender() {
/** @var \Drupal\Core\Render\RendererInterface $renderer */
$renderer = \Drupal::service('renderer');
$view = Views::getView('test_field_tokens');
$this->executeView($view);
$random_text = $this->randomMachineName();
$view->field['job']->setTestValue($random_text);
$output = $renderer->executeInRenderContext(new RenderContext(), function () use ($view) {
return $view->field['job']->theme($view->result[0]);
});
$this->assertEqual($output, $random_text, 'Make sure the render method rendered the manual set value.');
}
/**
* Tests all things related to the query.
*/
public function testQuery() {
// Tests adding additional fields to the query.
$view = Views::getView('test_view');
$view->initHandlers();
$id_field = $view->field['id'];
$id_field->additional_fields['job'] = 'job';
// Choose also a field alias key which doesn't match to the table field.
$id_field->additional_fields['created_test'] = array('table' => 'views_test_data', 'field' => 'created');
$view->build();
// Make sure the field aliases have the expected value.
$this->assertEqual($id_field->aliases['job'], 'views_test_data_job');
$this->assertEqual($id_field->aliases['created_test'], 'views_test_data_created');
$this->executeView($view);
// Tests the getValue method with and without a field aliases.
foreach ($this->dataSet() as $key => $row) {
$id = $key + 1;
$result = $view->result[$key];
$this->assertEqual($id_field->getValue($result), $id);
$this->assertEqual($id_field->getValue($result, 'job'), $row['job']);
$this->assertEqual($id_field->getValue($result, 'created_test'), $row['created']);
}
}
/**
* Asserts that a string is part of another string.
*
* @param string $haystack
* The value to search in.
* @param string $needle
* The value to search for.
* @param string $message
* (optional) A message to display with the assertion. Do not translate
* messages: use format_string() to embed variables in the message text, not
* t(). If left blank, a default message will be displayed.
* @param string $group
* (optional) The group this message is in, which is displayed in a column
* in test output. Use 'Debug' to indicate this is debugging output. Do not
* translate this string. Defaults to 'Other'; most tests do not override
* this default.
*
* @return bool
* TRUE if the assertion succeeded, FALSE otherwise.
*/
protected function assertSubString($haystack, $needle, $message = '', $group = 'Other') {
return $this->assertTrue(strpos($haystack, $needle) !== FALSE, $message, $group);
}
/**
* Asserts that a string is not part of another string.
*
* @param string $haystack
* The value to search in.
* @param string $needle
* The value to search for.
* @param string $message
* (optional) A message to display with the assertion. Do not translate
* messages: use format_string() to embed variables in the message text, not
* t(). If left blank, a default message will be displayed.
* @param string $group
* (optional) The group this message is in, which is displayed in a column
* in test output. Use 'Debug' to indicate this is debugging output. Do not
* translate this string. Defaults to 'Other'; most tests do not override
* this default.
*
* @return bool
* TRUE if the assertion succeeded, FALSE otherwise.
*/
protected function assertNotSubString($haystack, $needle, $message = '', $group = 'Other') {
return $this->assertTrue(strpos($haystack, $needle) === FALSE, $message, $group);
}
/**
* Tests general rewriting of the output.
*/
public function testRewrite() {
/** @var \Drupal\Core\Render\RendererInterface $renderer */
$renderer = \Drupal::service('renderer');
$view = Views::getView('test_view');
$view->initHandlers();
$this->executeView($view);
$row = $view->result[0];
$id_field = $view->field['id'];
// Don't check the rewrite checkbox, so the text shouldn't appear.
$id_field->options['alter']['text'] = $random_text = $this->randomMachineName();
$output = $renderer->executeInRenderContext(new RenderContext(), function () use ($id_field, $row) {
return $id_field->theme($row);
});
$this->assertNotSubString($output, $random_text);
$id_field->options['alter']['alter_text'] = TRUE;
$output = $renderer->executeInRenderContext(new RenderContext(), function () use ($id_field, $row) {
return $id_field->theme($row);
});
$this->assertSubString($output, $random_text);
}
/**
* Tests the field tokens, row level and field level.
*/
public function testFieldTokens() {
/** @var \Drupal\Core\Render\RendererInterface $renderer */
$renderer = \Drupal::service('renderer');
$view = Views::getView('test_field_tokens');
$this->executeView($view);
$name_field_0 = $view->field['name'];
$name_field_1 = $view->field['name_1'];
$name_field_2 = $view->field['name_2'];
$row = $view->result[0];
$name_field_0->options['alter']['alter_text'] = TRUE;
$name_field_0->options['alter']['text'] = '{{ name }}';
$name_field_1->options['alter']['alter_text'] = TRUE;
$name_field_1->options['alter']['text'] = '{{ name_1 }} {{ name }}';
$name_field_2->options['alter']['alter_text'] = TRUE;
$name_field_2->options['alter']['text'] = '{% if name_2|length > 3 %}{{ name_2 }} {{ name_1 }}{% endif %}';
foreach ($view->result as $row) {
$expected_output_0 = $row->views_test_data_name;
$expected_output_1 = "$row->views_test_data_name $row->views_test_data_name";
$expected_output_2 = "$row->views_test_data_name $row->views_test_data_name $row->views_test_data_name";
$output = $renderer->executeInRenderContext(new RenderContext(), function () use ($name_field_0, $row) {
return $name_field_0->advancedRender($row);
});
$this->assertEqual($output, $expected_output_0, format_string('Test token replacement: "!token" gave "!output"', [
'!token' => $name_field_0->options['alter']['text'],
'!output' => $output,
]));
$output = $renderer->executeInRenderContext(new RenderContext(), function () use ($name_field_1, $row) {
return $name_field_1->advancedRender($row);
});
$this->assertEqual($output, $expected_output_1, format_string('Test token replacement: "!token" gave "!output"', [
'!token' => $name_field_1->options['alter']['text'],
'!output' => $output,
]));
$output = $renderer->executeInRenderContext(new RenderContext(), function () use ($name_field_2, $row) {
return $name_field_2->advancedRender($row);
});
$this->assertEqual($output, $expected_output_2, format_string('Test token replacement: "!token" gave "!output"', [
'!token' => $name_field_2->options['alter']['text'],
'!output' => $output,
]));
}
$job_field = $view->field['job'];
$job_field->options['alter']['alter_text'] = TRUE;
$job_field->options['alter']['text'] = '{{ job }}';
$random_text = $this->randomMachineName();
$job_field->setTestValue($random_text);
$output = $renderer->executeInRenderContext(new RenderContext(), function () use ($job_field, $row) {
return $job_field->advancedRender($row);
});
$this->assertSubString($output, $random_text, format_string('Make sure the self token (!token => !value) appears in the output (!output)', [
'!value' => $random_text,
'!output' => $output,
'!token' => $job_field->options['alter']['text'],
]));
// Verify the token format used in D7 and earlier does not get substituted.
$old_token = '[job]';
$job_field->options['alter']['text'] = $old_token;
$random_text = $this->randomMachineName();
$job_field->setTestValue($random_text);
$output = $renderer->executeInRenderContext(new RenderContext(), function () use ($job_field, $row) {
return $job_field->advancedRender($row);
});
$this->assertEqual($output, $old_token, format_string('Make sure the old token style (!token => !value) is not changed in the output (!output)', [
'!value' => $random_text,
'!output' => $output,
'!token' => $job_field->options['alter']['text'],
]));
// Verify HTML tags are allowed in rewrite templates while token
// replacements are escaped.
$job_field->options['alter']['text'] = '<h1>{{ job }}</h1>';
$random_text = $this->randomMachineName();
$job_field->setTestValue('<span>' . $random_text . '</span>');
$output = $renderer->executeInRenderContext(new RenderContext(), function () use ($job_field, $row) {
return $job_field->advancedRender($row);
});
$this->assertEqual($output, '<h1>&lt;span&gt;' . $random_text . '&lt;/span&gt;</h1>', 'Valid tags are allowed in rewrite templates and token replacements.');
// Verify <script> tags are correctly removed from rewritten text.
$rewrite_template = '<script>alert("malicious");</script>';
$job_field->options['alter']['text'] = $rewrite_template;
$random_text = $this->randomMachineName();
$job_field->setTestValue($random_text);
$output = $renderer->executeInRenderContext(new RenderContext(), function () use ($job_field, $row) {
return $job_field->advancedRender($row);
});
$this->assertNotSubString($output, '<script>', 'Ensure a script tag in the rewrite template is removed.');
$rewrite_template = '<script>{{ job }}</script>';
$job_field->options['alter']['text'] = $rewrite_template;
$random_text = $this->randomMachineName();
$job_field->setTestValue($random_text);
$output = $renderer->executeInRenderContext(new RenderContext(), function () use ($job_field, $row) {
return $job_field->advancedRender($row);
});
$this->assertEqual($output, $random_text, format_string('Make sure a script tag in the template (!template) is removed, leaving only the replaced token in the output (!output)', [
'!output' => $output,
'!template' => $rewrite_template,
]));
}
/**
* Tests the exclude setting.
*/
public function testExclude() {
/** @var \Drupal\Core\Render\RendererInterface $renderer */
$renderer = $this->container->get('renderer');
$view = Views::getView('test_field_output');
$view->initHandlers();
// Hide the field and see whether it's rendered.
$view->field['name']->options['exclude'] = TRUE;
$output = $view->preview();
$output = $renderer->renderRoot($output);
foreach ($this->dataSet() as $entry) {
$this->assertNotSubString($output, $entry['name']);
}
// Show and check the field.
$view->field['name']->options['exclude'] = FALSE;
$output = $view->preview();
$output = $renderer->renderRoot($output);
foreach ($this->dataSet() as $entry) {
$this->assertSubString($output, $entry['name']);
}
}
/**
* Tests everything related to empty output of a field.
*/
function testEmpty() {
$this->_testHideIfEmpty();
$this->_testEmptyText();
}
/**
* Tests the hide if empty functionality.
*
* This tests alters the result to get easier and less coupled results. It is
* important that assertIdentical() is used in this test since in PHP 0 == ''.
*/
function _testHideIfEmpty() {
/** @var \Drupal\Core\Render\RendererInterface $renderer */
$renderer = \Drupal::service('renderer');
$view = Views::getView('test_view');
$view->initDisplay();
$this->executeView($view);
$column_map_reversed = array_flip($this->columnMap);
$view->row_index = 0;
$random_name = $this->randomMachineName();
$random_value = $this->randomMachineName();
// Test when results are not rewritten and empty values are not hidden.
$view->field['name']->options['hide_alter_empty'] = FALSE;
$view->field['name']->options['hide_empty'] = FALSE;
$view->field['name']->options['empty_zero'] = FALSE;
// Test a valid string.
$view->result[0]->{$column_map_reversed['name']} = $random_name;
$render = $renderer->executeInRenderContext(new RenderContext(), function () use ($view) {
return $view->field['name']->advancedRender($view->result[0]);
});
$this->assertIdentical((string) $render, $random_name, 'By default, a string should not be treated as empty.');
// Test an empty string.
$view->result[0]->{$column_map_reversed['name']} = "";
$render = $renderer->executeInRenderContext(new RenderContext(), function () use ($view) {
return $view->field['name']->advancedRender($view->result[0]);
});
$this->assertIdentical($render, "", 'By default, "" should not be treated as empty.');
// Test zero as an integer.
$view->result[0]->{$column_map_reversed['name']} = 0;
$render = $renderer->executeInRenderContext(new RenderContext(), function () use ($view) {
return $view->field['name']->advancedRender($view->result[0]);
});
$this->assertIdentical((string) $render, '0', 'By default, 0 should not be treated as empty.');
// Test zero as a string.
$view->result[0]->{$column_map_reversed['name']} = "0";
$render = $renderer->executeInRenderContext(new RenderContext(), function () use ($view) {
return $view->field['name']->advancedRender($view->result[0]);
});
$this->assertIdentical((string) $render, "0", 'By default, "0" should not be treated as empty.');
// Test when results are not rewritten and non-zero empty values are hidden.
$view->field['name']->options['hide_alter_empty'] = TRUE;
$view->field['name']->options['hide_empty'] = TRUE;
$view->field['name']->options['empty_zero'] = FALSE;
// Test a valid string.
$view->result[0]->{$column_map_reversed['name']} = $random_name;
$render = $renderer->executeInRenderContext(new RenderContext(), function () use ($view) {
return $view->field['name']->advancedRender($view->result[0]);
});
$this->assertIdentical((string) $render, $random_name, 'If hide_empty is checked, a string should not be treated as empty.');
// Test an empty string.
$view->result[0]->{$column_map_reversed['name']} = "";
$render = $renderer->executeInRenderContext(new RenderContext(), function () use ($view) {
return $view->field['name']->advancedRender($view->result[0]);
});
$this->assertIdentical($render, "", 'If hide_empty is checked, "" should be treated as empty.');
// Test zero as an integer.
$view->result[0]->{$column_map_reversed['name']} = 0;
$render = $renderer->executeInRenderContext(new RenderContext(), function () use ($view) {
return $view->field['name']->advancedRender($view->result[0]);
});
$this->assertIdentical((string) $render, '0', 'If hide_empty is checked, but not empty_zero, 0 should not be treated as empty.');
// Test zero as a string.
$view->result[0]->{$column_map_reversed['name']} = "0";
$render = $renderer->executeInRenderContext(new RenderContext(), function () use ($view) {
return $view->field['name']->advancedRender($view->result[0]);
});
$this->assertIdentical((string) $render, "0", 'If hide_empty is checked, but not empty_zero, "0" should not be treated as empty.');
// Test when results are not rewritten and all empty values are hidden.
$view->field['name']->options['hide_alter_empty'] = TRUE;
$view->field['name']->options['hide_empty'] = TRUE;
$view->field['name']->options['empty_zero'] = TRUE;
// Test zero as an integer.
$view->result[0]->{$column_map_reversed['name']} = 0;
$render = $renderer->executeInRenderContext(new RenderContext(), function () use ($view) {
return $view->field['name']->advancedRender($view->result[0]);
});
$this->assertIdentical($render, "", 'If hide_empty and empty_zero are checked, 0 should be treated as empty.');
// Test zero as a string.
$view->result[0]->{$column_map_reversed['name']} = "0";
$render = $renderer->executeInRenderContext(new RenderContext(), function () use ($view) {
return $view->field['name']->advancedRender($view->result[0]);
});
$this->assertIdentical($render, "", 'If hide_empty and empty_zero are checked, "0" should be treated as empty.');
// Test when results are rewritten to a valid string and non-zero empty
// results are hidden.
$view->field['name']->options['hide_alter_empty'] = FALSE;
$view->field['name']->options['hide_empty'] = TRUE;
$view->field['name']->options['empty_zero'] = FALSE;
$view->field['name']->options['alter']['alter_text'] = TRUE;
$view->field['name']->options['alter']['text'] = $random_name;
// Test a valid string.
$view->result[0]->{$column_map_reversed['name']} = $random_value;
$render = $renderer->executeInRenderContext(new RenderContext(), function () use ($view) {
return $view->field['name']->advancedRender($view->result[0]);
});
$this->assertIdentical((string) $render, $random_name, 'If the rewritten string is not empty, it should not be treated as empty.');
// Test an empty string.
$view->result[0]->{$column_map_reversed['name']} = "";
$render = $renderer->executeInRenderContext(new RenderContext(), function () use ($view) {
return $view->field['name']->advancedRender($view->result[0]);
});
$this->assertIdentical((string) $render, $random_name, 'If the rewritten string is not empty, "" should not be treated as empty.');
// Test zero as an integer.
$view->result[0]->{$column_map_reversed['name']} = 0;
$render = $renderer->executeInRenderContext(new RenderContext(), function () use ($view) {
return $view->field['name']->advancedRender($view->result[0]);
});
$this->assertIdentical((string) $render, $random_name, 'If the rewritten string is not empty, 0 should not be treated as empty.');
// Test zero as a string.
$view->result[0]->{$column_map_reversed['name']} = "0";
$render = $renderer->executeInRenderContext(new RenderContext(), function () use ($view) {
return $view->field['name']->advancedRender($view->result[0]);
});
$this->assertIdentical((string) $render, $random_name, 'If the rewritten string is not empty, "0" should not be treated as empty.');
// Test when results are rewritten to an empty string and non-zero empty results are hidden.
$view->field['name']->options['hide_alter_empty'] = TRUE;
$view->field['name']->options['hide_empty'] = TRUE;
$view->field['name']->options['empty_zero'] = FALSE;
$view->field['name']->options['alter']['alter_text'] = TRUE;
$view->field['name']->options['alter']['text'] = "";
// Test a valid string.
$view->result[0]->{$column_map_reversed['name']} = $random_name;
$render = $renderer->executeInRenderContext(new RenderContext(), function () use ($view) {
return $view->field['name']->advancedRender($view->result[0]);
});
$this->assertIdentical((string) $render, $random_name, 'If the rewritten string is empty, it should not be treated as empty.');
// Test an empty string.
$view->result[0]->{$column_map_reversed['name']} = "";
$render = $renderer->executeInRenderContext(new RenderContext(), function () use ($view) {
return $view->field['name']->advancedRender($view->result[0]);
});
$this->assertIdentical($render, "", 'If the rewritten string is empty, "" should be treated as empty.');
// Test zero as an integer.
$view->result[0]->{$column_map_reversed['name']} = 0;
$render = $renderer->executeInRenderContext(new RenderContext(), function () use ($view) {
return $view->field['name']->advancedRender($view->result[0]);
});
$this->assertIdentical((string) $render, '0', 'If the rewritten string is empty, 0 should not be treated as empty.');
// Test zero as a string.
$view->result[0]->{$column_map_reversed['name']} = "0";
$render = $renderer->executeInRenderContext(new RenderContext(), function () use ($view) {
return $view->field['name']->advancedRender($view->result[0]);
});
$this->assertIdentical((string) $render, "0", 'If the rewritten string is empty, "0" should not be treated as empty.');
// Test when results are rewritten to zero as a string and non-zero empty
// results are hidden.
$view->field['name']->options['hide_alter_empty'] = FALSE;
$view->field['name']->options['hide_empty'] = TRUE;
$view->field['name']->options['empty_zero'] = FALSE;
$view->field['name']->options['alter']['alter_text'] = TRUE;
$view->field['name']->options['alter']['text'] = "0";
// Test a valid string.
$view->result[0]->{$column_map_reversed['name']} = $random_name;
$render = $renderer->executeInRenderContext(new RenderContext(), function () use ($view) {
return $view->field['name']->advancedRender($view->result[0]);
});
$this->assertIdentical((string) $render, "0", 'If the rewritten string is zero and empty_zero is not checked, the string rewritten as 0 should not be treated as empty.');
// Test an empty string.
$view->result[0]->{$column_map_reversed['name']} = "";
$render = $renderer->executeInRenderContext(new RenderContext(), function () use ($view) {
return $view->field['name']->advancedRender($view->result[0]);
});
$this->assertIdentical((string) $render, "0", 'If the rewritten string is zero and empty_zero is not checked, "" rewritten as 0 should not be treated as empty.');
// Test zero as an integer.
$view->result[0]->{$column_map_reversed['name']} = 0;
$render = $renderer->executeInRenderContext(new RenderContext(), function () use ($view) {
return $view->field['name']->advancedRender($view->result[0]);
});
$this->assertIdentical((string) $render, "0", 'If the rewritten string is zero and empty_zero is not checked, 0 should not be treated as empty.');
// Test zero as a string.
$view->result[0]->{$column_map_reversed['name']} = "0";
$render = $renderer->executeInRenderContext(new RenderContext(), function () use ($view) {
return $view->field['name']->advancedRender($view->result[0]);
});
$this->assertIdentical((string) $render, "0", 'If the rewritten string is zero and empty_zero is not checked, "0" should not be treated as empty.');
// Test when results are rewritten to a valid string and non-zero empty
// results are hidden.
$view->field['name']->options['hide_alter_empty'] = TRUE;
$view->field['name']->options['hide_empty'] = TRUE;
$view->field['name']->options['empty_zero'] = FALSE;
$view->field['name']->options['alter']['alter_text'] = TRUE;
$view->field['name']->options['alter']['text'] = $random_value;
// Test a valid string.
$view->result[0]->{$column_map_reversed['name']} = $random_name;
$render = $renderer->executeInRenderContext(new RenderContext(), function () use ($view) {
return $view->field['name']->advancedRender($view->result[0]);
});
$this->assertIdentical((string) $render, $random_value, 'If the original and rewritten strings are valid, it should not be treated as empty.');
// Test an empty string.
$view->result[0]->{$column_map_reversed['name']} = "";
$render = $renderer->executeInRenderContext(new RenderContext(), function () use ($view) {
return $view->field['name']->advancedRender($view->result[0]);
});
$this->assertIdentical($render, "", 'If either the original or rewritten string is invalid, "" should be treated as empty.');
// Test zero as an integer.
$view->result[0]->{$column_map_reversed['name']} = 0;
$render = $renderer->executeInRenderContext(new RenderContext(), function () use ($view) {
return $view->field['name']->advancedRender($view->result[0]);
});
$this->assertIdentical((string) $render, $random_value, 'If the original and rewritten strings are valid, 0 should not be treated as empty.');
// Test zero as a string.
$view->result[0]->{$column_map_reversed['name']} = "0";
$render = $renderer->executeInRenderContext(new RenderContext(), function () use ($view) {
return $view->field['name']->advancedRender($view->result[0]);
});
$this->assertIdentical((string) $render, $random_value, 'If the original and rewritten strings are valid, "0" should not be treated as empty.');
// Test when results are rewritten to zero as a string and all empty
// original values and results are hidden.
$view->field['name']->options['hide_alter_empty'] = TRUE;
$view->field['name']->options['hide_empty'] = TRUE;
$view->field['name']->options['empty_zero'] = TRUE;
$view->field['name']->options['alter']['alter_text'] = TRUE;
$view->field['name']->options['alter']['text'] = "0";
// Test a valid string.
$view->result[0]->{$column_map_reversed['name']} = $random_name;
$render = $renderer->executeInRenderContext(new RenderContext(), function () use ($view) {
return $view->field['name']->advancedRender($view->result[0]);
});
$this->assertIdentical((string) $render, "", 'If the rewritten string is zero, it should be treated as empty.');
// Test an empty string.
$view->result[0]->{$column_map_reversed['name']} = "";
$render = $renderer->executeInRenderContext(new RenderContext(), function () use ($view) {
return $view->field['name']->advancedRender($view->result[0]);
});
$this->assertIdentical($render, "", 'If the rewritten string is zero, "" should be treated as empty.');
// Test zero as an integer.
$view->result[0]->{$column_map_reversed['name']} = 0;
$render = $renderer->executeInRenderContext(new RenderContext(), function () use ($view) {
return $view->field['name']->advancedRender($view->result[0]);
});
$this->assertIdentical($render, "", 'If the rewritten string is zero, 0 should not be treated as empty.');
// Test zero as a string.
$view->result[0]->{$column_map_reversed['name']} = "0";
$render = $renderer->executeInRenderContext(new RenderContext(), function () use ($view) {
return $view->field['name']->advancedRender($view->result[0]);
});
$this->assertIdentical($render, "", 'If the rewritten string is zero, "0" should not be treated as empty.');
}
/**
* Tests the usage of the empty text.
*/
function _testEmptyText() {
/** @var \Drupal\Core\Render\RendererInterface $renderer */
$renderer = \Drupal::service('renderer');
$view = Views::getView('test_view');
$view->initDisplay();
$this->executeView($view);
$column_map_reversed = array_flip($this->columnMap);
$view->row_index = 0;
$empty_text = $view->field['name']->options['empty'] = $this->randomMachineName();
$view->result[0]->{$column_map_reversed['name']} = "";
$render = $renderer->executeInRenderContext(new RenderContext(), function () use ($view) {
return $view->field['name']->advancedRender($view->result[0]);
});
$this->assertIdentical((string) $render, $empty_text, 'If a field is empty, the empty text should be used for the output.');
$view->result[0]->{$column_map_reversed['name']} = "0";
$render = $renderer->executeInRenderContext(new RenderContext(), function () use ($view) {
return $view->field['name']->advancedRender($view->result[0]);
});
$this->assertIdentical((string) $render, "0", 'If a field is 0 and empty_zero is not checked, the empty text should not be used for the output.');
$view->result[0]->{$column_map_reversed['name']} = "0";
$view->field['name']->options['empty_zero'] = TRUE;
$render = $renderer->executeInRenderContext(new RenderContext(), function () use ($view) {
return $view->field['name']->advancedRender($view->result[0]);
});
$this->assertIdentical((string) $render, $empty_text, 'If a field is 0 and empty_zero is checked, the empty text should be used for the output.');
$view->result[0]->{$column_map_reversed['name']} = "";
$view->field['name']->options['alter']['alter_text'] = TRUE;
$alter_text = $view->field['name']->options['alter']['text'] = $this->randomMachineName();
$view->field['name']->options['hide_alter_empty'] = FALSE;
$render = $renderer->executeInRenderContext(new RenderContext(), function () use ($view) {
return $view->field['name']->advancedRender($view->result[0]);
});
$this->assertIdentical((string) $render, $alter_text, 'If a field is empty, some rewrite text exists, but hide_alter_empty is not checked, render the rewrite text.');
$view->field['name']->options['hide_alter_empty'] = TRUE;
$render = $renderer->executeInRenderContext(new RenderContext(), function () use ($view) {
return $view->field['name']->advancedRender($view->result[0]);
});
$this->assertIdentical((string) $render, $empty_text, 'If a field is empty, some rewrite text exists, and hide_alter_empty is checked, use the empty text.');
}
/**
* Tests views_handler_field::isValueEmpty().
*/
function testIsValueEmpty() {
$view = Views::getView('test_view');
$view->initHandlers();
$field = $view->field['name'];
$this->assertFalse($field->isValueEmpty("not empty", TRUE), 'A normal string is not empty.');
$this->assertTrue($field->isValueEmpty("not empty", TRUE, FALSE), 'A normal string which skips empty() can be seen as empty.');
$this->assertTrue($field->isValueEmpty("", TRUE), '"" is considered as empty.');
$this->assertTrue($field->isValueEmpty('0', TRUE), '"0" is considered as empty if empty_zero is TRUE.');
$this->assertTrue($field->isValueEmpty(0, TRUE), '0 is considered as empty if empty_zero is TRUE.');
$this->assertFalse($field->isValueEmpty('0', FALSE), '"0" is considered not as empty if empty_zero is FALSE.');
$this->assertFalse($field->isValueEmpty(0, FALSE), '0 is considered not as empty if empty_zero is FALSE.');
$this->assertTrue($field->isValueEmpty(NULL, TRUE, TRUE), 'Null should be always seen as empty, regardless of no_skip_empty.');
$this->assertTrue($field->isValueEmpty(NULL, TRUE, FALSE), 'Null should be always seen as empty, regardless of no_skip_empty.');
}
/**
* Tests whether the filters are click sortable as expected.
*/
public function testClickSortable() {
// Test that clickSortable is TRUE by default.
$item = array(
'table' => 'views_test_data',
'field' => 'name',
);
$plugin = $this->container->get('plugin.manager.views.field')->getHandler($item);
$this->assertTrue($plugin->clickSortable(), 'TRUE as a default value is correct.');
// Test that clickSortable is TRUE by when set TRUE in the data.
$item['field'] = 'id';
$plugin = $this->container->get('plugin.manager.views.field')->getHandler($item);
$this->assertTrue($plugin->clickSortable(), 'TRUE as a views data value is correct.');
// Test that clickSortable is FALSE by when set FALSE in the data.
$item['field'] = 'job';
$plugin = $this->container->get('plugin.manager.views.field')->getHandler($item);
$this->assertFalse($plugin->clickSortable(), 'FALSE as a views data value is correct.');
}
/**
* Tests the trimText method.
*/
public function testTrimText() {
// Test unicode. See https://www.drupal.org/node/513396#comment-2839416.
$text = array(
'Tuy nhiên, những hi vọng',
'Giả sử chúng tôi có 3 Apple',
'siêu nhỏ này là bộ xử lý',
'Di động của nhà sản xuất Phần Lan',
'khoảng cách từ đại lí đến',
'của hãng bao gồm ba dòng',
'сд асд асд ас',
'асд асд асд ас'
);
// Just test maxlength without word boundary.
$alter = array(
'max_length' => 10,
);
$expect = array(
'Tuy nhiên,',
'Giả sử chú',
'siêu nhỏ n',
'Di động củ',
'khoảng các',
'của hãng b',
'сд асд асд',
'асд асд ас',
);
foreach ($text as $key => $line) {
$result_text = FieldPluginBase::trimText($alter, $line);
$this->assertEqual($result_text, $expect[$key]);
}
// Test also word_boundary
$alter['word_boundary'] = TRUE;
$expect = array(
'Tuy nhiên',
'Giả sử',
'siêu nhỏ',
'Di động',
'khoảng',
'của hãng',
'сд асд',
'асд асд',
);
foreach ($text as $key => $line) {
$result_text = FieldPluginBase::trimText($alter, $line);
$this->assertEqual($result_text, $expect[$key]);
}
}
}

View file

@ -8,7 +8,7 @@
namespace Drupal\views\Tests\Handler;
use Drupal\Core\Url;
use Drupal\views\Tests\ViewUnitTestBase;
use Drupal\views\Tests\ViewKernelTestBase;
use Drupal\views\Views;
/**
@ -16,7 +16,7 @@ use Drupal\views\Views;
*
* @group views
*/
class FieldUrlTest extends ViewUnitTestBase {
class FieldUrlTest extends ViewKernelTestBase {
public static $modules = array('system');

View file

@ -400,7 +400,7 @@ class FieldWebTest extends HandlerTestBase {
$output = $view->preview();
$output = $renderer->renderRoot($output);
$this->assertFalse($this->xpathContent($output, '//div[contains(@class, :class)]', array(':class' => 'field-content')));
$this->assertFalse($this->xpathContent($output, '//div[contains(@class, :class)]', array(':class' => 'field-label')));
$this->assertFalse($this->xpathContent($output, '//div[contains(@class, :class)]', array(':class' => 'field__label')));
$id_field->options['element_default_classes'] = TRUE;
$output = $view->preview();

View file

@ -7,7 +7,7 @@
namespace Drupal\views\Tests\Handler;
use Drupal\views\Tests\ViewUnitTestBase;
use Drupal\views\Tests\ViewKernelTestBase;
use Drupal\views\Views;
/**
@ -17,7 +17,7 @@ use Drupal\views\Views;
* @group views
* @see \Drupal\views\Plugin\views\filter\BooleanOperatorString
*/
class FilterBooleanOperatorStringTest extends ViewUnitTestBase {
class FilterBooleanOperatorStringTest extends ViewKernelTestBase {
/**
* The modules to enable for this test.

View file

@ -7,7 +7,7 @@
namespace Drupal\views\Tests\Handler;
use Drupal\views\Tests\ViewUnitTestBase;
use Drupal\views\Tests\ViewKernelTestBase;
use Drupal\views\Views;
/**
@ -16,7 +16,7 @@ use Drupal\views\Views;
* @group views
* @see \Drupal\views\Plugin\views\filter\BooleanOperator
*/
class FilterBooleanOperatorTest extends ViewUnitTestBase {
class FilterBooleanOperatorTest extends ViewKernelTestBase {
/**
* The modules to enable for this test.

View file

@ -7,7 +7,7 @@
namespace Drupal\views\Tests\Handler;
use Drupal\views\Tests\ViewUnitTestBase;
use Drupal\views\Tests\ViewKernelTestBase;
use Drupal\views\Views;
/**
@ -15,7 +15,7 @@ use Drupal\views\Views;
*
* @group views
*/
class FilterCombineTest extends ViewUnitTestBase {
class FilterCombineTest extends ViewKernelTestBase {
/**
* Views used by this test.

View file

@ -7,7 +7,7 @@
namespace Drupal\views\Tests\Handler;
use Drupal\views\Tests\ViewUnitTestBase;
use Drupal\views\Tests\ViewKernelTestBase;
use Drupal\views\Views;
/**
@ -15,7 +15,7 @@ use Drupal\views\Views;
*
* @group views
*/
class FilterEqualityTest extends ViewUnitTestBase {
class FilterEqualityTest extends ViewKernelTestBase {
public static $modules = array('system');

View file

@ -7,7 +7,7 @@
namespace Drupal\views\Tests\Handler;
use Drupal\views\Tests\ViewUnitTestBase;
use Drupal\views\Tests\ViewKernelTestBase;
use Drupal\views\Views;
/**
@ -15,7 +15,7 @@ use Drupal\views\Views;
*
* @group views
*/
class FilterInOperatorTest extends ViewUnitTestBase {
class FilterInOperatorTest extends ViewKernelTestBase {
public static $modules = array('system');

View file

@ -7,7 +7,7 @@
namespace Drupal\views\Tests\Handler;
use Drupal\views\Tests\ViewUnitTestBase;
use Drupal\views\Tests\ViewKernelTestBase;
use Drupal\views\Views;
/**
@ -15,7 +15,7 @@ use Drupal\views\Views;
*
* @group views
*/
class FilterNumericTest extends ViewUnitTestBase {
class FilterNumericTest extends ViewKernelTestBase {
public static $modules = array('system');

View file

@ -7,7 +7,7 @@
namespace Drupal\views\Tests\Handler;
use Drupal\views\Tests\ViewUnitTestBase;
use Drupal\views\Tests\ViewKernelTestBase;
use Drupal\views\Views;
/**
@ -15,7 +15,7 @@ use Drupal\views\Views;
*
* @group views
*/
class FilterStringTest extends ViewUnitTestBase {
class FilterStringTest extends ViewKernelTestBase {
public static $modules = array('system');

View file

@ -7,7 +7,7 @@
namespace Drupal\views\Tests\Handler;
use Drupal\views\Tests\ViewUnitTestBase;
use Drupal\views\Tests\ViewKernelTestBase;
use Drupal\views\Views;
/**
@ -15,7 +15,7 @@ use Drupal\views\Views;
*
* @group views
*/
class HandlerAliasTest extends ViewUnitTestBase {
class HandlerAliasTest extends ViewKernelTestBase {
public static $modules = array('user');

View file

@ -8,7 +8,7 @@
namespace Drupal\views\Tests\Handler;
use Drupal\Component\Utility\SafeMarkup;
use Drupal\views\Tests\ViewUnitTestBase;
use Drupal\views\Tests\ViewKernelTestBase;
use Drupal\views\Views;
/**
@ -16,7 +16,7 @@ use Drupal\views\Views;
*
* @group views
*/
class SortDateTest extends ViewUnitTestBase {
class SortDateTest extends ViewKernelTestBase {
/**
* Views used by this test.

View file

@ -9,7 +9,7 @@ namespace Drupal\views\Tests\Handler;
use Drupal\Core\Cache\Cache;
use Drupal\views\Plugin\views\display\DisplayPluginBase;
use Drupal\views\Tests\ViewUnitTestBase;
use Drupal\views\Tests\ViewKernelTestBase;
use Drupal\views\Views;
/**
@ -17,7 +17,7 @@ use Drupal\views\Views;
*
* @group views
*/
class SortRandomTest extends ViewUnitTestBase {
class SortRandomTest extends ViewKernelTestBase {
/**
* Views used by this test.

View file

@ -7,7 +7,7 @@
namespace Drupal\views\Tests\Handler;
use Drupal\views\Tests\ViewUnitTestBase;
use Drupal\views\Tests\ViewKernelTestBase;
use Drupal\views\Views;
/**
@ -15,7 +15,7 @@ use Drupal\views\Views;
*
* @group views
*/
class SortTest extends ViewUnitTestBase {
class SortTest extends ViewKernelTestBase {
/**
* Views used by this test.

View file

@ -16,7 +16,7 @@ use Drupal\views\Plugin\views\filter\Standard;
use Drupal\views\Views;
use Drupal\Component\Utility\SafeMarkup;
class ModuleTest extends ViewUnitTestBase {
class ModuleTest extends ViewKernelTestBase {
/**
* Views used by this test.

View file

@ -94,11 +94,11 @@ class ArgumentDefaultTest extends PluginTestBase {
// Note, the undefined index error has two spaces after it.
$error = array(
'%type' => 'Notice',
'!message' => 'Undefined index: ' . $argument_type,
'@message' => 'Undefined index: ' . $argument_type,
'%function' => 'views_handler_argument->validateOptionsForm()',
);
$message = t('%type: !message in %function', $error);
$this->assertNoRaw($message, t('Did not find error message: !message.', array('!message' => $message)));
$message = t('%type: @message in %function', $error);
$this->assertNoRaw($message, format_string('Did not find error message: @message.', array('@message' => $message)));
}
/**

View file

@ -7,7 +7,7 @@
namespace Drupal\views\Tests\Plugin;
use Drupal\views\Tests\ViewUnitTestBase;
use Drupal\views\Tests\ViewKernelTestBase;
use Drupal\views\Views;
/**
@ -15,7 +15,7 @@ use Drupal\views\Views;
*
* @group views
*/
class ArgumentValidatorTest extends ViewUnitTestBase {
class ArgumentValidatorTest extends ViewKernelTestBase {
/**
* Views used by this test.

View file

@ -8,14 +8,14 @@
namespace Drupal\views\Tests\Plugin;
use Drupal\Core\Cache\Cache;
use Drupal\views\Tests\ViewUnitTestBase;
use Drupal\views\Tests\ViewKernelTestBase;
/**
* Tests views block config dependencies functionality.
*
* @group views
*/
class BlockDependenciesTest extends ViewUnitTestBase {
class BlockDependenciesTest extends ViewKernelTestBase {
/**
* Views used by this test.

View file

@ -9,7 +9,7 @@ namespace Drupal\views\Tests\Plugin;
use Drupal\Core\Render\RenderContext;
use Drupal\node\Entity\Node;
use Drupal\views\Tests\ViewUnitTestBase;
use Drupal\views\Tests\ViewKernelTestBase;
use Drupal\views\Views;
use Drupal\views_test_data\Plugin\views\filter\FilterTest as FilterPlugin;
@ -19,7 +19,7 @@ use Drupal\views_test_data\Plugin\views\filter\FilterTest as FilterPlugin;
* @group views
* @see views_plugin_cache
*/
class CacheTest extends ViewUnitTestBase {
class CacheTest extends ViewKernelTestBase {
/**
* Views used by this test.

View file

@ -0,0 +1,122 @@
<?php
/**
* @file
* Contains \Drupal\views\Tests\Plugin\DisplayKernelTest.
*/
namespace Drupal\views\Tests\Plugin;
use Drupal\views\Views;
use Drupal\views\Tests\ViewKernelTestBase;
use Drupal\views\Plugin\views\style\StylePluginBase;
use Drupal\views\Plugin\views\access\AccessPluginBase;
use Drupal\views\Plugin\views\exposed_form\ExposedFormPluginBase;
use Drupal\views\Plugin\views\pager\PagerPluginBase;
use Drupal\views\Plugin\views\query\QueryPluginBase;
use Drupal\views\Plugin\views\cache\CachePluginBase;
use Drupal\views\Plugin\views\row\RowPluginBase;
/**
* Drupal unit tests for the DisplayPluginBase class.
*
* @group views
*/
class DisplayKernelTest extends ViewKernelTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = array('block', 'node', 'field', 'user');
/**
* Views plugin types to test.
*
* @var array
*/
protected $pluginTypes = array(
'access',
'cache',
'query',
'exposed_form',
'pager',
'style',
'row',
);
/**
* Views handler types to test.
*
* @var array
*/
protected $handlerTypes = array(
'fields',
'sorts',
);
/**
* Views used by this test.
*
* @var array
*/
public static $testViews = array('test_display_defaults');
/**
* Tests the default display options.
*/
public function testDefaultOptions() {
// Save the view.
$view = Views::getView('test_display_defaults');
$view->mergeDefaults();
$view->save();
// Reload to get saved storage values.
$view = Views::getView('test_display_defaults');
$view->initDisplay();
$display_data = $view->storage->get('display');
foreach ($view->displayHandlers as $id => $display) {
// Test the view plugin options against the storage.
foreach ($this->pluginTypes as $type) {
$options = $display->getOption($type);
$this->assertIdentical($display_data[$id]['display_options'][$type]['options'], $options['options']);
}
// Test the view handler options against the storage.
foreach ($this->handlerTypes as $type) {
$options = $display->getOption($type);
$this->assertIdentical($display_data[$id]['display_options'][$type], $options);
}
}
}
/**
* Tests the \Drupal\views\Plugin\views\display\DisplayPluginBase::getPlugin() method.
*/
public function testGetPlugin() {
$view = Views::getView('test_display_defaults');
$view->initDisplay();
$display_handler = $view->display_handler;
$this->assertTrue($display_handler->getPlugin('access') instanceof AccessPluginBase, 'An access plugin instance was returned.');
$this->assertTrue($display_handler->getPlugin('cache') instanceof CachePluginBase, 'A cache plugin instance was returned.');
$this->assertTrue($display_handler->getPlugin('exposed_form') instanceof ExposedFormPluginBase, 'An exposed_form plugin instance was returned.');
$this->assertTrue($display_handler->getPlugin('pager') instanceof PagerPluginBase, 'A pager plugin instance was returned.');
$this->assertTrue($display_handler->getPlugin('query') instanceof QueryPluginBase, 'A query plugin instance was returned.');
$this->assertTrue($display_handler->getPlugin('row') instanceof RowPluginBase, 'A row plugin instance was returned.');
$this->assertTrue($display_handler->getPlugin('style') instanceof StylePluginBase, 'A style plugin instance was returned.');
// Test that nothing is returned when an invalid type is requested.
$this->assertNull($display_handler->getPlugin('invalid'), 'NULL was returned for an invalid instance');
// Test that nothing was returned for an instance with no 'type' in options.
unset($display_handler->options['access']);
$this->assertNull($display_handler->getPlugin('access'), 'NULL was returned for a plugin type with no "type" option');
// Get a plugin twice, and make sure the same instance is returned.
$view->destroy();
$view->initDisplay();
$first = spl_object_hash($display_handler->getPlugin('style'));
$second = spl_object_hash($display_handler->getPlugin('style'));
$this->assertIdentical($first, $second, 'The same plugin instance was returned.');
}
}

View file

@ -10,7 +10,7 @@ namespace Drupal\views\Tests\Plugin;
use Drupal\Core\Menu\MenuTreeParameters;
use Drupal\Core\Session\AnonymousUserSession;
use Drupal\views\Views;
use Drupal\views\Tests\ViewUnitTestBase;
use Drupal\views\Tests\ViewKernelTestBase;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\HttpKernelInterface;
@ -21,7 +21,7 @@ use Symfony\Component\HttpKernel\HttpKernelInterface;
* @group views
* @see \Drupal\views\Plugin\display\Page
*/
class DisplayPageTest extends ViewUnitTestBase {
class DisplayPageTest extends ViewKernelTestBase {
/**
* Views used by this test.

View file

@ -24,19 +24,23 @@ class DisplayPageWebTest extends PluginTestBase {
*
* @var array
*/
public static $testViews = array('test_page_display', 'test_page_display_arguments', 'test_page_display_menu');
public static $testViews = array('test_page_display', 'test_page_display_arguments', 'test_page_display_menu', 'test_page_display_path');
/**
* Modules to enable.
*
* @var array
*/
public static $modules = ['menu_ui', 'block'];
public static $modules = ['menu_ui', 'block', 'views_ui'];
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->enableViewsTestModule();
$this->drupalPlaceBlock('local_tasks_block');
}
/**
@ -53,7 +57,7 @@ class DisplayPageWebTest extends PluginTestBase {
$this->drupalGet('test_route_with_argument/1');
$this->assertResponse(200);
$this->assertCacheContexts(['languages:language_interface', 'theme', 'url']);
$this->assertCacheContexts(['languages:language_interface', 'route.name', 'theme', 'url']);
$result = $this->xpath('//span[@class="field-content"]');
$this->assertEqual(count($result), 1, 'Ensure that just the filtered entry was returned.');
$this->assertEqual((string) $result[0], 1, 'The passed ID was returned.');
@ -138,4 +142,36 @@ class DisplayPageWebTest extends PluginTestBase {
$this->assertFalse($xpath, 'The view title was not displayed in the view markup.');
}
/**
* Tests the views page path functionality.
*/
public function testPagePaths() {
$this->drupalLogin($this->rootUser);
$this->assertPagePath('0');
$this->assertPagePath('9999');
}
/**
* Tests that we can successfully change a view page display path.
*
* @param string $path
* Path that will be set as the view page display path.
*
* @return boolean
* Assertion result.
*/
public function assertPagePath($path) {
$view = Views::getView('test_page_display_path');
$view->initDisplay('page_1');
$view->displayHandlers->get('page_1')->overrideOption('path', $path);
$view->save();
$this->container->get('router.builder')->rebuild();
// Check if we successfully changed the path.
$this->drupalGet($path);
$success = $this->assertResponse(200);
// Check if we don't get any error on the view edit page.
$this->drupalGet('admin/structure/views/view/test_page_display_path');
return $success && $this->assertResponse(200);
}
}

View file

@ -207,6 +207,7 @@ class ExposedFormTest extends ViewTestBase {
'entity_test_view_grants',
'theme',
'url.query_args',
'user.roles:authenticated',
'languages:language_content'
];

View file

@ -8,7 +8,7 @@
namespace Drupal\views\Tests\Plugin;
use Drupal\Core\Cache\CacheBackendInterface;
use Drupal\views\Tests\ViewUnitTestBase;
use Drupal\views\Tests\ViewKernelTestBase;
use Drupal\views\Views;
/**
@ -16,7 +16,7 @@ use Drupal\views\Views;
*
* @group views
*/
class PagerKernelTest extends ViewUnitTestBase {
class PagerKernelTest extends ViewKernelTestBase {
/**
* {@inheritdoc}

View file

@ -0,0 +1,61 @@
<?php
/**
* @file
* Contains \Drupal\views\Tests\Plugin\PluginBaseTest.
*/
namespace Drupal\views\Tests\Plugin;
use Drupal\Core\Render\RenderContext;
use Drupal\Core\Render\SafeString;
use Drupal\simpletest\KernelTestBase;
use Drupal\views\Plugin\views\PluginBase;
/**
* Tests the PluginBase class.
*
* @group views
*/
class PluginBaseTest extends KernelTestBase {
/**
* @var TestPluginBase
*/
var $testPluginBase;
public function setUp() {
parent::setUp();
$this->testPluginBase = new TestPluginBase();
}
/**
* Test that the token replacement in views works correctly.
*/
public function testViewsTokenReplace() {
$text = '{{ langcode__value }} means {{ langcode }}';
$tokens = ['{{ langcode }}' => SafeString::create('English'), '{{ langcode__value }}' => 'en'];
$result = \Drupal::service('renderer')->executeInRenderContext(new RenderContext(), function () use ($text, $tokens) {
return $this->testPluginBase->viewsTokenReplace($text, $tokens);
});
$this->assertIdentical($result, 'en means English');
}
}
/**
* Helper class for using the PluginBase abstract class.
*/
class TestPluginBase extends PluginBase {
public function __construct() {
parent::__construct([], '', []);
}
public function viewsTokenReplace($text, $tokens) {
return parent::viewsTokenReplace($text, $tokens);
}
}

View file

@ -0,0 +1,17 @@
<?php
/**
* @file
* Contains \Drupal\views\Tests\Plugin\PluginKernelTestBase.
*/
namespace Drupal\views\Tests\Plugin;
use Drupal\views\Tests\ViewKernelTestBase;
/**
* Base test class for views plugin unit tests.
*/
abstract class PluginKernelTestBase extends ViewKernelTestBase {
}

View file

@ -8,7 +8,7 @@
namespace Drupal\views\Tests\Plugin;
use Drupal\views\Views;
use Drupal\views\Tests\ViewUnitTestBase;
use Drupal\views\Tests\ViewKernelTestBase;
use Drupal\views_test_data\Plugin\views\query\QueryTest as QueryTestPlugin;
/**
@ -16,7 +16,7 @@ use Drupal\views_test_data\Plugin\views\query\QueryTest as QueryTestPlugin;
*
* @group views
*/
class QueryTest extends ViewUnitTestBase {
class QueryTest extends ViewKernelTestBase {
/**
* Views used by this test.

View file

@ -15,7 +15,7 @@ use Drupal\views\Views;
* @see \Drupal\views\Tests\Handler\JoinTest
* @see \Drupal\views\Tests\Plugin\RelationshipTest
*/
abstract class RelationshipJoinTestBase extends PluginUnitTestBase {
abstract class RelationshipJoinTestBase extends PluginKernelTestBase {
/**
* Modules to enable.
@ -30,7 +30,7 @@ abstract class RelationshipJoinTestBase extends PluginUnitTestBase {
protected $rootUser;
/**
* Overrides \Drupal\views\Tests\ViewUnitTestBase::setUpFixtures().
* Overrides \Drupal\views\Tests\ViewKernelTestBase::setUpFixtures().
*/
protected function setUpFixtures() {
$this->installEntitySchema('user');

View file

@ -9,7 +9,7 @@ namespace Drupal\views\Tests\Plugin;
use Drupal\Core\Form\FormState;
use Drupal\views\Views;
use Drupal\views\Tests\ViewUnitTestBase;
use Drupal\views\Tests\ViewKernelTestBase;
/**
* Tests the generic entity row plugin.
@ -17,7 +17,7 @@ use Drupal\views\Tests\ViewUnitTestBase;
* @group views
* @see \Drupal\views\Plugin\views\row\EntityRow
*/
class RowEntityTest extends ViewUnitTestBase {
class RowEntityTest extends ViewKernelTestBase {
/**
* Modules to enable.

View file

@ -12,7 +12,7 @@ use Drupal\node\Entity\Node;
use Drupal\node\Entity\NodeType;
use Drupal\node\NodeInterface;
use Drupal\simpletest\UserCreationTrait;
use Drupal\views\Tests\ViewUnitTestBase;
use Drupal\views\Tests\ViewKernelTestBase;
use Drupal\views\Views;
/**
@ -20,7 +20,7 @@ use Drupal\views\Views;
*
* @group views
*/
class RowRenderCacheTest extends ViewUnitTestBase {
class RowRenderCacheTest extends ViewKernelTestBase {
use UserCreationTrait;

View file

@ -7,7 +7,7 @@
namespace Drupal\views\Tests\Plugin;
use Drupal\views\Tests\ViewUnitTestBase;
use Drupal\views\Tests\ViewKernelTestBase;
use Drupal\views\Views;
/**
@ -16,7 +16,7 @@ use Drupal\views\Views;
* @group views
* @see \Drupal\views\Plugin\views\query\Sql
*/
class SqlQueryTest extends ViewUnitTestBase {
class SqlQueryTest extends ViewKernelTestBase {
/**
* Views used by this test.

View file

@ -7,7 +7,7 @@
namespace Drupal\views\Tests\Plugin;
use Drupal\views\Tests\ViewUnitTestBase;
use Drupal\views\Tests\ViewKernelTestBase;
use Drupal\views\Views;
/**
@ -16,7 +16,7 @@ use Drupal\views\Views;
* @group views
* @see \Drupal\views\Plugin\views\style\HtmlList
*/
class StyleHtmlListTest extends ViewUnitTestBase {
class StyleHtmlListTest extends ViewKernelTestBase {
/**
* Views used by this test.

View file

@ -53,7 +53,7 @@ class StyleMappingTest extends StyleTestBase {
$output = $view->preview();
$rendered_output = \Drupal::service('renderer')->renderRoot($output);
$this->storeViewPreview($rendered_output);
$rows = $this->elements->body->div->div->div;
$rows = $this->elements->body->div->div;
$data_set = $this->dataSet();
$count = 0;

View file

@ -109,4 +109,31 @@ class StyleTableTest extends PluginTestBase {
$this->assertTrue(count($result), 'Ensure the job column values are joined into a single column');
}
/**
* Test that a number with the value of "0" is displayed in the table.
*/
public function testNumericFieldVisible() {
// Adds a new datapoint in the views_test_data table to have a person with
// an age of zero.
$data_set = $this->dataSet();
$query = db_insert('views_test_data')
->fields(array_keys($data_set[0]));
$query->values(array(
'name' => 'James McCartney',
'age' => 0,
'job' => 'Baby',
'created' => gmmktime(6, 30, 10, 1, 1, 2000),
'status' => 1,
));
$query->execute();
$this->drupalGet('test-table');
$result = $this->xpath('//tbody/tr/td[contains(., "Baby")]');
$this->assertTrue(count($result), 'Ensure that the baby is found.');
$result = $this->xpath('//tbody/tr/td[text()=0]');
$this->assertTrue(count($result), 'Ensure that the baby\'s age is shown');
}
}

View file

@ -17,7 +17,7 @@ use Symfony\Component\HttpFoundation\Request;
* @group views
* @see \Drupal\views\Plugin\views\style\Table
*/
class StyleTableUnitTest extends PluginUnitTestBase {
class StyleTableUnitTest extends PluginKernelTestBase {
/**
* Views used by this test.

View file

@ -7,13 +7,13 @@
namespace Drupal\views\Tests\Plugin;
use Drupal\views\Tests\ViewUnitTestBase;
use Drupal\views\Tests\ViewKernelTestBase;
use Masterminds\HTML5;
/**
* Tests some general style plugin related functionality.
*/
abstract class StyleTestBase extends ViewUnitTestBase {
abstract class StyleTestBase extends ViewKernelTestBase {
/**
* Stores the SimpleXML representation of the output.

View file

@ -32,7 +32,7 @@ class StyleUnformattedTest extends StyleTestBase {
$output = $view->preview();
$this->storeViewPreview(\Drupal::service('renderer')->renderRoot($output));
$rows = $this->elements->body->div->div->div;
$rows = $this->elements->body->div->div;
$count = 0;
$count_result = count($view->result);
foreach ($rows as $row) {

View file

@ -10,14 +10,14 @@ namespace Drupal\views\Tests\Plugin;
use Drupal\Core\DependencyInjection\ContainerBuilder;
use Drupal\views\Plugin\Block\ViewsBlock;
use Drupal\views\Tests\ViewTestData;
use Drupal\views\Tests\ViewUnitTestBase;
use Drupal\views\Tests\ViewKernelTestBase;
/**
* Tests native behaviors of the block views plugin.
*
* @group views
*/
class ViewsBlockTest extends ViewUnitTestBase {
class ViewsBlockTest extends ViewKernelTestBase {
/**
* Modules to enable.

View file

@ -0,0 +1,75 @@
<?php
/**
* @file
* Contains \Drupal\views\Tests\Plugin\ViewsSqlExceptionTest.
*/
namespace Drupal\views\Tests\Plugin;
use Drupal\views\Views;
use Drupal\Core\Database\DatabaseExceptionWrapper;
/**
* Tests the views exception handling.
*
* @group views
*/
class ViewsSqlExceptionTest extends PluginTestBase {
/**
* Views used by this test.
*
* @var array
*/
public static $testViews = array('test_filter');
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->enableViewsTestModule();
}
/**
* Overrides Drupal\views\Tests\ViewTestBase::viewsData().
*/
protected function viewsData() {
$data = parent::viewsData();
$data['views_test_data']['name']['filter']['id'] = 'test_exception_filter';
return $data;
}
/**
* Test for the SQL exception.
*/
public function testSqlException() {
$view = Views::getView('test_filter');
$view->initDisplay();
// Adding a filter that will result in an invalid query.
$view->displayHandlers->get('default')->overrideOption('filters', array(
'test_filter' => array(
'id' => 'test_exception_filter',
'table' => 'views_test_data',
'field' => 'name',
'operator' => '=',
'value' => 'John',
'group' => 0,
),
));
try {
$this->executeView($view);
$this->fail('Expected exception not thrown.');
}
catch (DatabaseExceptionWrapper $e) {
$exception_assert_message = "Exception in {$view->storage->label()}[{$view->storage->id()}]";
$this->assertEqual(strstr($e->getMessage(), ':', TRUE), $exception_assert_message);
}
}
}

View file

@ -14,7 +14,7 @@ use Drupal\views\Views;
*
* @group views
*/
class PluginInstanceTest extends ViewUnitTestBase {
class PluginInstanceTest extends ViewKernelTestBase {
/**
* All views plugin types.

View file

@ -18,7 +18,7 @@ use Drupal\views\Views;
*
* @group views
*/
class QueryGroupByTest extends ViewUnitTestBase {
class QueryGroupByTest extends ViewKernelTestBase {
/**
* Views used by this test.

View file

@ -19,7 +19,7 @@ use Drupal\views\Entity\View;
*
* @group views
*/
class RenderCacheIntegrationTest extends ViewUnitTestBase {
class RenderCacheIntegrationTest extends ViewKernelTestBase {
use AssertViewsCacheTagsTrait;

View file

@ -101,6 +101,39 @@ class SearchIntegrationTest extends ViewTestBase {
$this->assertNoLink('pizza');
$this->assertNoLink('sandwich');
$this->assertOneLink('cola');
// Test sorting.
$node = [
'title' => "Drupal's search rocks.",
'type' => $type->id(),
];
$this->drupalCreateNode($node);
$node['title'] = "Drupal's search rocks <em>really</em> rocks!";
$this->drupalCreateNode($node);
$this->cronRun();
$this->drupalGet('test-arg/rocks');
$xpath = '//div[@class="views-row"]//a';
/** @var \SimpleXMLElement[] $results */
$results = $this->xpath($xpath);
$this->assertEqual((string) $results[0], "Drupal's search rocks <em>really</em> rocks!");
$this->assertEqual((string) $results[1], "Drupal's search rocks.");
$this->assertEscaped("Drupal's search rocks <em>really</em> rocks!");
// Test sorting with another set of titles.
$node = [
'title' => "Testing one two two two",
'type' => $type->id(),
];
$this->drupalCreateNode($node);
$node['title'] = "Testing one one one";
$this->drupalCreateNode($node);
$this->cronRun();
$this->drupalGet('test-arg/one');
$xpath = '//div[@class="views-row"]//a';
/** @var \SimpleXMLElement[] $results */
$results = $this->xpath($xpath);
$this->assertEqual((string) $results[0], "Testing one one one");
$this->assertEqual((string) $results[1], "Testing one two two two");
}
/**

View file

@ -15,7 +15,7 @@ use Drupal\views\Views;
*
* @group views
*/
class TokenReplaceTest extends ViewUnitTestBase {
class TokenReplaceTest extends ViewKernelTestBase {
public static $modules = array('system');

View file

@ -0,0 +1,25 @@
<?php
/**
* @file
* Contains \Drupal\views\Tests\Update\EntityViewsDataUpdateFilledTest.
*/
namespace Drupal\views\Tests\Update;
/**
* Runs EntityViewsDataUpdateTest with a dump filled with content.
*
* @group Update
*/
class EntityViewsDataUpdateFilledTest extends EntityViewsDataUpdateTest {
/**
* {@inheritdoc}
*/
protected function setDatabaseDumpFiles() {
parent::setDatabaseDumpFiles();
$this->databaseDumpFiles[0] = __DIR__ . '/../../../../system/tests/fixtures/update/drupal-8.filled.standard.php.gz';
}
}

View file

@ -0,0 +1,86 @@
<?php
/**
* @file
* Contains \Drupal\views\Tests\Update\EntityViewsDataUpdateTest.
*/
namespace Drupal\views\Tests\Update;
use Drupal\system\Tests\Update\UpdatePathTestBase;
use Drupal\views\Views;
/**
* Tests the upgrade path for views field plugins.
*
* @see https://www.drupal.org/node/2455125
*
* @group Update
*/
class EntityViewsDataUpdateTest extends UpdatePathTestBase {
/**
* {@inheritdoc}
*/
protected function setDatabaseDumpFiles() {
$this->databaseDumpFiles = [
__DIR__ . '/../../../../system/tests/fixtures/update/drupal-8.bare.standard.php.gz',
__DIR__ . '/../../../../system/tests/fixtures/update/drupal-8.views-entity-views-data-2455125.php',
];
}
/**
* Tests that field plugins are updated properly.
*/
public function testUpdateHookN() {
$this->runUpdates();
// Load and initialize our test view.
$view = Views::getView('update_test');
$view->initHandlers();
// Extract the fields from the test view that were updated.
/** @var \Drupal\views\Plugin\views\field\Field $field */
$created = $view->field['created'];
/** @var \Drupal\views\Plugin\views\field\Field $field */
$created_1 = $view->field['created_1'];
/** @var \Drupal\views\Plugin\views\field\Field $field */
$created_2 = $view->field['created_2'];
// Make sure the plugins were converted from date to field.
$this->assertEqual($created->getPluginId(), 'field', 'created has correct plugin_id');
$this->assertEqual($created_1->getPluginId(), 'field', 'created has correct plugin_id');
$this->assertEqual($created_2->getPluginId(), 'field', 'created has correct plugin_id');
// Check options on 'created'.
$options = $created->options;
$this->assertEqual($options['type'], 'timestamp');
$this->assertFalse(array_key_exists('date_format', $options));
$this->assertFalse(array_key_exists('custom_date_format', $options));
$this->assertFalse(array_key_exists('timezone', $options));
$this->assertEqual($options['settings']['date_format'], 'long');
$this->assertEqual($options['settings']['custom_date_format'], '');
$this->assertEqual($options['settings']['timezone'], 'Africa/Abidjan');
// Check options on 'created'.
$options = $created_1->options;
$this->assertEqual($options['type'], 'timestamp_ago');
$this->assertFalse(array_key_exists('date_format', $options));
$this->assertFalse(array_key_exists('custom_date_format', $options));
$this->assertFalse(array_key_exists('timezone', $options));
$this->assertEqual($options['settings']['future_format'], '@interval');
$this->assertEqual($options['settings']['past_format'], '@interval');
$this->assertEqual($options['settings']['granularity'], 2);
// Check options on 'created'.
$options = $created_2->options;
$this->assertEqual($options['type'], 'timestamp_ago');
$this->assertFalse(array_key_exists('date_format', $options));
$this->assertFalse(array_key_exists('custom_date_format', $options));
$this->assertFalse(array_key_exists('timezone', $options));
$this->assertEqual($options['settings']['future_format'], '@interval hence');
$this->assertEqual($options['settings']['past_format'], '@interval ago');
$this->assertEqual($options['settings']['granularity'], 2);
}
}

View file

@ -127,7 +127,7 @@ class ViewElementTest extends ViewTestBase {
// Set the content as our rendered array.
$render = $this->render;
$render['#embed'] = TRUE;
$render['view']['#embed'] = TRUE;
$this->setRawContent($renderer->renderRoot($render));
$xpath = $this->xpath('//div[@class="views-element-container"]');
@ -173,7 +173,7 @@ class ViewElementTest extends ViewTestBase {
// Test the render array again.
$render = $this->render;
$render['#embed'] = TRUE;
$render['view']['#embed'] = TRUE;
$this->setRawContent($renderer->renderRoot($render));
// There should be 1 row in the results, 'John' arg 25.
$xpath = $this->xpath('//div[@class="view-content"]/div');
@ -183,6 +183,15 @@ class ViewElementTest extends ViewTestBase {
$this->drupalGet('views_test_data_element_embed_form');
$xpath = $this->xpath('//div[@class="view-content"]/div');
$this->assertEqual(count($xpath), 1);
// Tests the render array with an exposed filter.
$render = $this->render;
$render['view']['#display_id'] = 'embed_2';
$render['view']['#embed'] = TRUE;
$this->setRawContent($renderer->renderRoot($render));
// Ensure that the exposed form is rendered.
$this->assertEqual(1, count($this->xpath('//form[@class="views-exposed-form"]')));
}
}

View file

@ -31,7 +31,7 @@ use Symfony\Component\HttpFoundation\Response;
* @group views
* @see \Drupal\views\ViewExecutable
*/
class ViewExecutableTest extends ViewUnitTestBase {
class ViewExecutableTest extends ViewKernelTestBase {
use CommentTestTrait;

View file

@ -0,0 +1,153 @@
<?php
/**
* @file
* Contains \Drupal\views\Tests\ViewKernelTestBase.
*/
namespace Drupal\views\Tests;
use Drupal\Core\Database\Query\SelectInterface;
use Drupal\views\ViewExecutable;
use Drupal\views\ViewsBundle;
use Drupal\simpletest\KernelTestBase;
use Symfony\Component\HttpFoundation\Request;
/**
* Defines a base class for Views unit testing.
*
* Use this test class for unit tests of Views functionality. If a test
* requires the full web test environment provided by WebTestBase, extend
* ViewTestBase instead.
*
* @see \Drupal\views\Tests\ViewTestBase
*/
abstract class ViewKernelTestBase extends KernelTestBase {
use ViewResultAssertionTrait;
/**
* Modules to enable.
*
* @var array
*/
public static $modules = array('system', 'views', 'views_test_config', 'views_test_data', 'user');
/**
* {@inheritdoc}
*
* @param bool $import_test_views
* Should the views specififed on the test class be imported. If you need
* to setup some additional stuff, like fields, you need to call false and
* then call createTestViews for your own.
*/
protected function setUp($import_test_views = TRUE) {
parent::setUp();
$this->installSchema('system', array('router', 'sequences'));
$this->setUpFixtures();
if ($import_test_views) {
ViewTestData::createTestViews(get_class($this), array('views_test_config'));
}
}
/**
* Sets up the configuration and schema of views and views_test_data modules.
*
* Because the schema of views_test_data.module is dependent on the test
* using it, it cannot be enabled normally.
*/
protected function setUpFixtures() {
// First install the system module. Many Views have Page displays have menu
// links, and for those to work, the system menus must already be present.
$this->installConfig(array('system'));
// Define the schema and views data variable before enabling the test module.
\Drupal::state()->set('views_test_data_schema', $this->schemaDefinition());
\Drupal::state()->set('views_test_data_views_data', $this->viewsData());
$this->installConfig(array('views', 'views_test_config', 'views_test_data'));
foreach ($this->schemaDefinition() as $table => $schema) {
$this->installSchema('views_test_data', $table);
}
\Drupal::service('router.builder')->rebuild();
// Load the test dataset.
$data_set = $this->dataSet();
$query = db_insert('views_test_data')
->fields(array_keys($data_set[0]));
foreach ($data_set as $record) {
$query->values($record);
}
$query->execute();
}
/**
* Orders a nested array containing a result set based on a given column.
*
* @param array $result_set
* An array of rows from a result set, with each row as an associative
* array keyed by column name.
* @param string $column
* The column name by which to sort the result set.
* @param bool $reverse
* (optional) Boolean indicating whether to sort the result set in reverse
* order. Defaults to FALSE.
*
* @return array
* The sorted result set.
*/
protected function orderResultSet($result_set, $column, $reverse = FALSE) {
$order = $reverse ? -1 : 1;
usort($result_set, function ($a, $b) use ($column, $order) {
if ($a[$column] == $b[$column]) {
return 0;
}
return $order * (($a[$column] < $b[$column]) ? -1 : 1);
});
return $result_set;
}
/**
* Executes a view with debugging.
*
* @param \Drupal\views\ViewExecutable $view
* The view object.
* @param array $args
* (optional) An array of the view arguments to use for the view.
*/
protected function executeView($view, array $args = array()) {
$view->setDisplay();
$view->preExecute($args);
$view->execute();
$verbose_message = '<pre>Executed view: ' . ((string) $view->build_info['query']). '</pre>';
if ($view->build_info['query'] instanceof SelectInterface) {
$verbose_message .= '<pre>Arguments: ' . print_r($view->build_info['query']->getArguments(), TRUE) . '</pre>';
}
$this->verbose($verbose_message);
}
/**
* Returns the schema definition.
*/
protected function schemaDefinition() {
return ViewTestData::schemaDefinition();
}
/**
* Returns the views data definition.
*/
protected function viewsData() {
return ViewTestData::viewsData();
}
/**
* Returns a very simple test dataset.
*/
protected function dataSet() {
return ViewTestData::dataSet();
}
}

View file

@ -22,7 +22,7 @@ use Drupal\views\Views;
* @see \Drupal\views\Entity\View
* @see \Drupal\Core\Config\Entity\ConfigEntityStorage
*/
class ViewStorageTest extends ViewUnitTestBase {
class ViewStorageTest extends ViewKernelTestBase {
/**
* Properties that should be stored in the configuration.

View file

@ -15,10 +15,10 @@ use Drupal\views\ViewExecutable;
* Defines a base class for Views testing in the full web test environment.
*
* Use this base test class if you need to emulate a full Drupal installation.
* When possible, ViewUnitTestBase should be used instead. Both base classes
* When possible, ViewKernelTestBase should be used instead. Both base classes
* include the same methods.
*
* @see \Drupal\views\Tests\ViewUnitTestBase
* @see \Drupal\views\Tests\ViewKernelTestBase
* @see \Drupal\simpletest\WebTestBase
*/
abstract class ViewTestBase extends WebTestBase {

Some files were not shown because too many files have changed in this diff Show more