Update to drupal 8.0.0-rc1. For more information, see https://www.drupal.org/node/2582663

This commit is contained in:
Greg Anderson 2015-10-08 11:40:12 -07:00
parent eb34d130a8
commit f32e58e4b1
8476 changed files with 211648 additions and 170042 deletions

View file

@ -0,0 +1,205 @@
<?php
/**
* @file
* Contains \Drupal\Tests\system\Kernel\Entity\EntityReferenceSelectionReferenceableTest.
*/
namespace Drupal\Tests\system\Kernel\Entity;
use Drupal\Component\Utility\Html;
use Drupal\Component\Utility\Unicode;
use Drupal\field\Tests\EntityReference\EntityReferenceTestTrait;
use Drupal\field\Entity\FieldConfig;
use Drupal\node\Entity\NodeType;
use Drupal\KernelTests\KernelTestBase;
/**
* Tests entity reference selection plugins.
*
* @group entity_reference
*/
class EntityReferenceSelectionReferenceableTest extends KernelTestBase {
use EntityReferenceTestTrait;
/**
* Bundle of 'entity_test_no_label' entity.
*
* @var string
*/
protected $bundle;
/**
* Labels to be tested.
*
* @var array
*/
protected static $labels = ['abc', 'Xyz_', 'xyabz_', 'foo_', 'bar_', 'baz_', 'șz_', NULL, '<strong>'];
/**
* The selection handler.
*
* @var \Drupal\Core\Entity\EntityReferenceSelection\SelectionInterface.
*/
protected $selectionHandler;
/**
* {@inheritdoc}
*/
public static $modules = ['system', 'user', 'field', 'entity_reference', 'node', 'entity_test'];
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->installEntitySchema('entity_test_no_label');
/** @var \Drupal\Core\Entity\EntityStorageInterface $storage */
$storage = $this->container->get('entity.manager')
->getStorage('entity_test_no_label');
// Create a new node-type.
NodeType::create([
'type' => $node_type = Unicode::strtolower($this->randomMachineName()),
'name' => $this->randomString(),
])->save();
// Create an entity reference field targeting 'entity_test_no_label'
// entities.
$field_name = Unicode::strtolower($this->randomMachineName());
$this->createEntityReferenceField('node', $node_type, $field_name, $this->randomString(), 'entity_test_no_label');
$field_config = FieldConfig::loadByName('node', $node_type, $field_name);
$this->selectionHandler = $this->container->get('plugin.manager.entity_reference_selection')->getSelectionHandler($field_config);
// Generate a bundle name to be used with 'entity_test_no_label'.
$this->bundle = Unicode::strtolower($this->randomMachineName());
// Create 6 entities to be referenced by the field.
foreach (static::$labels as $name) {
$storage->create([
'id' => Unicode::strtolower($this->randomMachineName()),
'name' => $name,
'type' => $this->bundle,
])->save();
}
}
/**
* Tests values returned by SelectionInterface::getReferenceableEntities()
* when the target entity type has no 'label' key.
*
* @param mixed $match
* The input text to be checked.
* @param string $match_operator
* The matching operator.
* @param int $limit
* The limit of returning records.
* @param int $count_limited
* The expected number of limited entities to be retrieved.
* @param array $items
* Array of entity labels expected to be returned.
* @param int $count_all
* The total number (unlimited) of entities to be retrieved.
*
* @dataProvider providerTestCases
*/
public function testReferenceablesWithNoLabelKey($match, $match_operator, $limit, $count_limited, array $items, $count_all) {
// Test ::getReferenceableEntities().
$referenceables = $this->selectionHandler->getReferenceableEntities($match, $match_operator, $limit);
// Number of returned items.
if (empty($count_limited)) {
$this->assertTrue(empty($referenceables[$this->bundle]));
}
else {
$this->assertSame(count($referenceables[$this->bundle]), $count_limited);
}
// Test returned items.
foreach ($items as $item) {
// SelectionInterface::getReferenceableEntities() always return escaped
// entity labels.
// @see \Drupal\Core\Entity\EntityReferenceSelection\SelectionInterface::getReferenceableEntities()
$item = is_string($item) ? Html::escape($item) : $item;
$this->assertTrue(array_search($item, $referenceables[$this->bundle]) !== FALSE);
}
// Test ::countReferenceableEntities().
$count_referenceables = $this->selectionHandler->countReferenceableEntities($match, $match_operator);
$this->assertSame($count_referenceables, $count_all);
}
/**
* Provides test cases for ::testReferenceablesWithNoLabelKey() test.
*
* @return array[]
*/
public function providerTestCases() {
return [
// All referenceables, no limit. Expecting 9 items.
[NULL, 'CONTAINS', 0, 9, static::$labels, 9],
// Referenceables containing 'w', no limit. Expecting no item.
['w', 'CONTAINS', 0, 0, [], 0],
// Referenceables starting with 'w', no limit. Expecting no item.
['w', 'STARTS_WITH', 0, 0, [], 0],
// Referenceables containing 'ab', no limit. Expecting 2 items ('abc',
// 'xyabz').
['ab', 'CONTAINS', 0, 2, ['abc', 'xyabz_'], 2],
// Referenceables starting with 'A', no limit. Expecting 1 item ('abc').
['A', 'STARTS_WITH', 0, 1, ['abc'], 1],
// Referenceables containing '_', limited to 3. Expecting 3 limited items
// ('Xyz_', 'xyabz_', 'foo_') and 5 total.
['_', 'CONTAINS', 3, 3, ['Xyz_', 'xyabz_', 'foo_'], 6],
// Referenceables ending with with 'z_', limited to 3. Expecting 3 limited
// items ('Xyz_', 'xyabz_', 'baz_') and 4 total.
['z_', 'ENDS_WITH', 3, 3, ['Xyz_', 'xyabz_', 'baz_'], 4],
// Referenceables identical with 'xyabz_', no limit. Expecting 1 item
// ('xyabz_').
['xyabz_', '=', 0, 1, ['xyabz_'], 1],
// Referenceables greater than 'foo', no limit. Expecting 4 items ('Xyz_',
// 'xyabz_', 'foo_', 'șz_').
['foo', '>', 0, 4, ['Xyz_', 'xyabz_', 'foo_', 'șz_'], 4],
// Referenceables greater or identical with 'baz_', no limit. Expecting 5
// items ('Xyz_', 'xyabz_', 'foo_', 'baz_', 'șz_').
['baz_', '>=', 0, 5, ['Xyz_', 'xyabz_', 'foo_', 'baz_', 'șz_'], 5],
// Referenceables less than 'foo', no limit. Expecting 5 items ('abc',
// 'bar_', 'baz_', NULL, '<strong>').
['foo', '<', 0, 5, ['abc', 'bar_', 'baz_', NULL, '<strong>'], 5],
// Referenceables less or identical with 'baz_', no limit. Expecting 5
// items ('abc', 'bar_', 'baz_', NULL, '<strong>').
['baz_', '<=', 0, 5, ['abc', 'bar_', 'baz_', NULL, '<strong>'], 5],
// Referenceables not identical with 'baz_', no limit. Expecting 7 items
// ('abc', 'Xyz_', 'xyabz_', 'foo_', 'bar_', 'șz_', NULL, '<strong>').
['baz_', '<>', 0, 8, ['abc', 'Xyz_', 'xyabz_', 'foo_', 'bar_', 'șz_', NULL, '<strong>'], 8],
// Referenceables in ('bar_', 'baz_'), no limit. Expecting 2 items
// ('bar_', 'baz_')
[['bar_', 'baz_'], 'IN', 0, 2, ['bar_', 'baz_'], 2],
// Referenceables not in ('bar_', 'baz_'), no limit. Expecting 6 items
// ('abc', 'Xyz_', 'xyabz_', 'foo_', 'șz_', NULL, '<strong>')
[['bar_', 'baz_'], 'NOT IN', 0, 7, ['abc', 'Xyz_', 'xyabz_', 'foo_', 'șz_', NULL, '<strong>'], 7],
// Referenceables not null, no limit. Expecting 9 items ('abc', 'Xyz_',
// 'xyabz_', 'foo_', 'bar_', 'baz_', 'șz_', NULL, '<strong>').
//
// Note: Even we set the name as NULL, when retrieving the label from the
// entity we'll get an empty string, meaning that this match operator
// will return TRUE every time.
[NULL, 'IS NOT NULL', 0, 9, static::$labels, 9],
// Referenceables null, no limit. Expecting 9 items ('abc', 'Xyz_',
// 'xyabz_', 'foo_', 'bar_', 'baz_', 'șz_', NULL, '<strong>').
//
// Note: Even we set the name as NULL, when retrieving the label from the
// entity we'll get an empty string, meaning that this match operator
// will return FALSE every time.
[NULL, 'IS NULL', 0, 9, static::$labels, 9],
// Referenceables containing '<strong>' markup, no limit. Expecting 1 item
// ('<strong>').
['<strong>', 'CONTAINS', 0, 1, ['<strong>'], 1],
// Test an unsupported operator. We expect no items.
['abc', '*unsupported*', 0, 0, [], 0],
];
}
}

View file

@ -7,6 +7,7 @@
namespace Drupal\Tests\system\Kernel\Extension;
use Drupal\Component\FileCache\FileCacheFactory;
use Drupal\Core\DependencyInjection\ContainerBuilder;
use \Drupal\Core\Extension\ModuleUninstallValidatorException;
use Drupal\KernelTests\KernelTestBase;
@ -18,6 +19,11 @@ use Drupal\KernelTests\KernelTestBase;
*/
class ModuleHandlerTest extends KernelTestBase {
/**
* {@inheritdoc}
*/
public static $modules = ['system'];
/**
* {@inheritdoc}
*/
@ -48,7 +54,7 @@ class ModuleHandlerTest extends KernelTestBase {
* The basic functionality of retrieving enabled modules.
*/
function testModuleList() {
$module_list = array();
$module_list = ['system'];
$this->assertModuleList($module_list, 'Initial');

View file

@ -0,0 +1,137 @@
<?php
/**
* @file
* Contains \Drupal\Tests\system\Kernel\Scripts\DbCommandBaseTest.
*/
namespace Drupal\Tests\system\Kernel\Scripts;
use Drupal\Core\Command\DbCommandBase;
use Drupal\Core\Database\Database;
use Drupal\KernelTests\KernelTestBase;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Tester\CommandTester;
/**
* Test that the DbToolsApplication works correctly.
*
* The way console application's run it is impossible to test. For now we only
* test that we are registering the correct commands.
*
* @group console
*/
class DbCommandBaseTest extends KernelTestBase {
/**
* Test specifying a database key.
*/
public function testSpecifyDatabaseKey() {
$command = new DbCommandBaseTester();
$command_tester = new CommandTester($command);
Database::addConnectionInfo('magic_db', 'default', Database::getConnectionInfo('default')['default']);
$command_tester->execute([
'--database' => 'magic_db'
]);
$this->assertEquals('magic_db', $command->getDatabaseConnection($command_tester->getInput())->getKey(),
'Special db key is returned');
}
/**
* Invalid database names will throw a useful exception.
*
* @expectedException \Drupal\Core\Database\ConnectionNotDefinedException
*/
public function testSpecifyDatabaseDoesNotExist() {
$command = new DbCommandBaseTester();
$command_tester = new CommandTester($command);
$command_tester->execute([
'--database' => 'dne'
]);
$command->getDatabaseConnection($command_tester->getInput());
}
/**
* Test supplying database connection as a url.
*/
public function testSpecifyDbUrl() {
$connection_info = Database::getConnectionInfo('default')['default'];
$command = new DbCommandBaseTester();
$command_tester = new CommandTester($command);
$command_tester->execute([
'-db-url' => $connection_info['driver'] . '://' . $connection_info['username'] . ':' . $connection_info['password'] . '@' . $connection_info['host'] . '/' . $connection_info['database']
]);
$this->assertEquals('db-tools', $command->getDatabaseConnection($command_tester->getInput())->getKey());
Database::removeConnection('db-tools');
$command_tester->execute([
'--database-url' => $connection_info['driver'] . '://' . $connection_info['username'] . ':' . $connection_info['password'] . '@' . $connection_info['host'] . '/' . $connection_info['database']
]);
$this->assertEquals('db-tools', $command->getDatabaseConnection($command_tester->getInput())->getKey());
}
/**
* Test specifying a prefix for different connections.
*/
public function testPrefix() {
if (Database::getConnection()->driver() == 'sqlite') {
$this->markTestSkipped('SQLITE modifies the prefixes so we cannot effectively test it');
}
Database::addConnectionInfo('magic_db', 'default', Database::getConnectionInfo('default')['default']);
$command = new DbCommandBaseTester();
$command_tester = new CommandTester($command);
$command_tester->execute([
'--database' => 'magic_db',
'--prefix' => 'extra',
]);
$this->assertEquals('extra', $command->getDatabaseConnection($command_tester->getInput())->tablePrefix());
$connection_info = Database::getConnectionInfo('default')['default'];
$command_tester->execute([
'-db-url' => $connection_info['driver'] . '://' . $connection_info['username'] . ':' . $connection_info['password'] . '@' . $connection_info['host'] . '/' . $connection_info['database'],
'--prefix' => 'extra2',
]);
$this->assertEquals('extra2', $command->getDatabaseConnection($command_tester->getInput())->tablePrefix());
// This breaks simpletest cleanup.
// $command_tester->execute([
// '--prefix' => 'notsimpletest',
// ]);
// $this->assertEquals('notsimpletest', $command->getDatabaseConnection($command_tester->getInput())->tablePrefix());
}
}
/**
* Concrete command implementation for testing base features.
*/
class DbCommandBaseTester extends DbCommandBase {
/**
* {@inheritdoc}
*/
public function configure() {
parent::configure();
$this->setName('test');
}
/**
* {@inheritdoc}
*/
public function getDatabaseConnection(InputInterface $input) {
return parent::getDatabaseConnection($input);
}
/**
* {@inheritdoc}
*/
protected function execute(InputInterface $input, OutputInterface $output) {
// Empty implementation for testing.
}
}

View file

@ -0,0 +1,88 @@
<?php
/**
* @file
* Contains \Drupal\Tests\system\Kernel\Scripts\DbDumpCommandTest.
*/
namespace Drupal\Tests\system\Kernel\Scripts;
use Drupal\Core\Command\DbDumpCommand;
use Drupal\Core\Database\Database;
use Drupal\KernelTests\KernelTestBase;
use Symfony\Component\Console\Tester\CommandTester;
/**
* Test that the DbDumpCommand works correctly.
*
* @group console
*/
class DbDumpCommandTest extends KernelTestBase {
/**
* {@inheritdoc}
*/
public static $modules = ['system'];
/**
* {@inheritdoc}
*/
public function setUp() {
parent::setUp();
// Determine what database backend is running, and set the skip flag.
if (Database::getConnection()->databaseType() !== 'mysql') {
$this->markTestSkipped("Skipping test since the DbDumpCommand is currently only compatible with MySQL");
}
$this->installSchema('system', 'router');
/** @var \Drupal\Core\Database\Connection $connection */
$connection = $this->container->get('database');
$connection->insert('router')->fields(['name', 'path', 'pattern_outline'])->values(['test', 'test', 'test'])->execute();
}
/**
* Test the command directly.
*/
public function testDbDumpCommand() {
$command = new DbDumpCommand();
$command_tester = new CommandTester($command);
$command_tester->execute([]);
// Assert that insert exists and that some expected fields exist.
$output = $command_tester->getDisplay();
$this->assertContains("createTable('router", $output, 'Table router found');
$this->assertContains("insert('router", $output, 'Insert found');
$this->assertContains("'name' => 'test", $output, 'Insert name field found');
$this->assertContains("'path' => 'test", $output, 'Insert path field found');
$this->assertContains("'pattern_outline' => 'test", $output, 'Insert pattern_outline field found');
}
/**
* Test schema only option.
*/
public function testSchemaOnly() {
$command = new DbDumpCommand();
$command_tester = new CommandTester($command);
$command_tester->execute(['--schema-only' => 'router']);
// Assert that insert statement doesn't exist for schema only table.
$output = $command_tester->getDisplay();
$this->assertContains("createTable('router", $output, 'Table router found');
$this->assertNotContains("insert('router", $output, 'Insert not found');
$this->assertNotContains("'name' => 'test", $output, 'Insert name field not found');
$this->assertNotContains("'path' => 'test", $output, 'Insert path field not found');
$this->assertNotContains("'pattern_outline' => 'test", $output, 'Insert pattern_outline field not found');
// Assert that insert statement doesn't exist for wildcard schema only match.
$command_tester->execute(['--schema-only' => 'route.*']);
$output = $command_tester->getDisplay();
$this->assertContains("createTable('router", $output, 'Table router found');
$this->assertNotContains("insert('router", $output, 'Insert not found');
$this->assertNotContains("'name' => 'test", $output, 'Insert name field not found');
$this->assertNotContains("'path' => 'test", $output, 'Insert path field not found');
$this->assertNotContains("'pattern_outline' => 'test", $output, 'Insert pattern_outline field not found');
}
}

View file

@ -0,0 +1,82 @@
<?php
/**
* @file
* Contains \Drupal\Tests\system\Kernel\Scripts\DbImportCommandTest.
*/
namespace Drupal\Tests\system\Kernel\Scripts;
use Drupal\Core\Command\DbImportCommand;
use Drupal\Core\Database\Database;
use Drupal\KernelTests\KernelTestBase;
use Symfony\Component\Console\Tester\CommandTester;
/**
* Test that the DbImportCommand works correctly.
*
* @group console
*/
class DbImportCommandTest extends KernelTestBase {
/**
* {@inheritdoc}
*/
public static $modules = ['system', 'config', 'dblog', 'menu_link_content', 'link', 'block_content', 'file', 'user'];
/**
* Tables that should be part of the exported script.
*
* @var array
*/
protected $tables = [
'block_content',
'block_content_field_data',
'block_content_field_revision',
'block_content_revision',
'cachetags',
'config',
'cache_discovery',
'cache_bootstrap',
'file_managed',
'key_value_expire',
'menu_link_content',
'menu_link_content_data',
'semaphore',
'sessions',
'url_alias',
'user__roles',
'users',
'users_field_data',
'watchdog',
];
/**
* Test the command directly.
*
* @requires extension pdo_sqlite
*/
public function testDbImportCommand() {
$connection_info = array(
'driver' => 'sqlite',
'database' => ':memory:',
);
Database::addConnectionInfo($this->databasePrefix, 'default', $connection_info);
$command = new DbImportCommand();
$command_tester = new CommandTester($command);
$command_tester->execute([
'script' => __DIR__ . '/../../../fixtures/update/drupal-8.bare.standard.php.gz',
'--database' => $this->databasePrefix,
]);
// The tables should now exist.
$connection = Database::getConnection('default', $this->databasePrefix);
foreach ($this->tables as $table) {
$this->assertTrue($connection
->schema()
->tableExists($table), strtr('Table @table created by the database script.', ['@table' => $table]));
}
}
}

View file

@ -0,0 +1,41 @@
<?php
/**
* @file
* Contains \Drupal\Tests\system\Kernel\Scripts\DbToolsApplicationTest.
*/
namespace Drupal\Tests\system\Kernel\Scripts;
use Drupal\Core\Command\DbToolsApplication;
use Drupal\KernelTests\KernelTestBase;
/**
* Test that the DbToolsApplication works correctly.
*
* The way console application's run it is impossible to test. For now we only
* test that we are registering the correct commands.
*
* @group console
*/
class DbToolsApplicationTest extends KernelTestBase {
/**
* Test that the dump command is correctly registered.
*/
public function testDumpCommandRegistration() {
$application = new DbToolsApplication();
$command = $application->find('dump');
$this->assertInstanceOf('\Drupal\Core\Command\DbDumpCommand', $command);
}
/**
* Test that the dump command is correctly registered.
*/
public function testImportCommandRegistration() {
$application = new DbToolsApplication();
$command = $application->find('import');
$this->assertInstanceOf('\Drupal\Core\Command\DbImportCommand', $command);
}
}

View file

@ -124,6 +124,7 @@ class PathBasedBreadcrumbBuilderTest extends UnitTestCase {
$cache_contexts_manager = $this->getMockBuilder('Drupal\Core\Cache\Context\CacheContextsManager')
->disableOriginalConstructor()
->getMock();
$cache_contexts_manager->method('assertValidTokens')->willReturn(TRUE);
$cache_contexts_manager->expects($this->any())
->method('validate_tokens');
$container = new Container();

View file

@ -47,6 +47,7 @@ class MenuLinkTreeTest extends UnitTestCase {
$cache_contexts_manager = $this->getMockBuilder('Drupal\Core\Cache\Context\CacheContextsManager')
->disableOriginalConstructor()
->getMock();
$cache_contexts_manager->method('assertValidTokens')->willReturn(TRUE);
$container = new ContainerBuilder();
$container->set('cache_contexts_manager', $cache_contexts_manager);
\Drupal::setContainer($container);
@ -140,14 +141,29 @@ class MenuLinkTreeTest extends UnitTestCase {
]
];
$get_built_element = function(MenuLinkTreeElement $element, array $classes) {
return [
'attributes' => new Attribute(['class' => array_merge(['menu-item'], $classes)]),
$get_built_element = function(MenuLinkTreeElement $element) {
$return = [
'attributes' => new Attribute(),
'title' => $element->link->getTitle(),
'url' => new Url($element->link->getRouteName(), $element->link->getRouteParameters(), ['set_active_class' => TRUE]),
'below' => [],
'original_link' => $element->link,
'is_expanded' => FALSE,
'is_collapsed' => FALSE,
'in_active_trail' => FALSE,
];
if ($element->hasChildren && !empty($element->subtree)) {
$return['is_expanded'] = TRUE;
}
elseif ($element->hasChildren) {
$return['is_collapsed'] = TRUE;
}
if ($element->inActiveTrail) {
$return['in_active_trail'] = TRUE;
}
return $return;
};
// The three access scenarios described in this method's documentation.
@ -195,7 +211,7 @@ class MenuLinkTreeTest extends UnitTestCase {
$tree[0]->access = $access;
if ($access === NULL || $access->isAllowed()) {
$expected_build = $base_expected_build;
$expected_build['#items']['test.example1'] = $get_built_element($tree[0], []);
$expected_build['#items']['test.example1'] = $get_built_element($tree[0]);
}
else {
$expected_build = $base_expected_build_empty;
@ -217,9 +233,9 @@ class MenuLinkTreeTest extends UnitTestCase {
$tree[0]->access = $access;
$expected_build = $base_expected_build;
if ($access === NULL || $access->isAllowed()) {
$expected_build['#items']['test.example1'] = $get_built_element($tree[0], []);
$expected_build['#items']['test.example1'] = $get_built_element($tree[0]);
}
$expected_build['#items']['test.example2'] = $get_built_element($tree[1], []);
$expected_build['#items']['test.example2'] = $get_built_element($tree[1]);
$expected_build['#cache']['contexts'] = array_merge($expected_build['#cache']['contexts'], $access_cache_contexts, $links[0]->getCacheContexts(), $links[1]->getCacheContexts());
$data[] = [
'description' => "Single-level tree; access=$i; link=$j.",
@ -245,13 +261,13 @@ class MenuLinkTreeTest extends UnitTestCase {
];
$tree[0]->subtree[0]->subtree[0]->access = $access;
$expected_build = $base_expected_build;
$expected_build['#items']['test.roota'] = $get_built_element($tree[0], ['menu-item--expanded']);
$expected_build['#items']['test.roota']['below']['test.parentc'] = $get_built_element($tree[0]->subtree[0], ['menu-item--expanded']);
$expected_build['#items']['test.roota'] = $get_built_element($tree[0]);
$expected_build['#items']['test.roota']['below']['test.parentc'] = $get_built_element($tree[0]->subtree[0]);
if ($access === NULL || $access->isAllowed()) {
$expected_build['#items']['test.roota']['below']['test.parentc']['below']['test.example1'] = $get_built_element($tree[0]->subtree[0]->subtree[0], []);
$expected_build['#items']['test.roota']['below']['test.parentc']['below']['test.example1'] = $get_built_element($tree[0]->subtree[0]->subtree[0]);
}
$expected_build['#items']['test.rootb'] = $get_built_element($tree[1], ['menu-item--expanded']);
$expected_build['#items']['test.rootb']['below']['test.example2'] = $get_built_element($tree[1]->subtree[0], []);
$expected_build['#items']['test.rootb'] = $get_built_element($tree[1]);
$expected_build['#items']['test.rootb']['below']['test.example2'] = $get_built_element($tree[1]->subtree[0]);
$expected_build['#cache']['contexts'] = array_merge($expected_build['#cache']['contexts'], $access_cache_contexts, $links[0]->getCacheContexts(), $links[1]->getCacheContexts());
$data[] = [
'description' => "Multi-level tree; access=$i; link=$j.",