Initial Drupal 11 with DDEV setup

This commit is contained in:
gluebox
2025-10-08 11:39:17 -04:00
commit 89ef74b305
25344 changed files with 2599172 additions and 0 deletions

View File

@ -0,0 +1,83 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\taxonomy\Functional;
use Drupal\Core\Datetime\DrupalDateTime;
use Drupal\Core\Field\FieldStorageDefinitionInterface;
use Drupal\taxonomy\Entity\Vocabulary;
/**
* Posts an article with a taxonomy term and a date prior to 1970.
*
* @group taxonomy
*/
class EarlyDateTest extends TaxonomyTestBase {
/**
* {@inheritdoc}
*/
protected static $modules = ['node', 'datetime'];
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
// Create a tags vocabulary for the 'article' content type.
$vocabulary = Vocabulary::create([
'name' => 'Tags',
'vid' => 'tags',
]);
$vocabulary->save();
$field_name = 'field_' . $vocabulary->id();
$handler_settings = [
'target_bundles' => [
$vocabulary->id() => $vocabulary->id(),
],
'auto_create' => TRUE,
];
$this->createEntityReferenceField('node', 'article', $field_name, 'Tags', 'taxonomy_term', 'default', $handler_settings, FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED);
\Drupal::service('entity_display.repository')
->getFormDisplay('node', 'article')
->setComponent($field_name, [
'type' => 'entity_reference_autocomplete_tags',
])
->save();
$this->drupalLogin($this->drupalCreateUser([
'administer taxonomy',
'administer nodes',
'bypass node access',
]));
}
/**
* Tests taxonomy functionality with nodes prior to 1970.
*/
public function testTaxonomyEarlyDateNode(): void {
// Posts an article with a taxonomy term and a date prior to 1970.
$date = new DrupalDateTime('1969-01-01 00:00:00');
$edit = [];
$edit['title[0][value]'] = $this->randomMachineName();
$edit['created[0][value][date]'] = $date->format('Y-m-d');
$edit['created[0][value][time]'] = $date->format('H:i:s');
$edit['body[0][value]'] = $this->randomMachineName();
$edit['field_tags[target_id]'] = $this->randomMachineName();
$this->drupalGet('node/add/article');
$this->submitForm($edit, 'Save');
// Checks that the node has been saved.
$node = $this->drupalGetNodeByTitle($edit['title[0][value]']);
$this->assertEquals($date->getTimestamp(), $node->getCreatedTime(), 'Legacy node was saved with the right date.');
}
}

View File

@ -0,0 +1,14 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\taxonomy\Functional;
use Drupal\Tests\system\Functional\Module\GenericModuleTestBase;
/**
* Generic module test for taxonomy.
*
* @group taxonomy
*/
class GenericTest extends GenericModuleTestBase {}

View File

@ -0,0 +1,31 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\taxonomy\Functional\Rest;
use Drupal\Tests\rest\Functional\AnonResourceTestTrait;
/**
* @group rest
*/
class TermJsonAnonTest extends TermResourceTestBase {
use AnonResourceTestTrait;
/**
* {@inheritdoc}
*/
protected static $format = 'json';
/**
* {@inheritdoc}
*/
protected static $mimeType = 'application/json';
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
}

View File

@ -0,0 +1,41 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\taxonomy\Functional\Rest;
use Drupal\Tests\rest\Functional\BasicAuthResourceTestTrait;
/**
* @group rest
*/
class TermJsonBasicAuthTest extends TermResourceTestBase {
use BasicAuthResourceTestTrait;
/**
* {@inheritdoc}
*/
protected static $modules = ['basic_auth'];
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* {@inheritdoc}
*/
protected static $format = 'json';
/**
* {@inheritdoc}
*/
protected static $mimeType = 'application/json';
/**
* {@inheritdoc}
*/
protected static $auth = 'basic_auth';
}

View File

@ -0,0 +1,37 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\taxonomy\Functional\Rest;
use Drupal\Tests\rest\Functional\CookieResourceTestTrait;
/**
* @group rest
* @group #slow
*/
class TermJsonCookieTest extends TermResourceTestBase {
use CookieResourceTestTrait;
/**
* {@inheritdoc}
*/
protected static $format = 'json';
/**
* {@inheritdoc}
*/
protected static $mimeType = 'application/json';
/**
* {@inheritdoc}
*/
protected static $auth = 'cookie';
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
}

View File

@ -0,0 +1,404 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\taxonomy\Functional\Rest;
use Drupal\Core\Cache\Cache;
use Drupal\taxonomy\Entity\Term;
use Drupal\taxonomy\Entity\Vocabulary;
use Drupal\Tests\rest\Functional\EntityResource\EntityResourceTestBase;
use GuzzleHttp\RequestOptions;
use PHPUnit\Framework\Attributes\Before;
/**
* Resource test base for taxonomy term entity.
*/
abstract class TermResourceTestBase extends EntityResourceTestBase {
/**
* {@inheritdoc}
*/
protected static $modules = ['content_translation', 'path', 'taxonomy'];
/**
* {@inheritdoc}
*/
protected static $entityTypeId = 'taxonomy_term';
/**
* {@inheritdoc}
*/
protected static $patchProtectedFieldNames = [
'changed' => NULL,
];
/**
* @var \Drupal\taxonomy\TermInterface
*/
protected $entity;
/**
* Marks some tests as skipped because XML cannot be deserialized.
*/
#[Before]
public function termResourceTestBaseSkipTests(): void {
if (static::$format === 'xml' && $this->name() === 'testPatchPath') {
$this->markTestSkipped('Deserialization of the XML format is not supported.');
}
}
/**
* {@inheritdoc}
*/
protected function setUpAuthorization($method) {
switch ($method) {
case 'GET':
$this->grantPermissionsToTestedRole(['access content']);
break;
case 'POST':
$this->grantPermissionsToTestedRole(['create terms in camelids']);
break;
case 'PATCH':
// Grant the 'create url aliases' permission to test the case when
// the path field is accessible, see
// \Drupal\Tests\rest\Functional\EntityResource\Node\NodeResourceTestBase
// for a negative test.
$this->grantPermissionsToTestedRole(['edit terms in camelids', 'create url aliases']);
break;
case 'DELETE':
$this->grantPermissionsToTestedRole(['delete terms in camelids']);
break;
}
}
/**
* {@inheritdoc}
*/
protected function createEntity() {
$vocabulary = Vocabulary::load('camelids');
if (!$vocabulary) {
// Create a "Camelids" vocabulary.
$vocabulary = Vocabulary::create([
'name' => 'Camelids',
'vid' => 'camelids',
]);
$vocabulary->save();
}
// Create a "Llama" taxonomy term.
$term = Term::create(['vid' => $vocabulary->id()])
->setName('Llama')
->setDescription("It is a little known fact that llamas cannot count higher than seven.")
->setChangedTime(123456789)
->set('path', '/llama');
$term->save();
return $term;
}
/**
* {@inheritdoc}
*/
protected function getExpectedNormalizedEntity() {
// We test with multiple parent terms, and combinations thereof.
// @see ::createEntity()
// @see ::testGet()
// @see ::testGetTermWithParent()
// @see ::providerTestGetTermWithParent()
$parent_term_ids = [];
for ($i = 0; $i < $this->entity->get('parent')->count(); $i++) {
$parent_term_ids[$i] = (int) $this->entity->get('parent')[$i]->target_id;
}
$expected_parent_normalization = FALSE;
switch ($parent_term_ids) {
case [0]:
$expected_parent_normalization = [
[
'target_id' => NULL,
],
];
break;
case [2]:
$expected_parent_normalization = [
[
'target_id' => 2,
'target_type' => 'taxonomy_term',
'target_uuid' => Term::load(2)->uuid(),
'url' => base_path() . 'taxonomy/term/2',
],
];
break;
case [0, 2]:
$expected_parent_normalization = [
[
'target_id' => NULL,
],
[
'target_id' => 2,
'target_type' => 'taxonomy_term',
'target_uuid' => Term::load(2)->uuid(),
'url' => base_path() . 'taxonomy/term/2',
],
];
break;
case [3, 2]:
$expected_parent_normalization = [
[
'target_id' => 3,
'target_type' => 'taxonomy_term',
'target_uuid' => Term::load(3)->uuid(),
'url' => base_path() . 'taxonomy/term/3',
],
[
'target_id' => 2,
'target_type' => 'taxonomy_term',
'target_uuid' => Term::load(2)->uuid(),
'url' => base_path() . 'taxonomy/term/2',
],
];
break;
}
return [
'tid' => [
['value' => 1],
],
'revision_id' => [
['value' => 1],
],
'uuid' => [
['value' => $this->entity->uuid()],
],
'vid' => [
[
'target_id' => 'camelids',
'target_type' => 'taxonomy_vocabulary',
'target_uuid' => Vocabulary::load('camelids')->uuid(),
],
],
'name' => [
['value' => 'Llama'],
],
'description' => [
[
'value' => 'It is a little known fact that llamas cannot count higher than seven.',
'format' => NULL,
'processed' => "<p>It is a little known fact that llamas cannot count higher than seven.</p>\n",
],
],
'parent' => $expected_parent_normalization,
'weight' => [
['value' => 0],
],
'langcode' => [
[
'value' => 'en',
],
],
'changed' => [
[
'value' => (new \DateTime())->setTimestamp($this->entity->getChangedTime())->setTimezone(new \DateTimeZone('UTC'))->format(\DateTime::RFC3339),
'format' => \DateTime::RFC3339,
],
],
'default_langcode' => [
[
'value' => TRUE,
],
],
'path' => [
[
'alias' => '/llama',
'pid' => 1,
'langcode' => 'en',
],
],
'status' => [
[
'value' => TRUE,
],
],
'revision_created' => [
[
'value' => (new \DateTime())->setTimestamp((int) $this->entity->getRevisionCreationTime())
->setTimezone(new \DateTimeZone('UTC'))
->format(\DateTime::RFC3339),
'format' => \DateTime::RFC3339,
],
],
'revision_user' => [],
'revision_translation_affected' => [
[
'value' => TRUE,
],
],
];
}
/**
* {@inheritdoc}
*/
protected function getNormalizedPostEntity() {
return [
'vid' => [
[
'target_id' => 'camelids',
],
],
'name' => [
[
'value' => 'Drama llama',
],
],
'description' => [
[
'value' => 'Drama llamas are the coolest camelids.',
'format' => NULL,
],
],
];
}
/**
* {@inheritdoc}
*/
protected function getExpectedUnauthorizedAccessMessage($method) {
switch ($method) {
case 'GET':
return "The 'access content' permission is required and the taxonomy term must be published.";
case 'POST':
return "The following permissions are required: 'create terms in camelids' OR 'administer taxonomy'.";
case 'PATCH':
return "The following permissions are required: 'edit terms in camelids' OR 'administer taxonomy'.";
case 'DELETE':
return "The following permissions are required: 'delete terms in camelids' OR 'administer taxonomy'.";
default:
return parent::getExpectedUnauthorizedAccessMessage($method);
}
}
/**
* Tests PATCHing a term's path.
*
* For a negative test, see the similar test coverage for Node.
*
* @see \Drupal\Tests\rest\Functional\EntityResource\Node\NodeResourceTestBase::testPatchPath()
*/
public function testPatchPath(): void {
$this->initAuthentication();
$this->provisionEntityResource();
$this->setUpAuthorization('GET');
$this->setUpAuthorization('PATCH');
$url = $this->getEntityResourceUrl()->setOption('query', ['_format' => static::$format]);
// GET term's current normalization.
$response = $this->request('GET', $url, $this->getAuthenticationRequestOptions('GET'));
$normalization = $this->serializer->decode((string) $response->getBody(), static::$format);
// Change term's path alias.
$normalization['path'][0]['alias'] .= 's-rule-the-world';
// Create term PATCH request.
$request_options = [];
$request_options[RequestOptions::HEADERS]['Content-Type'] = static::$mimeType;
$request_options = array_merge_recursive($request_options, $this->getAuthenticationRequestOptions('PATCH'));
$request_options[RequestOptions::BODY] = $this->serializer->encode($normalization, static::$format);
// PATCH request: 200.
$response = $this->request('PATCH', $url, $request_options);
$this->assertResourceResponse(200, FALSE, $response);
$updated_normalization = $this->serializer->decode((string) $response->getBody(), static::$format);
$this->assertSame($normalization['path'], $updated_normalization['path']);
}
/**
* {@inheritdoc}
*/
protected function getExpectedCacheTags() {
return Cache::mergeTags(parent::getExpectedCacheTags(), ['config:filter.format.plain_text', 'config:filter.settings']);
}
/**
* {@inheritdoc}
*/
protected function getExpectedCacheContexts() {
return Cache::mergeContexts(['url.site'], $this->container->getParameter('renderer.config')['required_cache_contexts']);
}
/**
* Tests GETting a term with a parent term other than the default <root> (0).
*
* @see ::getExpectedNormalizedEntity()
*
* @dataProvider providerTestGetTermWithParent
*/
public function testGetTermWithParent(array $parent_term_ids): void {
// Create all possible parent terms.
Term::create(['vid' => Vocabulary::load('camelids')->id()])
->setName('Lamoids')
->save();
Term::create(['vid' => Vocabulary::load('camelids')->id()])
->setName('Camels')
->save();
// Modify the entity under test to use the provided parent terms.
$this->entity->set('parent', $parent_term_ids)->save();
$this->initAuthentication();
$url = $this->getEntityResourceUrl();
$url->setOption('query', ['_format' => static::$format]);
$request_options = $this->getAuthenticationRequestOptions('GET');
$this->provisionEntityResource();
$this->setUpAuthorization('GET');
$response = $this->request('GET', $url, $request_options);
$expected = $this->getExpectedNormalizedEntity();
static::recursiveKSort($expected);
$actual = $this->serializer->decode((string) $response->getBody(), static::$format);
static::recursiveKSort($actual);
$this->assertSame($expected, $actual);
}
/**
* Data provider for ::testGetTermWithParent().
*/
public static function providerTestGetTermWithParent() {
return [
'root parent: [0] (= no parent)' => [
[0],
],
'non-root parent: [2]' => [
[2],
],
'multiple parents: [0,2] (root + non-root parent)' => [
[0, 2],
],
'multiple parents: [3,2] (both non-root parents)' => [
[3, 2],
],
];
}
/**
* {@inheritdoc}
*/
protected function getExpectedUnauthorizedEntityAccessCacheability($is_authenticated) {
// @see \Drupal\taxonomy\TermAccessControlHandler::checkAccess()
return parent::getExpectedUnauthorizedEntityAccessCacheability($is_authenticated)
->addCacheTags(['taxonomy_term:1']);
}
}

View File

@ -0,0 +1,33 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\taxonomy\Functional\Rest;
use Drupal\Tests\rest\Functional\AnonResourceTestTrait;
use Drupal\Tests\rest\Functional\EntityResource\XmlEntityNormalizationQuirksTrait;
/**
* @group rest
*/
class TermXmlAnonTest extends TermResourceTestBase {
use AnonResourceTestTrait;
use XmlEntityNormalizationQuirksTrait;
/**
* {@inheritdoc}
*/
protected static $format = 'xml';
/**
* {@inheritdoc}
*/
protected static $mimeType = 'text/xml; charset=UTF-8';
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
}

View File

@ -0,0 +1,43 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\taxonomy\Functional\Rest;
use Drupal\Tests\rest\Functional\BasicAuthResourceTestTrait;
use Drupal\Tests\rest\Functional\EntityResource\XmlEntityNormalizationQuirksTrait;
/**
* @group rest
*/
class TermXmlBasicAuthTest extends TermResourceTestBase {
use BasicAuthResourceTestTrait;
use XmlEntityNormalizationQuirksTrait;
/**
* {@inheritdoc}
*/
protected static $modules = ['basic_auth'];
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* {@inheritdoc}
*/
protected static $format = 'xml';
/**
* {@inheritdoc}
*/
protected static $mimeType = 'text/xml; charset=UTF-8';
/**
* {@inheritdoc}
*/
protected static $auth = 'basic_auth';
}

View File

@ -0,0 +1,38 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\taxonomy\Functional\Rest;
use Drupal\Tests\rest\Functional\CookieResourceTestTrait;
use Drupal\Tests\rest\Functional\EntityResource\XmlEntityNormalizationQuirksTrait;
/**
* @group rest
*/
class TermXmlCookieTest extends TermResourceTestBase {
use CookieResourceTestTrait;
use XmlEntityNormalizationQuirksTrait;
/**
* {@inheritdoc}
*/
protected static $format = 'xml';
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* {@inheritdoc}
*/
protected static $mimeType = 'text/xml; charset=UTF-8';
/**
* {@inheritdoc}
*/
protected static $auth = 'cookie';
}

View File

@ -0,0 +1,31 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\taxonomy\Functional\Rest;
use Drupal\Tests\rest\Functional\AnonResourceTestTrait;
/**
* @group rest
*/
class VocabularyJsonAnonTest extends VocabularyResourceTestBase {
use AnonResourceTestTrait;
/**
* {@inheritdoc}
*/
protected static $format = 'json';
/**
* {@inheritdoc}
*/
protected static $mimeType = 'application/json';
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
}

View File

@ -0,0 +1,41 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\taxonomy\Functional\Rest;
use Drupal\Tests\rest\Functional\BasicAuthResourceTestTrait;
/**
* @group rest
*/
class VocabularyJsonBasicAuthTest extends VocabularyResourceTestBase {
use BasicAuthResourceTestTrait;
/**
* {@inheritdoc}
*/
protected static $modules = ['basic_auth'];
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* {@inheritdoc}
*/
protected static $format = 'json';
/**
* {@inheritdoc}
*/
protected static $mimeType = 'application/json';
/**
* {@inheritdoc}
*/
protected static $auth = 'basic_auth';
}

View File

@ -0,0 +1,36 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\taxonomy\Functional\Rest;
use Drupal\Tests\rest\Functional\CookieResourceTestTrait;
/**
* @group rest
*/
class VocabularyJsonCookieTest extends VocabularyResourceTestBase {
use CookieResourceTestTrait;
/**
* {@inheritdoc}
*/
protected static $format = 'json';
/**
* {@inheritdoc}
*/
protected static $mimeType = 'application/json';
/**
* {@inheritdoc}
*/
protected static $auth = 'cookie';
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
}

View File

@ -0,0 +1,85 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\taxonomy\Functional\Rest;
use Drupal\taxonomy\Entity\Vocabulary;
use Drupal\Tests\rest\Functional\EntityResource\ConfigEntityResourceTestBase;
/**
* Resource test base for the TaxonomyVocabulary entity.
*/
abstract class VocabularyResourceTestBase extends ConfigEntityResourceTestBase {
/**
* {@inheritdoc}
*/
protected static $modules = ['taxonomy'];
/**
* {@inheritdoc}
*/
protected static $entityTypeId = 'taxonomy_vocabulary';
/**
* @var \Drupal\taxonomy\VocabularyInterface
*/
protected $entity;
/**
* {@inheritdoc}
*/
protected function setUpAuthorization($method) {
$this->grantPermissionsToTestedRole(['administer taxonomy']);
}
/**
* {@inheritdoc}
*/
protected function createEntity() {
$vocabulary = Vocabulary::create([
'name' => 'Llama',
'vid' => 'llama',
]);
$vocabulary->save();
return $vocabulary;
}
/**
* {@inheritdoc}
*/
protected function getExpectedNormalizedEntity() {
return [
'uuid' => $this->entity->uuid(),
'vid' => 'llama',
'langcode' => 'en',
'status' => TRUE,
'dependencies' => [],
'name' => 'Llama',
'description' => NULL,
'weight' => 0,
'new_revision' => FALSE,
];
}
/**
* {@inheritdoc}
*/
protected function getNormalizedPostEntity() {
// @todo Update in https://www.drupal.org/node/2300677.
return [];
}
/**
* {@inheritdoc}
*/
protected function getExpectedUnauthorizedAccessMessage($method) {
if ($method === 'GET') {
return "The following permissions are required: 'access taxonomy overview' OR 'administer taxonomy'.";
}
return parent::getExpectedUnauthorizedAccessMessage($method);
}
}

View File

@ -0,0 +1,33 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\taxonomy\Functional\Rest;
use Drupal\Tests\rest\Functional\AnonResourceTestTrait;
use Drupal\Tests\rest\Functional\EntityResource\XmlEntityNormalizationQuirksTrait;
/**
* @group rest
*/
class VocabularyXmlAnonTest extends VocabularyResourceTestBase {
use AnonResourceTestTrait;
use XmlEntityNormalizationQuirksTrait;
/**
* {@inheritdoc}
*/
protected static $format = 'xml';
/**
* {@inheritdoc}
*/
protected static $mimeType = 'text/xml; charset=UTF-8';
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
}

View File

@ -0,0 +1,43 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\taxonomy\Functional\Rest;
use Drupal\Tests\rest\Functional\BasicAuthResourceTestTrait;
use Drupal\Tests\rest\Functional\EntityResource\XmlEntityNormalizationQuirksTrait;
/**
* @group rest
*/
class VocabularyXmlBasicAuthTest extends VocabularyResourceTestBase {
use BasicAuthResourceTestTrait;
use XmlEntityNormalizationQuirksTrait;
/**
* {@inheritdoc}
*/
protected static $modules = ['basic_auth'];
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* {@inheritdoc}
*/
protected static $format = 'xml';
/**
* {@inheritdoc}
*/
protected static $mimeType = 'text/xml; charset=UTF-8';
/**
* {@inheritdoc}
*/
protected static $auth = 'basic_auth';
}

View File

@ -0,0 +1,38 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\taxonomy\Functional\Rest;
use Drupal\Tests\rest\Functional\CookieResourceTestTrait;
use Drupal\Tests\rest\Functional\EntityResource\XmlEntityNormalizationQuirksTrait;
/**
* @group rest
*/
class VocabularyXmlCookieTest extends VocabularyResourceTestBase {
use CookieResourceTestTrait;
use XmlEntityNormalizationQuirksTrait;
/**
* {@inheritdoc}
*/
protected static $format = 'xml';
/**
* {@inheritdoc}
*/
protected static $mimeType = 'text/xml; charset=UTF-8';
/**
* {@inheritdoc}
*/
protected static $auth = 'cookie';
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
}

View File

@ -0,0 +1,146 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\taxonomy\Functional;
use Drupal\Core\Field\FieldStorageDefinitionInterface;
use Drupal\views\Views;
/**
* Tests the taxonomy RSS display.
*
* @group taxonomy
*/
class RssTest extends TaxonomyTestBase {
/**
* {@inheritdoc}
*/
protected static $modules = ['node', 'field_ui', 'views'];
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* Vocabulary for testing.
*
* @var \Drupal\taxonomy\VocabularyInterface
*/
protected $vocabulary;
/**
* Name of the taxonomy term reference field.
*
* @var string
*/
protected $fieldName;
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
$this->drupalLogin($this->drupalCreateUser([
'administer taxonomy',
'bypass node access',
'administer content types',
'administer node display',
]));
$this->vocabulary = $this->createVocabulary();
$this->fieldName = 'taxonomy_' . $this->vocabulary->id();
$handler_settings = [
'target_bundles' => [
$this->vocabulary->id() => $this->vocabulary->id(),
],
'auto_create' => TRUE,
];
$this->createEntityReferenceField('node', 'article', $this->fieldName, '', 'taxonomy_term', 'default', $handler_settings, FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED);
/** @var \Drupal\Core\Entity\EntityDisplayRepositoryInterface $display_repository */
$display_repository = \Drupal::service('entity_display.repository');
$display_repository->getFormDisplay('node', 'article')
->setComponent($this->fieldName, [
'type' => 'options_select',
])
->save();
$display_repository->getViewDisplay('node', 'article')
->setComponent($this->fieldName, [
'type' => 'entity_reference_label',
])
->save();
}
/**
* Tests that terms added to nodes are displayed in core RSS feed.
*
* Create a node and assert that taxonomy terms appear in rss.xml.
*/
public function testTaxonomyRss(): void {
// Create two taxonomy terms.
$term1 = $this->createTerm($this->vocabulary);
// Add the RSS display.
$default_display = $this->container->get('entity_display.repository')->getViewDisplay('node', 'article');
$rss_display = $default_display->createCopy('rss');
$rss_display->save();
// Change the format to 'RSS category'.
$rss_display->setComponent('taxonomy_' . $this->vocabulary->id(), [
'type' => 'entity_reference_rss_category',
'region' => 'content',
]);
$rss_display->save();
// Create an article.
$node = $this->drupalCreateNode([
'type' => 'article',
$this->fieldName => $term1->id(),
]);
// Check that the term is displayed when the RSS feed is viewed.
$this->drupalGet('rss.xml');
$test_element = sprintf(
'<category %s>%s</category>',
'domain="' . $term1->toUrl('canonical', ['absolute' => TRUE])->toString() . '"',
$term1->getName()
);
$this->assertSession()->responseContains($test_element);
// Test that the feed icon exists for the term.
$this->drupalGet("taxonomy/term/{$term1->id()}");
$this->assertSession()->linkByHrefExists("taxonomy/term/{$term1->id()}/feed");
// Test that the feed page exists for the term.
$this->drupalGet("taxonomy/term/{$term1->id()}/feed");
$assert = $this->assertSession();
$assert->responseHeaderContains('Content-Type', 'application/rss+xml');
// Ensure the RSS version is 2.0.
$rss_array = $this->getSession()->getDriver()->find('rss');
$this->assertEquals('2.0', reset($rss_array)->getAttribute('version'));
// Check that the "Exception value" is disabled by default.
$this->drupalGet('taxonomy/term/all/feed');
$this->assertSession()->statusCodeEquals(404);
// Set the exception value to 'all'.
$view = Views::getView('taxonomy_term');
$arguments = $view->getDisplay()->getOption('arguments');
$arguments['tid']['exception']['value'] = 'all';
$view->getDisplay()->overrideOption('arguments', $arguments);
$view->storage->save();
// Check the article is shown in the feed.
$raw_xml = '<title>' . $node->label() . '</title>';
$this->drupalGet('taxonomy/term/all/feed');
$this->assertSession()->responseContains($raw_xml);
// Unpublish the article and check that it is not shown in the feed.
$node->setUnpublished()->save();
$this->drupalGet('taxonomy/term/all/feed');
$this->assertSession()->responseNotContains($raw_xml);
}
}

View File

@ -0,0 +1,96 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\taxonomy\Functional;
use Drupal\Tests\TestFileCreationTrait;
use Drupal\field\Entity\FieldConfig;
use Drupal\field\Entity\FieldStorageConfig;
use Drupal\taxonomy\VocabularyInterface;
/**
* Tests image upload on taxonomy terms.
*
* @group taxonomy
*/
class TaxonomyImageTest extends TaxonomyTestBase {
use TestFileCreationTrait {
getTestFiles as drupalGetTestFiles;
compareFiles as drupalCompareFiles;
}
/**
* The taxonomy vocabulary used for the test.
*
* @var \Drupal\taxonomy\VocabularyInterface
*/
protected VocabularyInterface $vocabulary;
/**
* {@inheritdoc}
*/
protected static $modules = ['image'];
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
$this->vocabulary = $this->createVocabulary();
$entity_type = 'taxonomy_term';
$name = 'field_test';
FieldStorageConfig::create([
'field_name' => $name,
'entity_type' => $entity_type,
'type' => 'image',
])->save();
FieldConfig::create([
'field_name' => $name,
'entity_type' => $entity_type,
'bundle' => $this->vocabulary->id(),
'settings' => [],
])->save();
/** @var \Drupal\Core\Entity\EntityDisplayRepositoryInterface $display_repository */
$display_repository = \Drupal::service('entity_display.repository');
$display_repository->getFormDisplay($entity_type, $this->vocabulary->id())
->setComponent($name, [
'type' => 'image_image',
'settings' => [],
])
->save();
}
/**
* Tests that a file can be uploaded before the taxonomy term has a name.
*/
public function testTaxonomyImageUpload(): void {
$user = $this->drupalCreateUser(['administer taxonomy']);
$this->drupalLogin($user);
$files = $this->drupalGetTestFiles('image');
$image = array_pop($files);
// Ensure that a file can be uploaded before taxonomy term has a name.
$edit = [
'files[field_test_0]' => \Drupal::service('file_system')->realpath($image->uri),
];
$this->drupalGet('admin/structure/taxonomy/manage/' . $this->vocabulary->id() . '/add');
$this->submitForm($edit, 'Upload');
$edit = [
'name[0][value]' => $this->randomMachineName(),
'field_test[0][alt]' => $this->randomMachineName(),
];
$this->submitForm($edit, 'Save');
$this->assertSession()->pageTextContains('Created new term');
}
}

View File

@ -0,0 +1,112 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\taxonomy\Functional;
use Drupal\Tests\BrowserTestBase;
use Drupal\taxonomy\Entity\Term;
use Drupal\Tests\taxonomy\Traits\TaxonomyTestTrait;
/**
* Taxonomy term revision delete form test.
*
* @group taxonomy
* @coversDefaultClass \Drupal\Core\Entity\Form\RevisionDeleteForm
*/
class TaxonomyRevisionDeleteTest extends BrowserTestBase {
use TaxonomyTestTrait;
/**
* {@inheritdoc}
*/
protected static $modules = [
'system',
'taxonomy',
];
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* {@inheritdoc}
*/
protected $permissions = [
'view term revisions in test',
'delete all taxonomy revisions',
];
/**
* Vocabulary for testing.
*
* @var \Drupal\taxonomy\VocabularyInterface
*/
private $vocabulary;
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
$this->vocabulary = $this->createVocabulary(['vid' => 'test', 'name' => 'Test']);
}
/**
* Tests revision delete.
*/
public function testDeleteForm(): void {
$termName = $this->randomMachineName();
$entity = Term::create([
'vid' => $this->vocabulary->id(),
'name' => $termName,
]);
$entity->setRevisionCreationTime((new \DateTimeImmutable('11 January 2009 4pm'))->getTimestamp())
->setRevisionTranslationAffected(TRUE);
$entity->setNewRevision();
$entity->save();
$revisionId = $entity->getRevisionId();
$this->drupalLogin($this->drupalCreateUser($this->permissions));
// Cannot delete latest revision.
$this->drupalGet($entity->toUrl('revision-delete-form'));
$this->assertSession()->statusCodeEquals(403);
// Create a new latest revision.
$entity
->setRevisionCreationTime((new \DateTimeImmutable('11 January 2009 5pm'))->getTimestamp())
->setRevisionTranslationAffected(TRUE)
->setNewRevision();
$entity->save();
// Reload the entity.
$revision = \Drupal::entityTypeManager()->getStorage('taxonomy_term')
->loadRevision($revisionId);
$this->drupalGet($revision->toUrl('revision-delete-form'));
$this->assertSession()->pageTextContains('Are you sure you want to delete the revision from Sun, 11 Jan 2009 - 16:00?');
$this->assertSession()->buttonExists('Delete');
$this->assertSession()->linkExists('Cancel');
$countRevisions = static function (): int {
return (int) \Drupal::entityTypeManager()->getStorage('taxonomy_term')
->getQuery()
->accessCheck(FALSE)
->allRevisions()
->count()
->execute();
};
$count = $countRevisions();
$this->submitForm([], 'Delete');
$this->assertEquals($count - 1, $countRevisions());
$this->assertSession()->statusCodeEquals(200);
$this->assertSession()->addressEquals(sprintf('taxonomy/term/%s/revisions', $entity->id()));
$this->assertSession()->pageTextContains(sprintf('Revision from Sun, 11 Jan 2009 - 16:00 of Test %s has been deleted.', $termName));
$this->assertSession()->elementsCount('css', 'table tbody tr', 1);
}
}

View File

@ -0,0 +1,112 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\taxonomy\Functional;
use Drupal\Tests\BrowserTestBase;
use Drupal\taxonomy\Entity\Term;
use Drupal\Tests\taxonomy\Traits\TaxonomyTestTrait;
/**
* Taxonomy term revision form test.
*
* @group taxonomy
* @coversDefaultClass \Drupal\Core\Entity\Form\RevisionRevertForm
*/
class TaxonomyRevisionRevertTest extends BrowserTestBase {
use TaxonomyTestTrait;
/**
* {@inheritdoc}
*/
protected static $modules = [
'system',
'taxonomy',
];
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* {@inheritdoc}
*/
protected $permissions = [
'view term revisions in test',
'revert all taxonomy revisions',
];
/**
* Vocabulary for testing.
*
* @var \Drupal\taxonomy\VocabularyInterface
*/
private $vocabulary;
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
$this->vocabulary = $this->createVocabulary(['vid' => 'test', 'name' => 'Test']);
}
/**
* Tests revision revert.
*/
public function testRevertForm(): void {
$termName = $this->randomMachineName();
$entity = Term::create([
'vid' => $this->vocabulary->id(),
'name' => $termName,
]);
$entity->setRevisionCreationTime((new \DateTimeImmutable('11 January 2009 4pm'))->getTimestamp())
->setRevisionTranslationAffected(TRUE);
$entity->setNewRevision();
$entity->save();
$revisionId = $entity->getRevisionId();
$this->drupalLogin($this->drupalCreateUser($this->permissions));
// Cannot revert latest revision.
$this->drupalGet($entity->toUrl('revision-revert-form'));
$this->assertSession()->statusCodeEquals(403);
// Create a new latest revision.
$entity
->setRevisionCreationTime((new \DateTimeImmutable('11 January 2009 5pm'))->getTimestamp())
->setRevisionTranslationAffected(TRUE)
->setNewRevision();
$entity->save();
// Reload the entity.
$revision = \Drupal::entityTypeManager()->getStorage('taxonomy_term')
->loadRevision($revisionId);
$this->drupalGet($revision->toUrl('revision-revert-form'));
$this->assertSession()->pageTextContains('Are you sure you want to revert to the revision from Sun, 11 Jan 2009 - 16:00?');
$this->assertSession()->buttonExists('Revert');
$this->assertSession()->linkExists('Cancel');
$countRevisions = static function (): int {
return (int) \Drupal::entityTypeManager()->getStorage('taxonomy_term')
->getQuery()
->accessCheck(FALSE)
->allRevisions()
->count()
->execute();
};
$count = $countRevisions();
$this->submitForm([], 'Revert');
$this->assertEquals($count + 1, $countRevisions());
$this->assertSession()->statusCodeEquals(200);
$this->assertSession()->addressEquals(sprintf('taxonomy/term/%s/revisions', $entity->id()));
$this->assertSession()->pageTextContains(sprintf('Test %s has been reverted to the revision from Sun, 11 Jan 2009 - 16:00.', $termName));
$this->assertSession()->elementsCount('css', 'table tbody tr', 3);
}
}

View File

@ -0,0 +1,58 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\taxonomy\Functional;
use Drupal\Tests\BrowserTestBase;
use Drupal\Tests\taxonomy\Traits\TaxonomyTestTrait;
/**
* Tests the new_revision setting of taxonomy vocabularies.
*
* @group taxonomy
*/
class TaxonomyRevisionTest extends BrowserTestBase {
use TaxonomyTestTrait;
/**
* {@inheritdoc}
*/
protected static $modules = [
'system',
'taxonomy',
];
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* Tests default revision settings on vocabularies.
*/
public function testVocabularyTermRevision(): void {
$assert = $this->assertSession();
$vocabulary1 = $this->createVocabulary(['new_revision' => TRUE]);
$vocabulary2 = $this->createVocabulary(['new_revision' => FALSE]);
$user = $this->createUser([
'administer taxonomy',
]);
$term1 = $this->createTerm($vocabulary1);
$term2 = $this->createTerm($vocabulary2);
// Create some revisions so revision checkbox is visible.
$term1 = $this->createTaxonomyTermRevision($term1);
$term2 = $this->createTaxonomyTermRevision($term2);
$this->drupalLogin($user);
$this->drupalGet($term1->toUrl('edit-form'));
$assert->statusCodeEquals(200);
$assert->checkboxChecked('Create new revision');
$this->drupalGet($term2->toUrl('edit-form'));
$assert->statusCodeEquals(200);
$assert->checkboxNotChecked('Create new revision');
}
}

View File

@ -0,0 +1,119 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\taxonomy\Functional;
use Drupal\Tests\BrowserTestBase;
use Drupal\taxonomy\Entity\Term;
use Drupal\Tests\taxonomy\Traits\TaxonomyTestTrait;
/**
* Taxonomy term version history test.
*
* @group taxonomy
* @coversDefaultClass \Drupal\Core\Entity\Controller\VersionHistoryController
*/
class TaxonomyRevisionVersionHistoryTest extends BrowserTestBase {
use TaxonomyTestTrait;
/**
* {@inheritdoc}
*/
protected static $modules = [
'system',
'taxonomy',
];
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* {@inheritdoc}
*/
protected $permissions = [
'view term revisions in test',
'revert all taxonomy revisions',
'delete all taxonomy revisions',
];
/**
* Vocabulary for testing.
*
* @var \Drupal\taxonomy\VocabularyInterface
*/
private $vocabulary;
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
$this->vocabulary = $this->createVocabulary(['vid' => 'test', 'name' => 'Test']);
}
/**
* Tests version history page.
*/
public function testVersionHistory(): void {
$entity = Term::create([
'vid' => $this->vocabulary->id(),
'name' => 'Test taxonomy term',
]);
$entity
->setDescription('Description 1')
->setRevisionCreationTime((new \DateTimeImmutable('1st June 2020 7am'))->getTimestamp())
->setRevisionLogMessage('first revision log')
->setRevisionUser($this->drupalCreateUser(name: 'first author'))
->setNewRevision();
$entity->save();
$entity
->setDescription('Description 2')
->setRevisionCreationTime((new \DateTimeImmutable('2nd June 2020 8am'))->getTimestamp())
->setRevisionLogMessage('second revision log')
->setRevisionUser($this->drupalCreateUser(name: 'second author'))
->setNewRevision();
$entity->save();
$entity
->setDescription('Description 3')
->setRevisionCreationTime((new \DateTimeImmutable('3rd June 2020 9am'))->getTimestamp())
->setRevisionLogMessage('third revision log')
->setRevisionUser($this->drupalCreateUser(name: 'third author'))
->setNewRevision();
$entity->save();
$this->drupalLogin($this->drupalCreateUser($this->permissions));
$this->drupalGet($entity->toUrl('version-history'));
$this->assertSession()->elementsCount('css', 'table tbody tr', 3);
// Order is newest to oldest revision by creation order.
$row1 = $this->assertSession()->elementExists('css', 'table tbody tr:nth-child(1)');
// Latest revision does not have revert or delete revision operation.
$this->assertSession()->elementNotExists('named', ['link', 'Revert'], $row1);
$this->assertSession()->elementNotExists('named', ['link', 'Delete'], $row1);
$this->assertSession()->elementTextContains('css', 'table tbody tr:nth-child(1)', 'Current revision');
$this->assertSession()->elementTextContains('css', 'table tbody tr:nth-child(1)', 'third revision log');
$this->assertSession()->elementTextContains('css', 'table tbody tr:nth-child(1)', '3 Jun 2020 - 09:00 by third author');
$row2 = $this->assertSession()->elementExists('css', 'table tbody tr:nth-child(2)');
$this->assertSession()->elementExists('named', ['link', 'Revert'], $row2);
$this->assertSession()->elementExists('named', ['link', 'Delete'], $row2);
$this->assertSession()->elementTextNotContains('css', 'table tbody tr:nth-child(2)', 'Current revision');
$this->assertSession()->elementTextContains('css', 'table tbody tr:nth-child(2)', 'second revision log');
$this->assertSession()->elementTextContains('css', 'table tbody tr:nth-child(2)', '2 Jun 2020 - 08:00 by second author');
$row3 = $this->assertSession()->elementExists('css', 'table tbody tr:nth-child(3)');
$this->assertSession()->elementExists('named', ['link', 'Revert'], $row3);
$this->assertSession()->elementExists('named', ['link', 'Delete'], $row3);
$this->assertSession()->elementTextNotContains('css', 'table tbody tr:nth-child(2)', 'Current revision');
$this->assertSession()->elementTextContains('css', 'table tbody tr:nth-child(3)', 'first revision log');
$this->assertSession()->elementTextContains('css', 'table tbody tr:nth-child(3)', '1 Jun 2020 - 07:00 by first author');
}
}

View File

@ -0,0 +1,217 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\taxonomy\Functional;
use Drupal\Core\Url;
use Drupal\Tests\content_moderation\Traits\ContentModerationTestTrait;
use Drupal\workflows\Entity\Workflow;
/**
* Tests taxonomy terms with Content Moderation.
*
* @group content_moderation
* @group taxonomy
*/
class TaxonomyTermContentModerationTest extends TaxonomyTestBase {
use ContentModerationTestTrait;
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* The vocabulary used for creating terms.
*
* @var \Drupal\taxonomy\VocabularyInterface
*/
protected $vocabulary;
/**
* {@inheritdoc}
*/
protected static $modules = ['content_moderation'];
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
$this->createEditorialWorkflow();
$this->drupalLogin($this->drupalCreateUser([
'administer taxonomy',
'use editorial transition create_new_draft',
'use editorial transition publish',
'view any unpublished content',
'view latest version',
]));
$this->vocabulary = $this->createVocabulary();
// Set the vocabulary as moderated.
$workflow = Workflow::load('editorial');
$workflow->getTypePlugin()->addEntityTypeAndBundle('taxonomy_term', $this->vocabulary->id());
$workflow->save();
}
/**
* Tests taxonomy term parents on a moderated vocabulary.
*/
public function testTaxonomyTermParents(): void {
$assert_session = $this->assertSession();
// Create a simple hierarchy in the vocabulary, a root term and three parent
// terms.
$root = $this->createTerm($this->vocabulary, ['langcode' => 'en', 'moderation_state' => 'published']);
$parent_1 = $this->createTerm($this->vocabulary, [
'langcode' => 'en',
'moderation_state' => 'published',
'parent' => $root->id(),
]);
$parent_2 = $this->createTerm($this->vocabulary, [
'langcode' => 'en',
'moderation_state' => 'published',
'parent' => $root->id(),
]);
$parent_3 = $this->createTerm($this->vocabulary, [
'langcode' => 'en',
'moderation_state' => 'published',
'parent' => $root->id(),
]);
// Create a child term and assign one of the parents above.
$child = $this->createTerm($this->vocabulary, [
'langcode' => 'en',
'moderation_state' => 'published',
'parent' => $parent_1->id(),
]);
/** @var \Drupal\taxonomy\TermStorageInterface $taxonomy_storage */
$taxonomy_storage = \Drupal::entityTypeManager()->getStorage('taxonomy_term');
$validation_message = 'You can only change the hierarchy for the published version of this term.';
// Add a pending revision without changing the term parent.
$this->drupalGet($child->toUrl('edit-form'));
$this->submitForm(['moderation_state[0][state]' => 'draft'], 'Save');
$assert_session->pageTextNotContains($validation_message);
// Add a pending revision and change the parent.
$this->drupalGet($child->toUrl('edit-form'));
$this->submitForm(['parent[]' => [$parent_2->id()], 'moderation_state[0][state]' => 'draft'], 'Save');
// Check that parents were not changed.
$assert_session->pageTextContains($validation_message);
$taxonomy_storage->resetCache();
$this->assertEquals([$parent_1->id()], array_keys($taxonomy_storage->loadParents($child->id())));
// Add a pending revision and add a new parent.
$this->drupalGet($child->toUrl('edit-form'));
$this->submitForm(['parent[]' => [$parent_1->id(), $parent_3->id()], 'moderation_state[0][state]' => 'draft'], 'Save');
// Check that parents were not changed.
$assert_session->pageTextContains($validation_message);
$taxonomy_storage->resetCache();
$this->assertEquals([$parent_1->id()], array_keys($taxonomy_storage->loadParents($child->id())));
// Add a pending revision and use the root term as a parent.
$this->drupalGet($child->toUrl('edit-form'));
$this->submitForm(['parent[]' => [$root->id()], 'moderation_state[0][state]' => 'draft'], 'Save');
// Check that parents were not changed.
$assert_session->pageTextContains($validation_message);
$taxonomy_storage->resetCache();
$this->assertEquals([$parent_1->id()], array_keys($taxonomy_storage->loadParents($child->id())));
// Add a pending revision and remove the parent.
$this->drupalGet($child->toUrl('edit-form'));
$this->submitForm(['parent[]' => [], 'moderation_state[0][state]' => 'draft'], 'Save');
// Check that parents were not changed.
$assert_session->pageTextContains($validation_message);
$taxonomy_storage->resetCache();
$this->assertEquals([$parent_1->id()], array_keys($taxonomy_storage->loadParents($child->id())));
// Add a published revision.
$this->drupalGet($child->toUrl('edit-form'));
$this->submitForm(['moderation_state[0][state]' => 'published'], 'Save');
// Change the parents.
$this->drupalGet($child->toUrl('edit-form'));
$this->submitForm(['parent[]' => [$parent_2->id()]], 'Save');
// Check that parents were changed.
$assert_session->pageTextNotContains($validation_message);
$taxonomy_storage->resetCache();
$this->assertNotEquals([$parent_1->id()], array_keys($taxonomy_storage->loadParents($child->id())));
// Add a pending revision and change the weight.
$this->drupalGet($child->toUrl('edit-form'));
$this->submitForm(['weight' => 10, 'moderation_state[0][state]' => 'draft'], 'Save');
// Check that weight was not changed.
$assert_session->pageTextContains($validation_message);
// Add a new term without any parent and publish it.
$edit = [
'name[0][value]' => $this->randomMachineName(),
'moderation_state[0][state]' => 'published',
];
$this->drupalGet(Url::fromRoute('entity.taxonomy_term.add_form', ['taxonomy_vocabulary' => $this->vocabulary->id()]));
$this->submitForm($edit, 'Save');
// Add a pending revision without any changes.
$terms = \Drupal::entityTypeManager()->getStorage('taxonomy_term')->loadByProperties(['name' => $edit['name[0][value]']]);
$term = reset($terms);
$this->drupalGet($term->toUrl('edit-form'));
$this->submitForm(['moderation_state[0][state]' => 'draft'], 'Save');
$assert_session->pageTextNotContains($validation_message);
}
/**
* Tests changing field values in pending revisions of taxonomy terms.
*/
public function testTaxonomyTermPendingRevisions(): void {
$assert_session = $this->assertSession();
$default_term_name = 'term - default revision';
$default_term_description = 'The default revision of a term.';
$term = $this->createTerm($this->vocabulary, [
'name' => $default_term_name,
'description' => $default_term_description,
'langcode' => 'en',
'moderation_state' => 'published',
]);
// Add a pending revision without changing the term parent.
$this->drupalGet($term->toUrl('edit-form'));
$assert_session->pageTextContains($default_term_name);
$assert_session->pageTextContains($default_term_description);
// Check the revision log message field appears on the term edit page.
$this->drupalGet($term->toUrl('edit-form'));
$assert_session->fieldExists('revision_log_message[0][value]');
$pending_term_name = 'term - pending revision';
$pending_term_description = 'The pending revision of a term.';
$edit = [
'name[0][value]' => $pending_term_name,
'description[0][value]' => $pending_term_description,
'moderation_state[0][state]' => 'draft',
];
$this->drupalGet($term->toUrl('edit-form'));
$this->submitForm($edit, 'Save');
$assert_session->pageTextContains($pending_term_name);
$assert_session->pageTextContains($pending_term_description);
$assert_session->pageTextNotContains($default_term_description);
// Check that the default revision of the term contains the correct values.
$this->drupalGet('taxonomy/term/' . $term->id());
$assert_session->pageTextContains($default_term_name);
$assert_session->pageTextContains($default_term_description);
}
}

View File

@ -0,0 +1,106 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\taxonomy\Functional;
/**
* Ensure that the term indentation works properly.
*
* @group taxonomy
*/
class TaxonomyTermIndentationTest extends TaxonomyTestBase {
/**
* {@inheritdoc}
*/
protected static $modules = ['taxonomy'];
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* Vocabulary for testing.
*
* @var \Drupal\taxonomy\VocabularyInterface
*/
protected $vocabulary;
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
$this->drupalLogin($this->drupalCreateUser([
'administer taxonomy',
'bypass node access',
]));
$this->vocabulary = $this->createVocabulary();
}
/**
* Tests term indentation.
*/
public function testTermIndentation(): void {
$assert = $this->assertSession();
// Create three taxonomy terms.
$this->createTerm($this->vocabulary);
$term2 = $this->createTerm($this->vocabulary);
$this->createTerm($this->vocabulary);
// Get the taxonomy storage.
$taxonomy_storage = $this->container->get('entity_type.manager')->getStorage('taxonomy_term');
// Indent the second term under the first one.
$this->drupalGet('admin/structure/taxonomy/manage/' . $this->vocabulary->get('vid') . '/overview');
$hidden_edit = [
'terms[tid:' . $term2->id() . ':0][term][tid]' => 2,
'terms[tid:' . $term2->id() . ':0][term][parent]' => 1,
'terms[tid:' . $term2->id() . ':0][term][depth]' => 1,
];
// Because we can't post hidden form elements, we have to change them in
// code here, and then submit.
foreach ($hidden_edit as $field => $value) {
$node = $assert->hiddenFieldExists($field);
$node->setValue($value);
}
$edit = [
'terms[tid:' . $term2->id() . ':0][weight]' => 1,
];
// Submit the edited form and check for HTML indentation element presence.
$this->submitForm($edit, 'Save');
$this->assertSession()->responseMatches('|<div class="js-indentation indentation">&nbsp;</div>|');
// Check explicitly that term 2's parent is term 1.
$parents = $taxonomy_storage->loadParents($term2->id());
$this->assertEquals(1, key($parents), 'Term 1 is the term 2\'s parent');
// Move the second term back out to the root level.
$this->drupalGet('admin/structure/taxonomy/manage/' . $this->vocabulary->get('vid') . '/overview');
$hidden_edit = [
'terms[tid:' . $term2->id() . ':0][term][tid]' => 2,
'terms[tid:' . $term2->id() . ':0][term][parent]' => 0,
'terms[tid:' . $term2->id() . ':0][term][depth]' => 0,
];
// Because we can't post hidden form elements, we have to change them in
// code here, and then submit.
foreach ($hidden_edit as $field => $value) {
$node = $assert->hiddenFieldExists($field);
$node->setValue($value);
}
$edit = [
'terms[tid:' . $term2->id() . ':0][weight]' => 1,
];
$this->submitForm($edit, 'Save');
// All terms back at the root level, no indentation should be present.
$this->assertSession()->responseNotMatches('|<div class="js-indentation indentation">&nbsp;</div>|');
// Check explicitly that term 2 has no parents.
\Drupal::entityTypeManager()->getStorage('taxonomy_term')->resetCache();
$parents = $taxonomy_storage->loadParents($term2->id());
$this->assertEmpty($parents, 'Term 2 has no parents now');
}
}

View File

@ -0,0 +1,120 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\taxonomy\Functional;
/**
* Ensures that the term pager works properly.
*
* @group taxonomy
*/
class TaxonomyTermPagerTest extends TaxonomyTestBase {
/**
* {@inheritdoc}
*/
protected static $modules = ['taxonomy', 'taxonomy_test'];
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* Vocabulary for testing.
*
* @var \Drupal\taxonomy\VocabularyInterface
*/
protected $vocabulary;
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
$this->drupalLogin($this->drupalCreateUser([
'administer taxonomy',
'bypass node access',
]));
$this->vocabulary = $this->createVocabulary();
}
/**
* Tests that the pager is displayed properly on the term overview page.
*/
public function testTaxonomyTermOverviewPager(): void {
// Set limit to 3 terms per page.
$this->config('taxonomy.settings')
->set('terms_per_page_admin', '3')
->save();
// Create 3 terms.
for ($x = 1; $x <= 3; $x++) {
$this->createTerm($this->vocabulary);
}
// Get Page 1.
$this->drupalGet('admin/structure/taxonomy/manage/' . $this->vocabulary->id() . '/overview');
// Pager should not be visible.
$this->assertSession()->responseNotMatches('|<nav class="pager" [^>]*>|');
// Create 3 more terms to show pager.
for ($x = 1; $x <= 3; $x++) {
$this->createTerm($this->vocabulary);
}
// Ensure that pager is visible on page 1.
$this->drupalGet('admin/structure/taxonomy/manage/' . $this->vocabulary->id() . '/overview');
$this->assertSession()->responseMatches('|<nav class="pager" [^>]*>|');
// Ensure that pager is visible on page 2.
$this->drupalGet('admin/structure/taxonomy/manage/' . $this->vocabulary->id() . '/overview', ['query' => ['page' => 1]]);
$this->assertSession()->responseMatches('|<nav class="pager" [^>]*>|');
}
/**
* Tests that overview page only loads the necessary terms.
*/
public function testTaxonomyTermOverviewTermLoad(): void {
// Set limit to 3 terms per page.
$this->config('taxonomy.settings')
->set('terms_per_page_admin', '3')
->save();
$state = $this->container->get('state');
// Create 5 terms.
for ($x = 0; $x <= 10; $x++) {
$this->createTerm($this->vocabulary, ['weight' => $x]);
}
// Check the overview page.
$state->set('taxonomy_test_taxonomy_term_load', []);
$this->drupalGet('admin/structure/taxonomy/manage/' . $this->vocabulary->id() . '/overview');
$loaded_terms = $state->get('taxonomy_test_taxonomy_term_load');
$this->assertCount(4, $loaded_terms);
// Check the overview page for submit callback.
$state->set('taxonomy_test_taxonomy_term_load', []);
$this->submitForm([], 'Save');
$loaded_terms = $state->get('taxonomy_test_taxonomy_term_load');
$this->assertCount(4, $loaded_terms);
$this->drupalGet('admin/structure/taxonomy/manage/' . $this->vocabulary->id() . '/overview', ['query' => ['page' => 2]]);
$state->set('taxonomy_test_taxonomy_term_load', []);
$this->submitForm([], 'Save');
$loaded_terms = $state->get('taxonomy_test_taxonomy_term_load');
$this->assertCount(4, $loaded_terms);
// Adding a new term with weight < 0 implies that all root terms are
// updated.
$this->createTerm($this->vocabulary, ['weight' => -1]);
$this->drupalGet('admin/structure/taxonomy/manage/' . $this->vocabulary->id() . '/overview', ['query' => ['page' => 2]]);
$state->set('taxonomy_test_taxonomy_term_load', []);
$this->submitForm([], 'Save');
$loaded_terms = $state->get('taxonomy_test_taxonomy_term_load');
$this->assertCount(12, $loaded_terms);
}
}

View File

@ -0,0 +1,37 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\taxonomy\Functional;
use Drupal\Tests\BrowserTestBase;
use Drupal\Tests\field\Traits\EntityReferenceFieldCreationTrait;
use Drupal\Tests\taxonomy\Traits\TaxonomyTestTrait;
/**
* Provides common helper methods for Taxonomy module tests.
*/
abstract class TaxonomyTestBase extends BrowserTestBase {
use TaxonomyTestTrait;
use EntityReferenceFieldCreationTrait;
/**
* {@inheritdoc}
*/
protected static $modules = ['taxonomy', 'block'];
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
$this->drupalPlaceBlock('system_breadcrumb_block');
// Create Basic page and Article node types.
if ($this->profile != 'standard') {
$this->drupalCreateContentType(['type' => 'article', 'name' => 'Article']);
}
}
}

View File

@ -0,0 +1,102 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\taxonomy\Functional;
use Drupal\Core\Field\FieldStorageDefinitionInterface;
use Drupal\field\Entity\FieldStorageConfig;
use Drupal\language\Entity\ConfigurableLanguage;
use Drupal\Tests\field\Traits\EntityReferenceFieldCreationTrait;
/**
* Provides common testing base for translated taxonomy terms.
*/
trait TaxonomyTranslationTestTrait {
use EntityReferenceFieldCreationTrait;
/**
* The vocabulary.
*
* @var \Drupal\taxonomy\Entity\Vocabulary
*/
protected $vocabulary;
/**
* The field name for our taxonomy term field.
*
* @var string
*/
protected $termFieldName = 'field_tag';
/**
* The langcode of the source language.
*
* @var string
*/
protected $baseLangcode = 'en';
/**
* Target langcode for translation.
*
* @var string
*/
protected $translateToLangcode = 'hu';
/**
* The node to check the translated value on.
*
* @var \Drupal\node\Entity\Node
*/
protected $node;
/**
* Adds additional languages.
*/
protected function setupLanguages() {
ConfigurableLanguage::createFromLangcode($this->translateToLangcode)->save();
$this->rebuildContainer();
}
/**
* Enables translations where it needed.
*/
protected function enableTranslation() {
// Enable translation for the current entity type and ensure the change is
// picked up.
\Drupal::service('content_translation.manager')->setEnabled('node', 'article', TRUE);
\Drupal::service('content_translation.manager')->setEnabled('taxonomy_term', $this->vocabulary->id(), TRUE);
}
/**
* Adds term reference field for the article content type.
*/
protected function setUpTermReferenceField() {
$handler_settings = [
'target_bundles' => [
$this->vocabulary->id() => $this->vocabulary->id(),
],
'auto_create' => TRUE,
];
$this->createEntityReferenceField('node', 'article', $this->termFieldName, '', 'taxonomy_term', 'default', $handler_settings, FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED);
$field_storage = FieldStorageConfig::loadByName('node', $this->termFieldName);
$field_storage->setTranslatable(FALSE);
$field_storage->save();
/** @var \Drupal\Core\Entity\EntityDisplayRepositoryInterface $display_repository */
$display_repository = \Drupal::service('entity_display.repository');
$display_repository->getFormDisplay('node', 'article')
->setComponent($this->termFieldName, [
'type' => 'entity_reference_autocomplete_tags',
])
->save();
$display_repository->getViewDisplay('node', 'article')
->setComponent($this->termFieldName, [
'type' => 'entity_reference_label',
])
->save();
}
}

View File

@ -0,0 +1,133 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\taxonomy\Functional;
use Drupal\taxonomy\Entity\Term;
use Drupal\taxonomy\TermInterface;
use Drupal\Tests\system\Functional\Cache\AssertPageCacheContextsAndTagsTrait;
/**
* Tests the taxonomy term access permissions.
*
* @group taxonomy
*/
class TermAccessTest extends TaxonomyTestBase {
use AssertPageCacheContextsAndTagsTrait;
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* Tests access control functionality for taxonomy terms.
*/
public function testTermAccess(): void {
$assert_session = $this->assertSession();
$vocabulary = $this->createVocabulary();
// Create two terms.
$published_term = Term::create([
'vid' => $vocabulary->id(),
'name' => 'Published term',
'status' => 1,
]);
$published_term->save();
$unpublished_term = Term::create([
'vid' => $vocabulary->id(),
'name' => 'Unpublished term',
'status' => 0,
]);
$unpublished_term->save();
// Start off logged in as admin.
$this->drupalLogin($this->drupalCreateUser(['administer taxonomy']));
// Test the 'administer taxonomy' permission.
$this->drupalGet('taxonomy/term/' . $published_term->id());
$assert_session->statusCodeEquals(200);
$this->assertTermAccess($published_term, 'view', TRUE);
$this->drupalGet('taxonomy/term/' . $unpublished_term->id());
$assert_session->statusCodeEquals(200);
$this->assertTermAccess($unpublished_term, 'view', TRUE);
$this->drupalGet('taxonomy/term/' . $published_term->id() . '/edit');
$assert_session->statusCodeEquals(200);
$this->assertTermAccess($published_term, 'update', TRUE);
$this->drupalGet('taxonomy/term/' . $unpublished_term->id() . '/edit');
$assert_session->statusCodeEquals(200);
$this->assertTermAccess($unpublished_term, 'update', TRUE);
$this->drupalGet('taxonomy/term/' . $published_term->id() . '/delete');
$assert_session->statusCodeEquals(200);
$this->assertTermAccess($published_term, 'delete', TRUE);
$this->drupalGet('taxonomy/term/' . $unpublished_term->id() . '/delete');
$assert_session->statusCodeEquals(200);
$this->assertTermAccess($unpublished_term, 'delete', TRUE);
// Test the 'access content' permission.
$this->drupalLogin($this->drupalCreateUser(['access content']));
$this->drupalGet('taxonomy/term/' . $published_term->id());
$assert_session->statusCodeEquals(200);
$this->assertTermAccess($published_term, 'view', TRUE);
$this->drupalGet('taxonomy/term/' . $unpublished_term->id());
$assert_session->statusCodeEquals(403);
$this->assertTermAccess($unpublished_term, 'view', FALSE, "The 'access content' permission is required and the taxonomy term must be published.");
$this->drupalGet('taxonomy/term/' . $published_term->id() . '/edit');
$assert_session->statusCodeEquals(403);
$this->assertTermAccess($published_term, 'update', FALSE, "The following permissions are required: 'edit terms in {$vocabulary->id()}' OR 'administer taxonomy'.");
$this->drupalGet('taxonomy/term/' . $unpublished_term->id() . '/edit');
$assert_session->statusCodeEquals(403);
$this->assertTermAccess($unpublished_term, 'update', FALSE, "The following permissions are required: 'edit terms in {$vocabulary->id()}' OR 'administer taxonomy'.");
$this->drupalGet('taxonomy/term/' . $published_term->id() . '/delete');
$assert_session->statusCodeEquals(403);
$this->assertTermAccess($published_term, 'delete', FALSE, "The following permissions are required: 'delete terms in {$vocabulary->id()}' OR 'administer taxonomy'.");
$this->drupalGet('taxonomy/term/' . $unpublished_term->id() . '/delete');
$assert_session->statusCodeEquals(403);
$this->assertTermAccess($unpublished_term, 'delete', FALSE, "The following permissions are required: 'delete terms in {$vocabulary->id()}' OR 'administer taxonomy'.");
// Install the Views module and repeat the checks for the 'view' permission.
\Drupal::service('module_installer')->install(['views'], TRUE);
$this->rebuildContainer();
$this->drupalGet('taxonomy/term/' . $published_term->id());
$assert_session->statusCodeEquals(200);
// @todo Change this assertion to expect a 403 status code when
// https://www.drupal.org/project/drupal/issues/2983070 is fixed.
$this->drupalGet('taxonomy/term/' . $unpublished_term->id());
$assert_session->statusCodeEquals(404);
}
/**
* Checks access on taxonomy term.
*
* @param \Drupal\taxonomy\TermInterface $term
* A taxonomy term entity.
* @param string $access_operation
* The entity operation, e.g. 'view', 'edit', 'delete', etc.
* @param bool $access_allowed
* Whether the current use has access to the given operation or not.
* @param string $access_reason
* (optional) The reason of the access result.
*
* @internal
*/
protected function assertTermAccess(TermInterface $term, string $access_operation, bool $access_allowed, string $access_reason = ''): void {
$access_result = $term->access($access_operation, NULL, TRUE);
$this->assertSame($access_allowed, $access_result->isAllowed());
if ($access_reason) {
$this->assertSame($access_reason, $access_result->getReason());
}
}
}

View File

@ -0,0 +1,234 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\taxonomy\Functional;
use Drupal\Component\Serialization\Json;
use Drupal\Core\Entity\Entity\EntityFormDisplay;
use Drupal\Core\Entity\Entity\EntityViewDisplay;
use Drupal\Core\Field\FieldStorageDefinitionInterface;
use Drupal\field\Entity\FieldConfig;
use Drupal\field\Entity\FieldStorageConfig;
/**
* Tests the autocomplete implementation of the taxonomy class.
*
* @group taxonomy
*/
class TermAutocompleteTest extends TaxonomyTestBase {
/**
* {@inheritdoc}
*/
protected static $modules = ['node'];
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* The vocabulary.
*
* @var \Drupal\taxonomy\Entity\Vocabulary
*/
protected $vocabulary;
/**
* The field to add to the content type for the taxonomy terms.
*
* @var string
*/
protected $fieldName;
/**
* The admin user.
*
* @var \Drupal\user\Entity\User
*/
protected $adminUser;
/**
* The autocomplete URL to call.
*
* @var string
*/
protected $autocompleteUrl;
/**
* The term IDs indexed by term names.
*
* @var array
*/
protected $termIds;
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
// Create a vocabulary.
$this->vocabulary = $this->createVocabulary();
// Create 11 terms, which have some sub-string in common, in a
// non-alphabetical order, so that we will have more than 10 matches later
// when we test the correct number of results is returned, and we can test
// the order of the results. The location of the sub-string to match varies
// also, since it should not be necessary to start with the sub-string to
// match it. Save term IDs to reuse later.
$termNames = [
'aaa 20 bbb',
'aaa 70 bbb',
'aaa 10 bbb',
'aaa 12 bbb',
'aaa 40 bbb',
'aaa 11 bbb',
'aaa 30 bbb',
'aaa 50 bbb',
'aaa 80',
'aaa 90',
'bbb 60 aaa',
];
foreach ($termNames as $termName) {
$term = $this->createTerm($this->vocabulary, ['name' => $termName]);
$this->termIds[$termName] = $term->id();
}
// Create a taxonomy_term_reference field on the article Content Type that
// uses a taxonomy_autocomplete widget.
$this->fieldName = $this->randomMachineName();
FieldStorageConfig::create([
'field_name' => $this->fieldName,
'entity_type' => 'node',
'type' => 'entity_reference',
'cardinality' => FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED,
'settings' => [
'target_type' => 'taxonomy_term',
],
])->save();
FieldConfig::create([
'field_name' => $this->fieldName,
'bundle' => 'article',
'entity_type' => 'node',
'settings' => [
'handler' => 'default',
'handler_settings' => [
// Restrict selection of terms to a single vocabulary.
'target_bundles' => [
$this->vocabulary->id() => $this->vocabulary->id(),
],
],
],
])->save();
EntityFormDisplay::load('node.article.default')
->setComponent($this->fieldName, [
'type' => 'entity_reference_autocomplete',
])
->save();
EntityViewDisplay::load('node.article.default')
->setComponent($this->fieldName, [
'type' => 'entity_reference_label',
])
->save();
// Create a user and then login.
$this->adminUser = $this->drupalCreateUser(['create article content']);
$this->drupalLogin($this->adminUser);
// Retrieve the autocomplete URL.
$this->drupalGet('node/add/article');
$field = $this->assertSession()->fieldExists("{$this->fieldName}[0][target_id]");
$this->autocompleteUrl = $this->getAbsoluteUrl($field->getAttribute('data-autocomplete-path'));
}
/**
* Helper function for JSON formatted requests.
*
* @param string|\Drupal\Core\Url $path
* Drupal path or URL to load into Mink controlled browser.
* @param array $options
* (optional) Options to be forwarded to the URL generator.
* @param string[] $headers
* (optional) An array containing additional HTTP request headers.
*
* @return string[]
* Array representing decoded JSON response.
*/
protected function drupalGetJson($path, array $options = [], array $headers = []) {
$options = array_merge_recursive(['query' => ['_format' => 'json']], $options);
return Json::decode($this->drupalGet($path, $options, $headers));
}
/**
* Tests that the autocomplete method returns the good number of results.
*
* @see \Drupal\taxonomy\Controller\TermAutocompleteController::autocomplete()
*/
public function testAutocompleteCountResults(): void {
// Test that no matching term found.
$data = $this->drupalGetJson(
$this->autocompleteUrl,
['query' => ['q' => 'zzz']]
);
$this->assertEmpty($data, 'Autocomplete returned no results');
// Test that only one matching term found, when only one matches.
$data = $this->drupalGetJson(
$this->autocompleteUrl,
['query' => ['q' => 'aaa 10']]
);
$this->assertCount(1, $data, 'Autocomplete returned 1 result');
// Test the correct number of matches when multiple are partial matches.
$data = $this->drupalGetJson(
$this->autocompleteUrl,
['query' => ['q' => 'aaa 1']]
);
$this->assertCount(3, $data, 'Autocomplete returned 3 results');
// Tests that only 10 results are returned, even if there are more than 10
// matches.
$data = $this->drupalGetJson(
$this->autocompleteUrl,
['query' => ['q' => 'aaa']]
);
$this->assertCount(10, $data, 'Autocomplete returned only 10 results (for over 10 matches)');
}
/**
* Tests that the autocomplete method returns properly ordered results.
*
* @see \Drupal\taxonomy\Controller\TermAutocompleteController::autocomplete()
*/
public function testAutocompleteOrderedResults(): void {
$expectedResults = [
'aaa 10 bbb',
'aaa 11 bbb',
'aaa 12 bbb',
'aaa 20 bbb',
'aaa 30 bbb',
'aaa 40 bbb',
'aaa 50 bbb',
'aaa 70 bbb',
'bbb 60 aaa',
];
// Build $expected to match the autocomplete results.
$expected = [];
foreach ($expectedResults as $termName) {
$expected[] = [
'value' => $termName . ' (' . $this->termIds[$termName] . ')',
'label' => $termName,
];
}
$data = $this->drupalGetJson(
$this->autocompleteUrl,
['query' => ['q' => 'bbb']]
);
$this->assertSame($expected, $data);
}
}

View File

@ -0,0 +1,49 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\taxonomy\Functional;
use Drupal\taxonomy\Entity\Vocabulary;
use Drupal\taxonomy\Entity\Term;
use Drupal\Tests\system\Functional\Entity\EntityWithUriCacheTagsTestBase;
/**
* Tests the Taxonomy term entity's cache tags.
*
* @group taxonomy
*/
class TermCacheTagsTest extends EntityWithUriCacheTagsTestBase {
/**
* {@inheritdoc}
*/
protected static $modules = ['taxonomy'];
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* {@inheritdoc}
*/
protected function createEntity() {
// Create a "Camelids" vocabulary.
$vocabulary = Vocabulary::create([
'name' => 'Camelids',
'vid' => 'camelids',
]);
$vocabulary->save();
// Create a "Llama" taxonomy term.
$term = Term::create([
'name' => 'Llama',
'vid' => $vocabulary->id(),
]);
$term->save();
return $term;
}
}

View File

@ -0,0 +1,43 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\taxonomy\Functional;
/**
* Tests views contextual links on terms.
*
* @group taxonomy
*/
class TermContextualLinksTest extends TaxonomyTestBase {
/**
* {@inheritdoc}
*/
protected static $modules = [
'contextual',
];
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* Tests contextual links.
*/
public function testTermContextualLinks(): void {
$vocabulary = $this->createVocabulary();
$term = $this->createTerm($vocabulary);
$user = $this->drupalCreateUser([
'administer taxonomy',
'access contextual links',
]);
$this->drupalLogin($user);
$this->drupalGet('taxonomy/term/' . $term->id());
$this->assertSession()->elementExists('css', 'div[data-contextual-id^="taxonomy_term:taxonomy_term=' . $term->id() . ':"]');
}
}

View File

@ -0,0 +1,256 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\taxonomy\Functional;
use Drupal\Core\Link;
use Drupal\Core\Database\Database;
use Drupal\Core\Field\FieldStorageDefinitionInterface;
/**
* Tests the hook implementations that maintain the taxonomy index.
*
* @group taxonomy
*/
class TermIndexTest extends TaxonomyTestBase {
/**
* {@inheritdoc}
*/
protected static $modules = ['views'];
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* Vocabulary for testing.
*
* @var \Drupal\taxonomy\VocabularyInterface
*/
protected $vocabulary;
/**
* Name of the taxonomy term reference field.
*
* @var string
*/
protected $fieldName1;
/**
* Name of the taxonomy term reference field.
*
* @var string
*/
protected $fieldName2;
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
// Create an administrative user.
$this->drupalLogin($this->drupalCreateUser([
'administer taxonomy',
'bypass node access',
]));
// Create a vocabulary and add two term reference fields to article nodes.
$this->vocabulary = $this->createVocabulary();
$this->fieldName1 = $this->randomMachineName();
$handler_settings = [
'target_bundles' => [
$this->vocabulary->id() => $this->vocabulary->id(),
],
'auto_create' => TRUE,
];
$this->createEntityReferenceField('node', 'article', $this->fieldName1, '', 'taxonomy_term', 'default', $handler_settings, FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED);
/** @var \Drupal\Core\Entity\EntityDisplayRepositoryInterface $display_repository */
$display_repository = \Drupal::service('entity_display.repository');
$display_repository->getFormDisplay('node', 'article')
->setComponent($this->fieldName1, [
'type' => 'options_select',
])
->save();
$display_repository->getViewDisplay('node', 'article')
->setComponent($this->fieldName1, [
'type' => 'entity_reference_label',
])
->save();
$this->fieldName2 = $this->randomMachineName();
$this->createEntityReferenceField('node', 'article', $this->fieldName2, '', 'taxonomy_term', 'default', $handler_settings, FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED);
/** @var \Drupal\Core\Entity\EntityDisplayRepositoryInterface $display_repository */
$display_repository = \Drupal::service('entity_display.repository');
$display_repository->getFormDisplay('node', 'article')
->setComponent($this->fieldName2, [
'type' => 'options_select',
])
->save();
$display_repository->getViewDisplay('node', 'article')
->setComponent($this->fieldName2, [
'type' => 'entity_reference_label',
])
->save();
}
/**
* Tests that the taxonomy index is maintained properly.
*/
public function testTaxonomyIndex(): void {
$node_storage = $this->container->get('entity_type.manager')->getStorage('node');
// Create terms in the vocabulary.
$term_1 = $this->createTerm($this->vocabulary);
$term_2 = $this->createTerm($this->vocabulary);
// Post an article.
$edit = [];
$edit['title[0][value]'] = $this->randomMachineName();
$edit['body[0][value]'] = $this->randomMachineName();
$edit["{$this->fieldName1}[]"] = $term_1->id();
$edit["{$this->fieldName2}[]"] = $term_1->id();
$this->drupalGet('node/add/article');
$this->submitForm($edit, 'Save');
// Check that the term is indexed, and only once.
$node = $this->drupalGetNodeByTitle($edit['title[0][value]']);
$connection = Database::getConnection();
$index_count = $connection->select('taxonomy_index')
->condition('nid', $node->id())
->condition('tid', $term_1->id())
->countQuery()
->execute()
->fetchField();
$this->assertEquals(1, $index_count, 'Term 1 is indexed once.');
// Update the article to change one term.
$edit["{$this->fieldName1}[]"] = $term_2->id();
$this->drupalGet('node/' . $node->id() . '/edit');
$this->submitForm($edit, 'Save');
// Check that both terms are indexed.
$index_count = $connection->select('taxonomy_index')
->condition('nid', $node->id())
->condition('tid', $term_1->id())
->countQuery()
->execute()
->fetchField();
$this->assertEquals(1, $index_count, 'Term 1 is indexed.');
$index_count = $connection->select('taxonomy_index')
->condition('nid', $node->id())
->condition('tid', $term_2->id())
->countQuery()
->execute()
->fetchField();
$this->assertEquals(1, $index_count, 'Term 2 is indexed.');
// Update the article to change another term.
$edit["{$this->fieldName2}[]"] = $term_2->id();
$this->drupalGet('node/' . $node->id() . '/edit');
$this->submitForm($edit, 'Save');
// Check that only one term is indexed.
$index_count = $connection->select('taxonomy_index')
->condition('nid', $node->id())
->condition('tid', $term_1->id())
->countQuery()
->execute()
->fetchField();
$this->assertEquals(0, $index_count, 'Term 1 is not indexed.');
$index_count = $connection->select('taxonomy_index')
->condition('nid', $node->id())
->condition('tid', $term_2->id())
->countQuery()
->execute()
->fetchField();
$this->assertEquals(1, $index_count, 'Term 2 is indexed once.');
// Redo the above tests without interface.
$node = $node_storage->load($node->id());
$node->title = $this->randomMachineName();
// Update the article with no term changed.
$node->save();
// Check that the index was not changed.
$index_count = $connection->select('taxonomy_index')
->condition('nid', $node->id())
->condition('tid', $term_1->id())
->countQuery()
->execute()
->fetchField();
$this->assertEquals(0, $index_count, 'Term 1 is not indexed.');
$index_count = $connection->select('taxonomy_index')
->condition('nid', $node->id())
->condition('tid', $term_2->id())
->countQuery()
->execute()
->fetchField();
$this->assertEquals(1, $index_count, 'Term 2 is indexed once.');
// Update the article to change one term.
$node->{$this->fieldName1} = [['target_id' => $term_1->id()]];
$node->save();
// Check that both terms are indexed.
$index_count = $connection->select('taxonomy_index')
->condition('nid', $node->id())
->condition('tid', $term_1->id())
->countQuery()
->execute()
->fetchField();
$this->assertEquals(1, $index_count, 'Term 1 is indexed.');
$index_count = $connection->select('taxonomy_index')
->condition('nid', $node->id())
->condition('tid', $term_2->id())
->countQuery()
->execute()
->fetchField();
$this->assertEquals(1, $index_count, 'Term 2 is indexed.');
// Update the article to change another term.
$node->{$this->fieldName2} = [['target_id' => $term_1->id()]];
$node->save();
// Check that only one term is indexed.
$index_count = $connection->select('taxonomy_index')
->condition('nid', $node->id())
->condition('tid', $term_1->id())
->countQuery()
->execute()
->fetchField();
$this->assertEquals(1, $index_count, 'Term 1 is indexed once.');
$index_count = $connection->select('taxonomy_index')
->condition('nid', $node->id())
->condition('tid', $term_2->id())
->countQuery()
->execute()
->fetchField();
$this->assertEquals(0, $index_count, 'Term 2 is not indexed.');
}
/**
* Tests that there is a link to the parent term on the child term page.
*/
public function testTaxonomyTermHierarchyBreadcrumbs(): void {
// Create two taxonomy terms and set term2 as the parent of term1.
$term1 = $this->createTerm($this->vocabulary);
$term2 = $this->createTerm($this->vocabulary);
$term1->parent = [$term2->id()];
$term1->save();
// Verify that the page breadcrumbs include a link to the parent term.
$this->drupalGet('taxonomy/term/' . $term1->id());
// Breadcrumbs are not rendered with a language, prevent the term
// language from being added to the options.
// Check that parent term link is displayed when viewing the node.
$this->assertSession()->responseContains(Link::fromTextAndUrl($term2->getName(), $term2->toUrl('canonical', ['language' => NULL]))->toString());
}
}

View File

@ -0,0 +1,172 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\taxonomy\Functional;
use Drupal\Core\Language\LanguageInterface;
use Drupal\language\Entity\ConfigurableLanguage;
/**
* Tests the language functionality for the taxonomy terms.
*
* @group taxonomy
*/
class TermLanguageTest extends TaxonomyTestBase {
/**
* {@inheritdoc}
*/
protected static $modules = ['language'];
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* Vocabulary for testing.
*
* @var \Drupal\taxonomy\VocabularyInterface
*/
protected $vocabulary;
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
// Create an administrative user.
$this->drupalLogin($this->drupalCreateUser(['administer taxonomy']));
// Create a vocabulary to which the terms will be assigned.
$this->vocabulary = $this->createVocabulary();
// Add some custom languages.
foreach (['aa', 'bb', 'cc'] as $language_code) {
ConfigurableLanguage::create([
'id' => $language_code,
'label' => $this->randomMachineName(),
])->save();
}
}
/**
* Tests the language of a term.
*/
public function testTermLanguage(): void {
// Configure the vocabulary to not hide the language selector.
$edit = [
'default_language[language_alterable]' => TRUE,
];
$this->drupalGet('admin/structure/taxonomy/manage/' . $this->vocabulary->id());
$this->submitForm($edit, 'Save');
// Add a term.
$this->drupalGet('admin/structure/taxonomy/manage/' . $this->vocabulary->id() . '/add');
// Check that we have the language selector.
$this->assertSession()->fieldExists('edit-langcode-0-value');
// Submit the term.
$edit = [
'name[0][value]' => $this->randomMachineName(),
'langcode[0][value]' => 'aa',
];
$this->submitForm($edit, 'Save');
$terms = \Drupal::entityTypeManager()->getStorage('taxonomy_term')->loadByProperties([
'name' => $edit['name[0][value]'],
]);
$term = reset($terms);
$this->assertEquals($edit['langcode[0][value]'], $term->language()->getId(), 'The term contains the correct langcode.');
// Check if on the edit page the language is correct.
$this->drupalGet('taxonomy/term/' . $term->id() . '/edit');
$this->assertTrue($this->assertSession()->optionExists('edit-langcode-0-value', $edit['langcode[0][value]'])->isSelected());
// Change the language of the term.
$edit['langcode[0][value]'] = 'bb';
$this->drupalGet('taxonomy/term/' . $term->id() . '/edit');
$this->submitForm($edit, 'Save');
// Check again that on the edit page the language is correct.
$this->drupalGet('taxonomy/term/' . $term->id() . '/edit');
$this->assertTrue($this->assertSession()->optionExists('edit-langcode-0-value', $edit['langcode[0][value]'])->isSelected());
}
/**
* Tests the default language selection for taxonomy terms.
*/
public function testDefaultTermLanguage(): void {
// Configure the vocabulary to not hide the language selector, and make the
// default language of the terms fixed.
$edit = [
'default_language[langcode]' => 'bb',
'default_language[language_alterable]' => TRUE,
];
$this->drupalGet('admin/structure/taxonomy/manage/' . $this->vocabulary->id());
$this->submitForm($edit, 'Save');
$this->drupalGet('admin/structure/taxonomy/manage/' . $this->vocabulary->id() . '/add');
$this->assertTrue($this->assertSession()->optionExists('edit-langcode-0-value', 'bb')->isSelected());
// Make the default language of the terms to be the current interface.
$edit = [
'default_language[langcode]' => 'current_interface',
'default_language[language_alterable]' => TRUE,
];
$this->drupalGet('admin/structure/taxonomy/manage/' . $this->vocabulary->id());
$this->submitForm($edit, 'Save');
$this->drupalGet('aa/admin/structure/taxonomy/manage/' . $this->vocabulary->id() . '/add');
$this->assertTrue($this->assertSession()->optionExists('edit-langcode-0-value', 'aa')->isSelected());
$this->drupalGet('bb/admin/structure/taxonomy/manage/' . $this->vocabulary->id() . '/add');
$this->assertTrue($this->assertSession()->optionExists('edit-langcode-0-value', 'bb')->isSelected());
// Change the default language of the site and check if the default terms
// language is still correctly selected.
$this->config('system.site')->set('default_langcode', 'cc')->save();
$edit = [
'default_language[langcode]' => LanguageInterface::LANGCODE_SITE_DEFAULT,
'default_language[language_alterable]' => TRUE,
];
$this->drupalGet('admin/structure/taxonomy/manage/' . $this->vocabulary->id());
$this->submitForm($edit, 'Save');
$this->drupalGet('admin/structure/taxonomy/manage/' . $this->vocabulary->id() . '/add');
$this->assertTrue($this->assertSession()->optionExists('edit-langcode-0-value', 'cc')->isSelected());
}
/**
* Tests that translated terms are displayed correctly on the term overview.
*/
public function testTermTranslatedOnOverviewPage(): void {
// Configure the vocabulary to not hide the language selector.
$edit = [
'default_language[language_alterable]' => TRUE,
];
$this->drupalGet('admin/structure/taxonomy/manage/' . $this->vocabulary->id());
$this->submitForm($edit, 'Save');
// Add a term.
$this->drupalGet('admin/structure/taxonomy/manage/' . $this->vocabulary->id() . '/add');
// Submit the term.
$edit = [
'name[0][value]' => $this->randomMachineName(),
'langcode[0][value]' => 'aa',
];
$this->submitForm($edit, 'Save');
$terms = \Drupal::entityTypeManager()->getStorage('taxonomy_term')->loadByProperties([
'name' => $edit['name[0][value]'],
]);
$term = reset($terms);
// Add a translation for that term.
$translated_title = $this->randomMachineName();
$term->addTranslation('bb', [
'name' => $translated_title,
]);
$term->save();
// Overview page in the other language shows the translated term
$this->drupalGet('bb/admin/structure/taxonomy/manage/' . $this->vocabulary->id() . '/overview');
$this->assertSession()->responseMatches('|<a[^>]*>' . $translated_title . '</a>|');
}
}

View File

@ -0,0 +1,363 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\taxonomy\Functional;
use Drupal\taxonomy\Entity\Vocabulary;
use Drupal\taxonomy\TermInterface;
use Drupal\Tests\BrowserTestBase;
/**
* Tests managing taxonomy parents through the user interface.
*
* @group taxonomy
*/
class TermParentsTest extends BrowserTestBase {
/**
* {@inheritdoc}
*/
protected static $modules = ['taxonomy_test'];
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* The term storage.
*
* @var \Drupal\taxonomy\TermStorageInterface
*/
protected $termStorage;
/**
* The state service.
*
* @var \Drupal\Core\State\StateInterface
*/
protected $state;
/**
* The ID of the vocabulary used in this test.
*
* @var string
*/
protected $vocabularyId = 'test_vocabulary';
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
/** @var \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager */
$entity_type_manager = $this->container->get('entity_type.manager');
$this->termStorage = $entity_type_manager->getStorage('taxonomy_term');
$this->state = $this->container->get('state');
Vocabulary::create(['vid' => $this->vocabularyId, 'name' => 'Test'])->save();
$this->drupalLogin($this->drupalCreateUser(['administer taxonomy']));
}
/**
* Tests specifying parents when creating terms.
*/
public function testAddWithParents(): void {
$this->drupalGet("/admin/structure/taxonomy/manage/{$this->vocabularyId}/add");
$page = $this->getSession()->getPage();
// Create a term without any parents.
$term_1 = $this->submitAddTermForm('Test term 1');
$expected = [['target_id' => 0]];
$this->assertEquals($expected, $term_1->get('parent')->getValue());
// Explicitly selecting <root> should have the same effect as not selecting
// anything.
$page->selectFieldOption('Parent terms', '<root>');
$term_2 = $this->submitAddTermForm('Test term 2');
$this->assertEquals($expected, $term_2->get('parent')->getValue());
// Create two terms with the previously created ones as parents,
// respectively.
$page->selectFieldOption('Parent terms', 'Test term 1');
$term_3 = $this->submitAddTermForm('Test term 3');
$expected = [['target_id' => $term_1->id()]];
$this->assertEquals($expected, $term_3->get('parent')->getValue());
$page->selectFieldOption('Parent terms', 'Test term 2');
$term_4 = $this->submitAddTermForm('Test term 4');
$expected = [['target_id' => $term_2->id()]];
$this->assertEquals($expected, $term_4->get('parent')->getValue());
// Create a term with term 3 as parent.
$page->selectFieldOption('Parent terms', '-Test term 3');
$term_5 = $this->submitAddTermForm('Test term 5');
$expected = [['target_id' => $term_3->id()]];
$this->assertEquals($expected, $term_5->get('parent')->getValue());
// Create a term with multiple parents.
$page->selectFieldOption('Parent terms', '--Test term 5');
$page->selectFieldOption('Parent terms', '-Test term 4', TRUE);
$term_6 = $this->submitAddTermForm('Test term 6');
$expected = [
['target_id' => $term_5->id()],
['target_id' => $term_4->id()],
];
$this->assertEquals($expected, $term_6->get('parent')->getValue());
}
/**
* Creates a term through the user interface and returns it.
*
* @param string $name
* The name of the term to create.
*
* @return \Drupal\taxonomy\TermInterface
* The newly created taxonomy term.
*/
protected function submitAddTermForm($name) {
$this->getSession()->getPage()->fillField('Name', $name);
$this->submitForm([], 'Save');
$result = $this->termStorage
->getQuery()
->accessCheck(FALSE)
->condition('name', $name)
->execute();
/** @var \Drupal\taxonomy\TermInterface $term_1 */
$term_1 = $this->termStorage->load(reset($result));
$this->assertInstanceOf(TermInterface::class, $term_1);
return $term_1;
}
/**
* Tests editing the parents of existing terms.
*/
public function testEditingParents(): void {
$terms = $this->doTestEditingSingleParent();
$term_5 = array_pop($terms);
$term_4 = array_pop($terms);
// Create a term with multiple parents.
$term_6 = $this->createTerm('Test term 6', [
// Term 5 comes before term 4 in the user interface, so add the parents in
// the matching order.
$term_5->id(),
$term_4->id(),
]);
$this->drupalGet($term_6->toUrl('edit-form'));
$this->assertParentOption('<root>');
$this->assertParentOption('Test term 1');
$this->assertParentOption('-Test term 3');
$this->assertParentOption('--Test term 5', TRUE);
$this->assertParentOption('Test term 2');
$this->assertParentOption('-Test term 4', TRUE);
$this->submitForm([], 'Save');
$this->assertParentsUnchanged($term_6);
}
/**
* Tests specifying parents when creating terms and a disabled parent form.
*/
public function testEditingParentsWithDisabledFormElement(): void {
// Disable the parent form element.
$this->state->set('taxonomy_test.disable_parent_form_element', TRUE);
$this->drupalGet("/admin/structure/taxonomy/manage/{$this->vocabularyId}/add");
$this->assertSession()->fieldDisabled('Parent terms');
$terms = $this->doTestEditingSingleParent();
$term_5 = array_pop($terms);
$term_4 = array_pop($terms);
// Create a term with multiple parents.
$term_6 = $this->createTerm('Test term 6', [
// When the parent form element is disabled, its default value is used as
// the value which gets populated in ascending order of term IDs.
$term_4->id(),
$term_5->id(),
]);
$this->drupalGet($term_6->toUrl('edit-form'));
$this->assertParentOption('<root>');
$this->assertParentOption('Test term 1');
$this->assertParentOption('-Test term 3');
$this->assertParentOption('--Test term 5', TRUE);
$this->assertParentOption('Test term 2');
$this->assertParentOption('-Test term 4', TRUE);
$this->submitForm([], 'Save');
$this->assertParentsUnchanged($term_6);
}
/**
* Performs tests that edit terms with a single parent.
*
* @return \Drupal\taxonomy\TermInterface[]
* A list of terms created for testing.
*/
protected function doTestEditingSingleParent(): array {
$terms = [];
// Create two terms without any parents.
$term_1 = $this->createTerm('Test term 1');
$this->drupalGet($term_1->toUrl('edit-form'));
$this->assertParentOption('<root>', TRUE);
$this->submitForm([], 'Save');
$this->assertParentsUnchanged($term_1);
$terms[] = $term_1;
$term_2 = $this->createTerm('Test term 2');
$this->drupalGet($term_2->toUrl('edit-form'));
$this->assertParentOption('<root>', TRUE);
$this->assertParentOption('Test term 1');
$this->submitForm([], 'Save');
$this->assertParentsUnchanged($term_2);
$terms[] = $term_2;
// Create two terms with the previously created terms as parents,
// respectively.
$term_3 = $this->createTerm('Test term 3', [$term_1->id()]);
$this->drupalGet($term_3->toUrl('edit-form'));
$this->assertParentOption('<root>');
$this->assertParentOption('Test term 1', TRUE);
$this->assertParentOption('Test term 2');
$this->submitForm([], 'Save');
$this->assertParentsUnchanged($term_3);
$terms[] = $term_3;
$term_4 = $this->createTerm('Test term 4', [$term_2->id()]);
$this->drupalGet($term_4->toUrl('edit-form'));
$this->assertParentOption('<root>');
$this->assertParentOption('Test term 1');
$this->assertParentOption('-Test term 3');
$this->assertParentOption('Test term 2', TRUE);
$this->submitForm([], 'Save');
$this->assertParentsUnchanged($term_4);
$terms[] = $term_4;
// Create a term with term 3 as parent.
$term_5 = $this->createTerm('Test term 5', [$term_3->id()]);
$this->drupalGet($term_5->toUrl('edit-form'));
$this->assertParentOption('<root>');
$this->assertParentOption('Test term 1');
$this->assertParentOption('-Test term 3', TRUE);
$this->assertParentOption('Test term 2');
$this->assertParentOption('-Test term 4');
$this->submitForm([], 'Save');
$this->assertParentsUnchanged($term_5);
$terms[] = $term_5;
return $terms;
}
/**
* Test the term add/edit form with parent query parameter.
*/
public function testParentFromQuery(): void {
// Create three terms without any parents.
$term_1 = $this->createTerm('Test term 1');
$term_2 = $this->createTerm('Test term 2');
$term_3 = $this->createTerm('Test term 3');
// Add term form with one parent.
$this->drupalGet("/admin/structure/taxonomy/manage/{$this->vocabularyId}/add", ['query' => ['parent' => $term_1->id()]]);
$this->assertParentOption('Test term 1', TRUE);
$this->assertParentOption('Test term 2', FALSE);
$this->assertParentOption('Test term 3', FALSE);
// Add term form with two parents.
$this->drupalGet("/admin/structure/taxonomy/manage/{$this->vocabularyId}/add", ['query' => ['parent[0]' => $term_1->id(), 'parent[1]' => $term_2->id()]]);
$this->assertParentOption('Test term 1', TRUE);
$this->assertParentOption('Test term 2', TRUE);
$this->assertParentOption('Test term 3', FALSE);
// Add term form with no parents.
$this->drupalGet("/admin/structure/taxonomy/manage/{$this->vocabularyId}/add", ['query' => ['parent' => '']]);
$this->assertParentOption('Test term 1', FALSE);
$this->assertParentOption('Test term 2', FALSE);
$this->assertParentOption('Test term 3', FALSE);
// Add term form with invalid parent.
$this->drupalGet("/admin/structure/taxonomy/manage/{$this->vocabularyId}/add", ['query' => ['parent' => -1]]);
$this->assertParentOption('Test term 1', FALSE);
$this->assertParentOption('Test term 2', FALSE);
$this->assertParentOption('Test term 3', FALSE);
// Edit term form with one parent.
$this->drupalGet($term_1->toUrl('edit-form'), ['query' => ['parent' => $term_2->id()]]);
$this->assertParentOption('Test term 2', TRUE);
$this->assertParentOption('Test term 3', FALSE);
// Edit term form with two parents.
$this->drupalGet($term_1->toUrl('edit-form'), ['query' => ['parent[0]' => $term_2->id(), 'parent[1]' => $term_3->id()]]);
$this->assertParentOption('Test term 2', TRUE);
$this->assertParentOption('Test term 3', TRUE);
// Edit term form with no parents.
$this->drupalGet($term_1->toUrl('edit-form'), ['query' => ['parent' => '']]);
$this->assertParentOption('Test term 2', FALSE);
$this->assertParentOption('Test term 3', FALSE);
// Edit term form with invalid parent.
$this->drupalGet($term_1->toUrl('edit-form'), ['query' => ['parent' => -1]]);
$this->assertParentOption('Test term 2', FALSE);
$this->assertParentOption('Test term 3', FALSE);
}
/**
* Creates a term, saves it and returns it.
*
* @param string $name
* The name of the term to create.
* @param int[] $parent_ids
* (optional) A list of parent term IDs.
*
* @return \Drupal\taxonomy\TermInterface
* The created term.
*/
protected function createTerm($name, array $parent_ids = []) {
/** @var \Drupal\taxonomy\TermInterface $term */
$term = $this->termStorage->create([
'name' => $name,
'vid' => $this->vocabularyId,
]);
foreach ($parent_ids as $delta => $parent_id) {
$term->get('parent')->set($delta, ['target_id' => $parent_id]);
}
$term->save();
return $term;
}
/**
* Asserts that an option in the parent form element of terms exists.
*
* @param string $option
* The label of the parent option.
* @param bool $selected
* (optional) Whether or not the option should be selected. Defaults to
* FALSE.
*
* @internal
*/
protected function assertParentOption(string $option, bool $selected = FALSE): void {
$option = $this->assertSession()->optionExists('Parent terms', $option);
if ($selected) {
$this->assertTrue($option->hasAttribute('selected'));
}
else {
$this->assertFalse($option->hasAttribute('selected'));
}
}
/**
* Asserts that the parents of the term have not changed after saving.
*
* @param \Drupal\taxonomy\TermInterface $term
* The term to check.
*
* @internal
*/
protected function assertParentsUnchanged(TermInterface $term): void {
$saved_term = $this->termStorage->load($term->id());
$expected = $term->get('parent')->getValue();
$this->assertEquals($expected, $saved_term->get('parent')->getValue());
}
}

View File

@ -0,0 +1,707 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\taxonomy\Functional;
use Drupal\Component\Utility\Tags;
use Drupal\Core\Field\FieldStorageDefinitionInterface;
use Drupal\Core\Url;
use Drupal\field\Entity\FieldConfig;
use Drupal\taxonomy\Entity\Term;
use Drupal\taxonomy\TermInterface;
use Drupal\Tests\system\Functional\Menu\AssertBreadcrumbTrait;
/**
* Tests load, save and delete for taxonomy terms.
*
* @group taxonomy
*/
class TermTest extends TaxonomyTestBase {
use AssertBreadcrumbTrait;
/**
* Vocabulary for testing.
*
* @var \Drupal\taxonomy\VocabularyInterface
*/
protected $vocabulary;
/**
* Taxonomy term reference field for testing.
*
* @var \Drupal\field\FieldConfigInterface
*/
protected $field;
/**
* {@inheritdoc}
*/
protected static $modules = ['block', 'taxonomy_test'];
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
$this->drupalPlaceBlock('local_actions_block');
$this->drupalPlaceBlock('local_tasks_block');
$this->drupalPlaceBlock('page_title_block');
$this->drupalLogin($this->drupalCreateUser([
'administer taxonomy',
'access taxonomy overview',
'bypass node access',
]));
$this->vocabulary = $this->createVocabulary();
$field_name = 'taxonomy_' . $this->vocabulary->id();
$handler_settings = [
'target_bundles' => [
$this->vocabulary->id() => $this->vocabulary->id(),
],
'auto_create' => TRUE,
];
$this->createEntityReferenceField('node', 'article', $field_name, '', 'taxonomy_term', 'default', $handler_settings, FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED);
$this->field = FieldConfig::loadByName('node', 'article', $field_name);
/** @var \Drupal\Core\Entity\EntityDisplayRepositoryInterface $display_repository */
$display_repository = \Drupal::service('entity_display.repository');
$display_repository->getFormDisplay('node', 'article')
->setComponent($field_name, [
'type' => 'options_select',
])
->save();
$display_repository->getViewDisplay('node', 'article')
->setComponent($field_name, [
'type' => 'entity_reference_label',
])
->save();
}
/**
* Tests terms in a single and multiple hierarchy.
*/
public function testTaxonomyTermHierarchy(): void {
// Create two taxonomy terms.
$term1 = $this->createTerm($this->vocabulary);
$term2 = $this->createTerm($this->vocabulary);
// Get the taxonomy storage.
/** @var \Drupal\taxonomy\TermStorageInterface $taxonomy_storage */
$taxonomy_storage = $this->container->get('entity_type.manager')->getStorage('taxonomy_term');
// Check that hierarchy is flat.
$this->assertEquals(0, $taxonomy_storage->getVocabularyHierarchyType($this->vocabulary->id()), 'Vocabulary is flat.');
// Edit $term2, setting $term1 as parent.
$edit = [];
$edit['parent[]'] = [$term1->id()];
$this->drupalGet('taxonomy/term/' . $term2->id() . '/edit');
$this->submitForm($edit, 'Save');
// Check the hierarchy.
$children = $taxonomy_storage->loadChildren($term1->id());
$parents = $taxonomy_storage->loadParents($term2->id());
$this->assertTrue(isset($children[$term2->id()]), 'Child found correctly.');
$this->assertTrue(isset($parents[$term1->id()]), 'Parent found correctly.');
// Load and save a term, confirming that parents are still set.
$term = Term::load($term2->id());
$term->save();
$parents = $taxonomy_storage->loadParents($term2->id());
$this->assertTrue(isset($parents[$term1->id()]), 'Parent found correctly.');
// Create a third term and save this as a parent of term2.
$term3 = $this->createTerm($this->vocabulary);
$term2->parent = [$term1->id(), $term3->id()];
$term2->save();
$parents = $taxonomy_storage->loadParents($term2->id());
$this->assertArrayHasKey($term1->id(), $parents);
$this->assertArrayHasKey($term3->id(), $parents);
}
/**
* Tests that many terms with parents show on each page.
*/
public function testTaxonomyTermChildTerms(): void {
// Set limit to 10 terms per page. Set variable to 9 so 10 terms appear.
$this->config('taxonomy.settings')->set('terms_per_page_admin', '9')->save();
$term1 = $this->createTerm($this->vocabulary);
$terms_array = [];
// Create 40 terms. Terms 1-12 get parent of $term1. All others are
// individual terms.
for ($x = 1; $x <= 40; $x++) {
$edit = [];
// Set terms in order so we know which terms will be on which pages.
$edit['weight'] = $x;
// Set terms 1-20 to be children of first term created.
if ($x <= 12) {
$edit['parent'] = $term1->id();
}
$term = $this->createTerm($this->vocabulary, $edit);
$terms_array[$x] = Term::load($term->id());
}
// Get Page 1. Parent term and terms 1-13 are displayed.
$this->drupalGet('admin/structure/taxonomy/manage/' . $this->vocabulary->id() . '/overview');
$this->assertSession()->pageTextContains($term1->getName());
for ($x = 1; $x <= 13; $x++) {
$this->assertSession()->pageTextContains($terms_array[$x]->getName());
}
// Get Page 2. Parent term and terms 1-18 are displayed.
$this->drupalGet('admin/structure/taxonomy/manage/' . $this->vocabulary->id() . '/overview', ['query' => ['page' => 1]]);
$this->assertSession()->pageTextContains($term1->getName());
for ($x = 1; $x <= 18; $x++) {
$this->assertSession()->pageTextContains($terms_array[$x]->getName());
}
// Get Page 3. No parent term and no terms <18 are displayed. Terms 18-25
// are displayed.
$this->drupalGet('admin/structure/taxonomy/manage/' . $this->vocabulary->id() . '/overview', ['query' => ['page' => 2]]);
$this->assertSession()->pageTextNotContains($term1->getName());
for ($x = 1; $x <= 17; $x++) {
$this->assertSession()->pageTextNotContains($terms_array[$x]->getName());
}
for ($x = 18; $x <= 25; $x++) {
$this->assertSession()->pageTextContains($terms_array[$x]->getName());
}
}
/**
* Tests term creation with a free-tagging vocabulary from the node form.
*/
public function testNodeTermCreationAndDeletion(): void {
// Enable tags in the vocabulary.
$field = $this->field;
\Drupal::service('entity_display.repository')
->getFormDisplay($field->getTargetEntityTypeId(), $field->getTargetBundle())
->setComponent($field->getName(), [
'type' => 'entity_reference_autocomplete_tags',
'settings' => [
'placeholder' => 'Start typing here.',
],
])
->save();
// Prefix the terms with a letter to ensure there is no clash in the first
// three letters.
// @see https://www.drupal.org/node/2397691
$terms = [
'term1' => 'a' . $this->randomMachineName(),
'term2' => 'b' . $this->randomMachineName(),
'term3' => 'c' . $this->randomMachineName() . ', ' . $this->randomMachineName(),
'term4' => 'd' . $this->randomMachineName(),
];
$edit = [];
$edit['title[0][value]'] = $this->randomMachineName();
$edit['body[0][value]'] = $this->randomMachineName();
// Insert the terms in a comma separated list. Vocabulary 1 is a
// free-tagging field created by the default profile.
$edit[$field->getName() . '[target_id]'] = Tags::implode($terms);
// Verify the placeholder is there.
$this->drupalGet('node/add/article');
$this->assertSession()->responseContains('placeholder="Start typing here."');
// Preview and verify the terms appear but are not created.
$this->submitForm($edit, 'Preview');
foreach ($terms as $term) {
$this->assertSession()->pageTextContains($term);
}
$tree = $this->container->get('entity_type.manager')->getStorage('taxonomy_term')->loadTree($this->vocabulary->id());
$this->assertEmpty($tree, 'The terms are not created on preview.');
// Save, creating the terms.
$this->drupalGet('node/add/article');
$this->submitForm($edit, 'Save');
$this->assertSession()->pageTextContains('Article ' . $edit['title[0][value]'] . ' has been created.');
// Verify that the creation message contains a link to a node.
$this->assertSession()->elementExists('xpath', '//div[@data-drupal-messages]//a[contains(@href, "node/")]');
foreach ($terms as $term) {
$this->assertSession()->pageTextContains($term);
}
// Get the created terms.
$term_objects = [];
foreach ($terms as $key => $term) {
$term_objects[$key] = \Drupal::entityTypeManager()->getStorage('taxonomy_term')->loadByProperties([
'name' => $term,
]);
$term_objects[$key] = reset($term_objects[$key]);
}
// Get the node.
$node = $this->drupalGetNodeByTitle($edit['title[0][value]']);
// Test editing the node.
$this->drupalGet('node/' . $node->id() . '/edit');
$this->submitForm($edit, 'Save');
foreach ($terms as $term) {
$this->assertSession()->pageTextContains($term);
}
// Delete term 1 from the term edit page.
$this->drupalGet('taxonomy/term/' . $term_objects['term1']->id() . '/edit');
$this->clickLink('Delete');
$this->submitForm([], 'Delete');
// Delete term 2 from the term delete page.
$this->drupalGet('taxonomy/term/' . $term_objects['term2']->id() . '/delete');
$this->submitForm([], 'Delete');
// Verify that the terms appear on the node page after the two terms were
// deleted.
$term_names = [$term_objects['term3']->getName(), $term_objects['term4']->getName()];
$this->drupalGet('node/' . $node->id());
foreach ($term_names as $term_name) {
$this->assertSession()->pageTextContains($term_name);
}
$this->assertSession()->pageTextNotContains($term_objects['term1']->getName());
$this->assertSession()->pageTextNotContains($term_objects['term2']->getName());
}
/**
* Save, edit and delete a term using the user interface.
*/
public function testTermInterface(): void {
\Drupal::service('module_installer')->install(['views']);
$edit = [
'name[0][value]' => $this->randomMachineName(12),
'description[0][value]' => $this->randomMachineName(100),
];
// Explicitly set the parents field to 'root', to ensure that
// TermForm::save() handles the invalid term ID correctly.
$edit['parent[]'] = [0];
// Create the term to edit.
$this->drupalGet('admin/structure/taxonomy/manage/' . $this->vocabulary->id() . '/add');
$this->submitForm($edit, 'Save');
// Ensure form redirected back to term add page.
$this->assertSession()->addressEquals('admin/structure/taxonomy/manage/' . $this->vocabulary->id() . '/add');
$terms = \Drupal::entityTypeManager()->getStorage('taxonomy_term')->loadByProperties([
'name' => $edit['name[0][value]'],
]);
$term = reset($terms);
$this->assertNotNull($term, 'Term found in database.');
// Submitting a term takes us to the add page; we need the List page.
$this->drupalGet('admin/structure/taxonomy/manage/' . $this->vocabulary->id() . '/overview');
$this->clickLink('Edit', 1);
// Verify that the randomly generated term is present.
$this->assertSession()->pageTextContains($edit['name[0][value]']);
$this->assertSession()->pageTextContains($edit['description[0][value]']);
// Test the "Add child" link on the overview page.
$this->drupalGet('admin/structure/taxonomy/manage/' . $this->vocabulary->id() . '/overview');
$this->assertSession()->linkExistsExact('Add child');
$this->clickLink('Add child');
$edit = [
'name[0][value]' => 'Child term',
];
$this->submitForm($edit, 'Save');
$terms = \Drupal::entityTypeManager()->getStorage('taxonomy_term')->loadByProperties([
'name' => 'Child term',
]);
$child = reset($terms);
$this->assertNotNull($child, 'Child term found in database.');
$this->assertEquals($term->id(), $child->get('parent')->getValue()[0]['target_id']);
// Edit the term.
$edit = [
'name[0][value]' => $this->randomMachineName(14),
'description[0][value]' => $this->randomMachineName(102),
];
$this->drupalGet('taxonomy/term/' . $term->id() . '/edit');
$this->submitForm($edit, 'Save');
// Ensure form redirected back to term view.
$this->assertSession()->addressEquals('taxonomy/term/' . $term->id());
// Check that the term is still present at admin UI after edit.
$this->drupalGet('admin/structure/taxonomy/manage/' . $this->vocabulary->id() . '/overview');
$this->assertSession()->pageTextContains($edit['name[0][value]']);
$this->assertSession()->linkExists('Edit');
// Unpublish the term.
$this->drupalGet('taxonomy/term/' . $term->id() . '/edit');
$this->submitForm(["status[value]" => 0], 'Save');
// Check that the term is now unpublished in the list.
$this->drupalGet('admin/structure/taxonomy/manage/' . $this->vocabulary->id() . '/overview');
$this->assertSession()->elementTextContains('css', "#edit-terms-tid{$term->id()}0-status", 'Unpublished');
// Check the term link can be clicked through to the term page.
$this->clickLink($edit['name[0][value]']);
$this->assertSession()->statusCodeEquals(200);
// View the term and check that it is correct.
$this->drupalGet('taxonomy/term/' . $term->id());
$this->assertSession()->pageTextContains($edit['name[0][value]']);
$this->assertSession()->pageTextContains($edit['description[0][value]']);
// Did this page request display a 'term-listing-heading'?
$this->assertSession()->elementExists('xpath', '//div[@class="views-element-container"]/div/header/div/div/p');
// Check that it does NOT show a description when description is blank.
$term->setDescription(NULL);
$term->save();
$this->drupalGet('taxonomy/term/' . $term->id());
$this->assertSession()->elementNotExists('xpath', '//div[@class="views-element-container"]/div/header/div/div/p');
// Check that the description value is processed.
$value = $this->randomMachineName();
$term->setDescription($value);
$term->save();
$this->assertSame("<p>{$value}</p>\n", (string) $term->description->processed);
// Check that the term feed page is working.
$this->drupalGet('taxonomy/term/' . $term->id() . '/feed');
// Delete the term.
$this->drupalGet('taxonomy/term/' . $term->id() . '/edit');
$this->clickLink('Delete');
$this->submitForm([], 'Delete');
// Assert that the term no longer exists.
$this->drupalGet('taxonomy/term/' . $term->id());
$this->assertSession()->statusCodeEquals(404);
// Test "save and go to list" action while creating term.
// Create the term to edit.
$this->drupalGet('admin/structure/taxonomy/manage/' . $this->vocabulary->id() . '/add');
$edit = [
'name[0][value]' => $this->randomMachineName(12),
'description[0][value]' => $this->randomMachineName(100),
];
// Create the term to edit.
$this->submitForm($edit, 'Save and go to list');
$this->assertSession()->statusCodeEquals(200);
$this->assertSession()->addressEquals('admin/structure/taxonomy/manage/' . $this->vocabulary->id() . '/overview');
$this->assertSession()->pageTextContains($edit['name[0][value]']);
// Validate that "Save and go to list" doesn't exist when destination
// parameter is present.
$this->drupalGet('admin/structure/taxonomy/manage/' . $this->vocabulary->id() . '/add', ['query' => ['destination' => 'node/add']]);
$this->assertSession()->pageTextNotContains('Save and go to list');
// Validate that "Save and go to list" doesn't exist when missing permission
// 'access taxonomy overview'.
$this->drupalLogin($this->drupalCreateUser([
'administer taxonomy',
'bypass node access',
]));
$this->drupalGet('admin/structure/taxonomy/manage/' . $this->vocabulary->id() . '/add');
$this->assertSession()->pageTextNotContains('Save and go to list');
}
/**
* Test UI with override_selector TRUE.
*/
public function testTermSaveOverrideSelector(): void {
$this->config('taxonomy.settings')->set('override_selector', TRUE)->save();
// Create a Term.
$edit = [
'name[0][value]' => $this->randomMachineName(12),
'description[0][value]' => $this->randomMachineName(100),
];
// Create the term to edit.
$this->drupalGet('admin/structure/taxonomy/manage/' . $this->vocabulary->id() . '/add');
$this->submitForm($edit, 'Save');
$terms = \Drupal::entityTypeManager()->getStorage('taxonomy_term')->loadByProperties([
'name' => $edit['name[0][value]'],
]);
$term = reset($terms);
$this->assertNotNull($term, 'Term found in database.');
// The term appears on the vocab list page.
$this->drupalGet('admin/structure/taxonomy/manage/' . $this->vocabulary->id() . '/overview');
$this->assertSession()->pageTextContains($term->getName());
}
/**
* Save, edit and delete a term using the user interface.
*/
public function testTermReorder(): void {
$assert = $this->assertSession();
$this->createTerm($this->vocabulary);
$this->createTerm($this->vocabulary);
$this->createTerm($this->vocabulary);
$taxonomy_storage = $this->container->get('entity_type.manager')->getStorage('taxonomy_term');
// Fetch the created terms in the default alphabetical order, i.e. term1
// precedes term2 alphabetically, and term2 precedes term3.
$taxonomy_storage->resetCache();
[$term1, $term2, $term3] = $taxonomy_storage->loadTree($this->vocabulary->id(), 0, NULL, TRUE);
$this->drupalGet('admin/structure/taxonomy/manage/' . $this->vocabulary->id() . '/overview');
// Each term has four hidden fields, "tid:1:0[tid]", "tid:1:0[parent]",
// "tid:1:0[depth]", and "tid:1:0[weight]". Change the order to term2,
// term3, term1 by setting weight property, make term3 a child of term2 by
// setting the parent and depth properties, and update all hidden fields.
$hidden_edit = [
'terms[tid:' . $term2->id() . ':0][term][tid]' => $term2->id(),
'terms[tid:' . $term2->id() . ':0][term][parent]' => 0,
'terms[tid:' . $term2->id() . ':0][term][depth]' => 0,
'terms[tid:' . $term3->id() . ':0][term][tid]' => $term3->id(),
'terms[tid:' . $term3->id() . ':0][term][parent]' => $term2->id(),
'terms[tid:' . $term3->id() . ':0][term][depth]' => 1,
'terms[tid:' . $term1->id() . ':0][term][tid]' => $term1->id(),
'terms[tid:' . $term1->id() . ':0][term][parent]' => 0,
'terms[tid:' . $term1->id() . ':0][term][depth]' => 0,
];
// Because we can't post hidden form elements, we have to change them in
// code here, and then submit.
foreach ($hidden_edit as $field => $value) {
$node = $assert->hiddenFieldExists($field);
$node->setValue($value);
}
// Edit non-hidden elements within submitForm().
$edit = [
'terms[tid:' . $term2->id() . ':0][weight]' => 0,
'terms[tid:' . $term3->id() . ':0][weight]' => 1,
'terms[tid:' . $term1->id() . ':0][weight]' => 2,
];
$this->submitForm($edit, 'Save');
$taxonomy_storage->resetCache();
$terms = $taxonomy_storage->loadTree($this->vocabulary->id());
$this->assertEquals($term2->id(), $terms[0]->tid, 'Term 2 was moved above term 1.');
$this->assertEquals([$term2->id()], $terms[1]->parents, 'Term 3 was made a child of term 2.');
$this->assertEquals($term1->id(), $terms[2]->tid, 'Term 1 was moved below term 2.');
$this->drupalGet('admin/structure/taxonomy/manage/' . $this->vocabulary->id() . '/overview');
$this->submitForm([], 'Reset to alphabetical');
// Submit confirmation form.
$this->submitForm([], 'Reset to alphabetical');
// Ensure form redirected back to overview.
$this->assertSession()->addressEquals('admin/structure/taxonomy/manage/' . $this->vocabulary->id() . '/overview');
$taxonomy_storage->resetCache();
$terms = $taxonomy_storage->loadTree($this->vocabulary->id(), 0, NULL, TRUE);
$this->assertEquals($term1->id(), $terms[0]->id(), 'Term 1 was moved to back above term 2.');
$this->assertEquals($term2->id(), $terms[1]->id(), 'Term 2 was moved to back below term 1.');
$this->assertEquals($term3->id(), $terms[2]->id(), 'Term 3 is still below term 2.');
$this->assertEquals([$term2->id()], $terms[2]->parents, 'Term 3 is still a child of term 2.');
}
/**
* Tests saving a term with multiple parents through the UI.
*/
public function testTermMultipleParentsInterface(): void {
// Add two new terms to the vocabulary so that we can have multiple parents.
// These will be terms with tids of 1 and 2 respectively.
$this->createTerm($this->vocabulary);
$this->createTerm($this->vocabulary);
// Add a new term with multiple parents.
$edit = [
'name[0][value]' => $this->randomMachineName(12),
'description[0][value]' => $this->randomMachineName(100),
'parent[]' => [0, 1],
];
// Save the new term.
$this->drupalGet('admin/structure/taxonomy/manage/' . $this->vocabulary->id() . '/add');
$this->submitForm($edit, 'Save');
// Check that the term was successfully created.
$term = $this->reloadTermByName($edit['name[0][value]']);
$this->assertNotNull($term, 'Term found in database.');
$this->assertEquals($edit['name[0][value]'], $term->getName(), 'Term name was successfully saved.');
$this->assertEquals($edit['description[0][value]'], $term->getDescription(), 'Term description was successfully saved.');
// Check that we have the expected parents.
$this->assertEquals([0, 1], $this->getParentTids($term), 'Term parents (root plus one) were successfully saved.');
// Load the edit form and save again to ensure parent are preserved.
// Generate a new name, so we know that the term really is saved.
$this->drupalGet('taxonomy/term/' . $term->id() . '/edit');
$edit = [
'name[0][value]' => $this->randomMachineName(12),
];
$this->submitForm($edit, 'Save');
$this->assertSession()->pageTextContains('Updated term ' . $edit['name[0][value]']);
$this->drupalGet('taxonomy/term/' . $term->id() . '/edit');
$this->submitForm([], 'Save');
$this->assertSession()->pageTextContains('Updated term ' . $edit['name[0][value]']);
// Check that we still have the expected parents.
$term = $this->reloadTermByName($edit['name[0][value]']);
$this->assertEquals([0, 1], $this->getParentTids($term), 'Term parents (root plus one) were successfully saved again.');
// Save with two real parents. i.e., not including <root>.
$this->drupalGet('taxonomy/term/' . $term->id() . '/edit');
$edit = [
'name[0][value]' => $this->randomMachineName(12),
'parent[]' => [1, 2],
];
$this->submitForm($edit, 'Save');
$this->assertSession()->pageTextContains('Updated term ' . $edit['name[0][value]']);
$this->drupalGet('taxonomy/term/' . $term->id() . '/edit');
$this->submitForm([], 'Save');
$this->assertSession()->pageTextContains('Updated term ' . $edit['name[0][value]']);
// Check that we have the expected parents.
$term = $this->reloadTermByName($edit['name[0][value]']);
$this->assertEquals([1, 2], $this->getParentTids($term), 'Term parents (two real) were successfully saved.');
}
/**
* Tests destination after saving terms.
*/
public function testRedirects(): void {
// Save a new term.
$addUrl = Url::fromRoute('entity.taxonomy_term.add_form', ['taxonomy_vocabulary' => $this->vocabulary->id()]);
$this->drupalGet($addUrl);
$this->submitForm([
'name[0][value]' => $this->randomMachineName(),
], 'Save');
// Adding a term reloads the form.
$this->assertSession()->addressEquals($addUrl->toString());
$this->assertSession()->pageTextContains('Created new term');
// Update a term.
$term = Term::create(['vid' => $this->vocabulary->id(), 'name' => $this->randomMachineName()]);
$term->save();
$this->drupalGet($term->toUrl('edit-form'));
$this->submitForm(edit: [], submit: 'Save');
// Updating a term sends user to view the term.
$this->assertSession()->addressEquals($term->toUrl()->setAbsolute());
$this->assertSession()->pageTextContains('Updated term');
// Unless the term is not accessible to the user.
// Label triggers forbidden in taxonomy_test_entity_access().
$term = Term::create(['vid' => $this->vocabulary->id(), 'name' => 'Inaccessible view']);
$term->save();
$this->drupalGet($term->toUrl('edit-form'));
$this->submitForm(edit: [], submit: 'Save');
// In which case, the edit form is reloaded.
$this->assertSession()->addressEquals($term->toUrl('edit-form')->setAbsolute());
$this->assertSession()->pageTextContains('Updated term');
}
/**
* Reloads a term by name.
*
* @param string $name
* The name of the term.
*
* @return \Drupal\taxonomy\TermInterface
* The reloaded term.
*/
private function reloadTermByName(string $name): TermInterface {
\Drupal::entityTypeManager()->getStorage('taxonomy_term')->resetCache();
/** @var \Drupal\taxonomy\TermInterface[] $terms */
$terms = \Drupal::entityTypeManager()->getStorage('taxonomy_term')->loadByProperties(['name' => $name]);
return reset($terms);
}
/**
* Get the parent tids for a term including root.
*
* @param \Drupal\taxonomy\TermInterface $term
* The term.
*
* @return array
* A sorted array of tids and 0 if the root is a parent.
*/
private function getParentTids($term): array {
$parent_tids = [];
foreach ($term->get('parent') as $item) {
$parent_tids[] = (int) $item->target_id;
}
sort($parent_tids);
return $parent_tids;
}
/**
* Tests that editing and saving a node with no changes works correctly.
*/
public function testReSavingTags(): void {
// Enable tags in the vocabulary.
$field = $this->field;
\Drupal::service('entity_display.repository')
->getFormDisplay($field->getTargetEntityTypeId(), $field->getTargetBundle())
->setComponent($field->getName(), [
'type' => 'entity_reference_autocomplete_tags',
])
->save();
// Create a term and a node using it.
$term = $this->createTerm($this->vocabulary);
$edit = [];
$edit['title[0][value]'] = $this->randomMachineName(8);
$edit['body[0][value]'] = $this->randomMachineName(16);
$edit[$this->field->getName() . '[target_id]'] = $term->getName();
$this->drupalGet('node/add/article');
$this->submitForm($edit, 'Save');
// Check that the term is displayed when editing and saving the node with no
// changes.
$this->clickLink('Edit');
$this->assertSession()->responseContains($term->getName());
$this->submitForm([], 'Save');
$this->assertSession()->responseContains($term->getName());
}
/**
* Check the breadcrumb on edit and delete a term page.
*/
public function testTermBreadcrumbs(): void {
$edit = [
'name[0][value]' => $this->randomMachineName(14),
'description[0][value]' => $this->randomMachineName(100),
'parent[]' => [0],
];
// Create the term.
$this->drupalGet('admin/structure/taxonomy/manage/' . $this->vocabulary->id() . '/add');
$this->submitForm($edit, 'Save');
$terms = \Drupal::entityTypeManager()->getStorage('taxonomy_term')->loadByProperties([
'name' => $edit['name[0][value]'],
]);
$term = reset($terms);
$this->assertNotNull($term, 'Term found in database.');
// Check the breadcrumb on the term edit page.
$trail = [
'' => 'Home',
'taxonomy/term/' . $term->id() => $term->label(),
];
$this->assertBreadcrumb('taxonomy/term/' . $term->id() . '/edit', $trail);
$this->assertSession()->assertEscaped($term->label());
// Check the breadcrumb on the term delete page.
$trail = [
'' => 'Home',
'taxonomy/term/' . $term->id() => $term->label(),
];
$this->assertBreadcrumb('taxonomy/term/' . $term->id() . '/delete', $trail);
$this->assertSession()->assertEscaped($term->label());
}
}

View File

@ -0,0 +1,115 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\taxonomy\Functional;
use Drupal\node\Entity\Node;
/**
* Tests the translation of taxonomy terms field on nodes.
*
* @group taxonomy
*/
class TermTranslationFieldViewTest extends TaxonomyTestBase {
use TaxonomyTranslationTestTrait;
/**
* The term that should be translated.
*
* @var \Drupal\taxonomy\Entity\Term
*/
protected $term;
/**
* The tag in the source language.
*
* @var string
*/
protected $baseTagName = 'OriginalTagName';
/**
* The translated value for the tag.
*
* @var string
*/
protected $translatedTagName = 'TranslatedTagName';
/**
* {@inheritdoc}
*/
protected static $modules = ['language', 'content_translation', 'taxonomy'];
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
$this->setupLanguages();
$this->vocabulary = $this->createVocabulary();
$this->enableTranslation();
$this->setUpTerm();
$this->setUpTermReferenceField();
$this->setUpNode();
}
/**
* Tests if the translated taxonomy term is displayed.
*/
public function testTranslatedTaxonomyTermReferenceDisplay(): void {
$path = 'node/' . $this->node->id();
$translation_path = $this->translateToLangcode . '/' . $path;
$this->drupalGet($path);
$this->assertSession()->pageTextNotContains($this->translatedTagName);
$this->assertSession()->pageTextContains($this->baseTagName);
$this->drupalGet($translation_path);
$this->assertSession()->pageTextContains($this->translatedTagName);
$this->assertSession()->pageTextNotContains($this->baseTagName);
}
/**
* Creates a test subject node, with translation.
*/
protected function setUpNode(): void {
/** @var \Drupal\node\Entity\Node $node */
$node = Node::create([
'title' => $this->randomMachineName(),
'type' => 'article',
'description' => [
[
'value' => $this->randomMachineName(),
'format' => 'basic_html',
],
],
$this->termFieldName => [['target_id' => $this->term->id()]],
'langcode' => $this->baseLangcode,
]);
$node->save();
$node->addTranslation($this->translateToLangcode, $node->toArray());
$node->save();
$this->node = $node;
}
/**
* Creates a test subject term, with translation.
*/
protected function setUpTerm(): void {
$this->term = $this->createTerm($this->vocabulary, [
'name' => $this->baseTagName,
'langcode' => $this->baseLangcode,
]);
$this->term->addTranslation($this->translateToLangcode, [
'name' => $this->translatedTagName,
]);
$this->term->save();
}
}

View File

@ -0,0 +1,157 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\taxonomy\Functional;
use Drupal\Core\Url;
use Drupal\Tests\system\Functional\Menu\AssertBreadcrumbTrait;
/**
* Tests for proper breadcrumb translation.
*
* @group taxonomy
*/
class TermTranslationTest extends TaxonomyTestBase {
use AssertBreadcrumbTrait;
use TaxonomyTranslationTestTrait;
/**
* Term to translated term mapping.
*
* @var array
*/
protected $termTranslationMap = [
'one' => 'translatedOne',
'two' => 'translatedTwo',
'three' => 'translatedThree',
];
/**
* Created terms.
*
* @var \Drupal\taxonomy\Entity\Term[]
*/
protected $terms = [];
/**
* {@inheritdoc}
*/
protected static $modules = ['taxonomy', 'language', 'content_translation'];
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
$this->setupLanguages();
$this->vocabulary = $this->createVocabulary();
$this->enableTranslation();
$this->setUpTerms();
$this->setUpTermReferenceField();
}
/**
* Tests translated breadcrumbs.
*/
public function testTranslatedBreadcrumbs(): void {
// Ensure non-translated breadcrumb is correct.
$breadcrumb = [Url::fromRoute('<front>')->toString() => 'Home'];
foreach ($this->terms as $term) {
$breadcrumb[$term->toUrl()->toString()] = $term->label();
}
// The last item will not be in the breadcrumb.
array_pop($breadcrumb);
// Check the breadcrumb on the leaf term page.
$term = $this->getLeafTerm();
$this->assertBreadcrumb($term->toUrl(), $breadcrumb, $term->label());
$languages = \Drupal::languageManager()->getLanguages();
// Construct the expected translated breadcrumb.
$breadcrumb = [Url::fromRoute('<front>', [], ['language' => $languages[$this->translateToLangcode]])->toString() => 'Home'];
foreach ($this->terms as $term) {
$translated = $term->getTranslation($this->translateToLangcode);
$url = $translated->toUrl('canonical', ['language' => $languages[$this->translateToLangcode]])->toString();
$breadcrumb[$url] = $translated->label();
}
array_pop($breadcrumb);
// Check for the translated breadcrumb on the translated leaf term page.
$term = $this->getLeafTerm();
$translated = $term->getTranslation($this->translateToLangcode);
$this->assertBreadcrumb($translated->toUrl('canonical', ['language' => $languages[$this->translateToLangcode]]), $breadcrumb, $translated->label());
}
/**
* Tests translation of terms are showed in the node.
*/
public function testTermsTranslation(): void {
// Set the display of the term reference field on the article content type
// to "Check boxes/radio buttons".
\Drupal::service('entity_display.repository')
->getFormDisplay('node', 'article')
->setComponent($this->termFieldName, [
'type' => 'options_buttons',
])
->save();
$this->drupalLogin($this->drupalCreateUser(['create article content']));
// Test terms are listed.
$this->drupalGet('node/add/article');
$this->assertSession()->pageTextContains('one');
$this->assertSession()->pageTextContains('two');
$this->assertSession()->pageTextContains('three');
// Test terms translated are listed.
$this->drupalGet('hu/node/add/article');
$this->assertSession()->pageTextContains('translatedOne');
$this->assertSession()->pageTextContains('translatedTwo');
$this->assertSession()->pageTextContains('translatedThree');
}
/**
* Setup translated terms in a hierarchy.
*/
protected function setUpTerms(): void {
$parent_vid = 0;
foreach ($this->termTranslationMap as $name => $translation) {
$term = $this->createTerm($this->vocabulary, [
'name' => $name,
'langcode' => $this->baseLangcode,
'parent' => $parent_vid,
]);
$term->addTranslation($this->translateToLangcode, [
'name' => $translation,
]);
$term->save();
// Each term is nested under the last.
$parent_vid = $term->id();
$this->terms[] = $term;
}
}
/**
* Get the final (leaf) term in the hierarchy.
*
* @return \Drupal\taxonomy\Entity\Term
* The final term in the hierarchy.
*/
protected function getLeafTerm() {
return $this->terms[count($this->termTranslationMap) - 1];
}
}

View File

@ -0,0 +1,218 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\taxonomy\Functional;
use Drupal\Tests\content_translation\Functional\ContentTranslationUITestBase;
use Drupal\Core\Language\LanguageInterface;
use Drupal\taxonomy\Entity\Vocabulary;
/**
* Tests the Term Translation UI.
*
* @group taxonomy
*/
class TermTranslationUITest extends ContentTranslationUITestBase {
/**
* The vocabulary used for creating terms.
*
* @var \Drupal\taxonomy\VocabularyInterface
*/
protected $vocabulary;
/**
* {@inheritdoc}
*/
protected $defaultCacheContexts = [
'languages:language_interface',
'theme',
'url.query_args:_wrapper_format',
'user.permissions',
'url.site',
];
/**
* {@inheritdoc}
*/
protected static $modules = ['language', 'content_translation', 'taxonomy'];
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* {@inheritdoc}
*/
protected function setUp(): void {
$this->entityTypeId = 'taxonomy_term';
$this->bundle = 'tags';
parent::setUp();
$this->doSetup();
}
/**
* {@inheritdoc}
*/
protected function setupBundle(): void {
parent::setupBundle();
// Create a vocabulary.
$this->vocabulary = Vocabulary::create([
'name' => $this->bundle,
'description' => $this->randomMachineName(),
'vid' => $this->bundle,
'langcode' => LanguageInterface::LANGCODE_NOT_SPECIFIED,
'weight' => mt_rand(0, 10),
]);
$this->vocabulary->save();
}
/**
* {@inheritdoc}
*/
protected function getTranslatorPermissions(): array {
return array_merge(parent::getTranslatorPermissions(), ['administer taxonomy']);
}
/**
* {@inheritdoc}
*/
protected function getNewEntityValues($langcode) {
return ['name' => $this->randomMachineName()] + parent::getNewEntityValues($langcode);
}
/**
* Returns an edit array containing the values to be posted.
*/
protected function getEditValues($values, $langcode, $new = FALSE) {
$edit = parent::getEditValues($values, $langcode, $new);
// To be able to post values for the configurable base fields (name,
// description) have to be suffixed with [0][value].
foreach ($edit as $property => $value) {
foreach (['name', 'description'] as $key) {
if ($property == $key) {
$edit[$key . '[0][value]'] = $value;
unset($edit[$property]);
}
}
}
return $edit;
}
/**
* {@inheritdoc}
*/
public function testTranslationUI(): void {
parent::testTranslationUI();
// Make sure that no row was inserted for taxonomy vocabularies which do
// not have translations enabled.
$tids = \Drupal::entityQueryAggregate('taxonomy_term')
->accessCheck(FALSE)
->aggregate('tid', 'COUNT')
->condition('vid', $this->bundle, '<>')
->groupBy('tid')
->execute();
foreach ($tids as $tid) {
$this->assertTrue($tid['tid_count'] < 2, 'Term does have translations.');
}
}
/**
* Tests translate link on vocabulary term list.
*/
public function testTranslateLinkVocabularyAdminPage(): void {
$this->drupalLogin($this->drupalCreateUser(array_merge(parent::getTranslatorPermissions(), ['access administration pages', 'administer taxonomy'])));
$values = [
'name' => $this->randomMachineName(),
];
$translatable_tid = $this->createEntity($values, $this->langcodes[0], $this->vocabulary->id());
// Create an untranslatable vocabulary.
$untranslatable_vocabulary = Vocabulary::create([
'name' => 'untranslatable_voc',
'description' => $this->randomMachineName(),
'vid' => 'untranslatable_voc',
'langcode' => LanguageInterface::LANGCODE_NOT_SPECIFIED,
'weight' => mt_rand(0, 10),
]);
$untranslatable_vocabulary->save();
$values = [
'name' => $this->randomMachineName(),
];
$untranslatable_tid = $this->createEntity($values, $this->langcodes[0], $untranslatable_vocabulary->id());
// Verify translation links.
$this->drupalGet('admin/structure/taxonomy/manage/' . $this->vocabulary->id() . '/overview');
$this->assertSession()->statusCodeEquals(200);
$this->assertSession()->linkByHrefExists('term/' . $translatable_tid . '/translations', 0, 'The translations link exists for a translatable vocabulary.');
$this->assertSession()->linkByHrefExists('term/' . $translatable_tid . '/edit', 0, 'The edit link exists for a translatable vocabulary.');
$this->drupalGet('admin/structure/taxonomy/manage/' . $untranslatable_vocabulary->id() . '/overview');
$this->assertSession()->statusCodeEquals(200);
$this->assertSession()->linkByHrefExists('term/' . $untranslatable_tid . '/edit');
$this->assertSession()->linkByHrefNotExists('term/' . $untranslatable_tid . '/translations');
}
/**
* {@inheritdoc}
*/
protected function doTestTranslationEdit(): void {
$storage = $this->container->get('entity_type.manager')
->getStorage($this->entityTypeId);
$entity = $storage->load($this->entityId);
$languages = $this->container->get('language_manager')->getLanguages();
foreach ($this->langcodes as $langcode) {
// We only want to test the title for non-english translations.
if ($langcode != 'en') {
$options = ['language' => $languages[$langcode]];
$url = $entity->toUrl('edit-form', $options);
$this->drupalGet($url);
$this->assertSession()->pageTextContains("{$entity->getTranslation($langcode)->label()} [{$languages[$langcode]->getName()} translation]");
}
}
}
/**
* {@inheritdoc}
*/
protected function doTestPublishedStatus(): void {
$storage = $this->container->get('entity_type.manager')
->getStorage($this->entityTypeId);
$entity = $storage->load($this->entityId);
$languages = $this->container->get('language_manager')->getLanguages();
$statuses = [
TRUE,
FALSE,
];
foreach ($statuses as $index => $value) {
// (Un)publish the term translations and check that the translation
// statuses are (un)published accordingly.
foreach ($this->langcodes as $langcode) {
$options = ['language' => $languages[$langcode]];
$url = $entity->toUrl('edit-form', $options);
$this->drupalGet($url, $options);
$this->submitForm(['status[value]' => $value], 'Save');
}
$entity = $storage->load($this->entityId);
foreach ($this->langcodes as $langcode) {
// The term is created as unpublished thus we switch to the published
// status first.
$status = !$index;
$translation = $entity->getTranslation($langcode);
$this->assertEquals($status, $this->manager->getTranslationMetadata($translation)->isPublished(), 'The translation has been correctly unpublished.');
}
}
}
}

View File

@ -0,0 +1,68 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\taxonomy\Functional;
/**
* Verifies that various taxonomy pages use the expected theme.
*
* @group taxonomy
*/
class ThemeTest extends TaxonomyTestBase {
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
// Make sure we are using distinct default and administrative themes for
// the duration of these tests.
\Drupal::service('theme_installer')->install(['olivero', 'claro']);
$this->config('system.theme')
->set('default', 'olivero')
->set('admin', 'claro')
->save();
// Create and log in as a user who has permission to add and edit taxonomy
// terms and view the administrative theme.
$admin_user = $this->drupalCreateUser([
'administer taxonomy',
'view the administration theme',
]);
$this->drupalLogin($admin_user);
}
/**
* Tests the theme used when adding, viewing and editing taxonomy terms.
*/
public function testTaxonomyTermThemes(): void {
// Adding a term to a vocabulary is considered an administrative action and
// should use the administrative theme.
$vocabulary = $this->createVocabulary();
$this->drupalGet('admin/structure/taxonomy/manage/' . $vocabulary->id() . '/add');
// Check that the administrative theme's CSS appears on the page for adding
// a taxonomy term.
$this->assertSession()->responseContains('claro/css/base/elements.css');
// Viewing a taxonomy term should use the default theme.
$term = $this->createTerm($vocabulary);
$this->drupalGet('taxonomy/term/' . $term->id());
// Check that the default theme's CSS appears on the page for viewing
// a taxonomy term.
$this->assertSession()->responseContains('olivero/css/base/base.css');
// Editing a taxonomy term should use the same theme as adding one.
$this->drupalGet('taxonomy/term/' . $term->id() . '/edit');
// Check that the administrative theme's CSS appears on the page for editing
// a taxonomy term.
$this->assertSession()->responseContains('claro/css/base/elements.css');
}
}

View File

@ -0,0 +1,35 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\taxonomy\Functional\Views;
/**
* Tests the representative node relationship for terms.
*
* @group taxonomy
*/
class TaxonomyDefaultArgumentTest extends TaxonomyTestBase {
/**
* Views used by this test.
*
* @var array
*/
public static $testViews = ['taxonomy_default_argument_test'];
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* Tests escaping of page title when the taxonomy plugin provides it.
*/
public function testTermTitleEscaping(): void {
$this->term1->setName('<em>Markup</em>')->save();
$this->drupalGet('taxonomy_default_argument_test/' . $this->term1->id());
$this->assertSession()->assertEscaped($this->term1->label());
}
}

View File

@ -0,0 +1,79 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\taxonomy\Functional\Views;
use Drupal\views\Views;
use Drupal\taxonomy\Entity\Vocabulary;
/**
* Tests the "All terms" taxonomy term field handler.
*
* @group taxonomy
*/
class TaxonomyFieldAllTermsTest extends TaxonomyTestBase {
/**
* Views used by this test.
*
* @var array
*/
public static $testViews = ['taxonomy_all_terms_test'];
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* Tests the "all terms" field handler.
*/
public function testViewsHandlerAllTermsField(): void {
$this->term1->setName('<em>Markup</em>')->save();
$view = Views::getView('taxonomy_all_terms_test');
$this->executeView($view);
$this->drupalGet('taxonomy_all_terms_test');
// Test term1 links.
$xpath = '//a[@href="' . $this->term1->toUrl()->toString() . '"]';
$this->assertSession()->elementsCount('xpath', $xpath, 2);
$links = $this->xpath($xpath);
$this->assertEquals($this->term1->label(), $links[0]->getText());
$this->assertEquals($this->term1->label(), $links[1]->getText());
$this->assertSession()->assertEscaped($this->term1->label());
// Test term2 links.
$xpath = '//a[@href="' . $this->term2->toUrl()->toString() . '"]';
$this->assertSession()->elementsCount('xpath', $xpath, 2);
$links = $this->xpath($xpath);
$this->assertEquals($this->term2->label(), $links[0]->getText());
$this->assertEquals($this->term2->label(), $links[1]->getText());
}
/**
* Tests token replacement in the "all terms" field handler.
*/
public function testViewsHandlerAllTermsWithTokens(): void {
$this->drupalGet('taxonomy_all_terms_token_test');
// Term itself: {{ term_node_tid }}.
$this->assertSession()->pageTextContains('Term: ' . $this->term1->getName());
// The taxonomy term ID for the term: {{ term_node_tid__tid }}.
$this->assertSession()->pageTextContains('The taxonomy term ID for the term: ' . $this->term1->id());
// The taxonomy term name for the term: {{ term_node_tid__name }}.
$this->assertSession()->pageTextContains('The taxonomy term name for the term: ' . $this->term1->getName());
// The machine name for the vocabulary the term belongs to:
// {{ term_node_tid__vocabulary_vid }}.
$this->assertSession()->pageTextContains('The machine name for the vocabulary the term belongs to: ' . $this->term1->bundle());
// The name for the vocabulary the term belongs to: {{
// term_node_tid__vocabulary }}.
$vocabulary = Vocabulary::load($this->term1->bundle());
$this->assertSession()->pageTextContains('The name for the vocabulary the term belongs to: ' . $vocabulary->label());
}
}

View File

@ -0,0 +1,203 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\taxonomy\Functional\Views;
use Drupal\Core\Language\LanguageInterface;
use Drupal\field\Entity\FieldConfig;
use Drupal\language\Entity\ConfigurableLanguage;
use Drupal\Tests\views\Functional\ViewTestBase;
use Drupal\views\Tests\ViewTestData;
use Drupal\views\Views;
use Drupal\field\Entity\FieldStorageConfig;
use Drupal\taxonomy\Entity\Vocabulary;
use Drupal\taxonomy\Entity\Term;
/**
* Tests taxonomy field filters with translations.
*
* @group taxonomy
*/
class TaxonomyFieldFilterTest extends ViewTestBase {
/**
* {@inheritdoc}
*/
protected static $modules = [
'language',
'taxonomy',
'taxonomy_test_views',
'text',
'views',
'node',
];
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* Views used by this test.
*
* @var array
*/
public static $testViews = ['test_field_filters'];
/**
* The vocabulary used for creating terms.
*
* @var \Drupal\taxonomy\VocabularyInterface
*/
protected $vocabulary;
/**
* List of taxonomy term names by language.
*
* @var array
*/
public $termNames = [];
/**
* {@inheritdoc}
*/
protected function setUp($import_test_views = TRUE, $modules = []): void {
parent::setUp($import_test_views, $modules);
// Add two new languages.
ConfigurableLanguage::createFromLangcode('fr')->save();
ConfigurableLanguage::createFromLangcode('es')->save();
// Set up term names.
$this->termNames = [
'en' => 'Food in Paris',
'es' => 'Comida en Paris',
'fr' => 'Nourriture en Paris',
];
// Create a vocabulary.
$this->vocabulary = Vocabulary::create([
'name' => 'Views testing tags',
'vid' => 'views_testing_tags',
]);
$this->vocabulary->save();
// Add a translatable field to the vocabulary.
$field = FieldStorageConfig::create([
'field_name' => 'field_foo',
'entity_type' => 'taxonomy_term',
'type' => 'text',
]);
$field->save();
FieldConfig::create([
'field_name' => 'field_foo',
'entity_type' => 'taxonomy_term',
'label' => 'Foo',
'bundle' => 'views_testing_tags',
])->save();
// Create term with translations.
$taxonomy = $this->createTermWithProperties(['name' => $this->termNames['en'], 'langcode' => 'en', 'description' => $this->termNames['en'], 'field_foo' => $this->termNames['en']]);
foreach (['es', 'fr'] as $langcode) {
$translation = $taxonomy->addTranslation($langcode, ['name' => $this->termNames[$langcode]]);
$translation->description->value = $this->termNames[$langcode];
$translation->field_foo->value = $this->termNames[$langcode];
}
$taxonomy->save();
Views::viewsData()->clear();
ViewTestData::createTestViews(static::class, ['taxonomy_test_views']);
}
/**
* Tests description and term name filters.
*/
public function testFilters(): void {
// Test the name filter page, which filters for name contains 'Comida'.
// Should show just the Spanish translation, once.
$this->assertPageCounts('test-name-filter', ['es' => 1, 'fr' => 0, 'en' => 0], 'Comida name filter');
// Test the description filter page, which filters for description contains
// 'Comida'. Should show just the Spanish translation, once.
$this->assertPageCounts('test-desc-filter', ['es' => 1, 'fr' => 0, 'en' => 0], 'Comida description filter');
// Test the field filter page, which filters for field_foo contains
// 'Comida'. Should show just the Spanish translation, once.
$this->assertPageCounts('test-field-filter', ['es' => 1, 'fr' => 0, 'en' => 0], 'Comida field filter');
// Test the name Paris filter page, which filters for name contains
// 'Paris'. Should show each translation once.
$this->assertPageCounts('test-name-paris', ['es' => 1, 'fr' => 1, 'en' => 1], 'Paris name filter');
// Test the description Paris page, which filters for description contains
// 'Paris'. Should show each translation, once.
$this->assertPageCounts('test-desc-paris', ['es' => 1, 'fr' => 1, 'en' => 1], 'Paris description filter');
// Test the field Paris filter page, which filters for field_foo contains
// 'Paris'. Should show each translation once.
$this->assertPageCounts('test-field-paris', ['es' => 1, 'fr' => 1, 'en' => 1], 'Paris field filter');
}
/**
* Asserts that the given taxonomy translation counts are correct.
*
* @param string $path
* Path of the page to test.
* @param array $counts
* Array whose keys are languages, and values are the number of times
* that translation should be shown on the given page.
* @param string $message
* Message suffix to display.
*
* @internal
*/
protected function assertPageCounts(string $path, array $counts, string $message): void {
// Get the text of the page.
$this->drupalGet($path);
$text = $this->getTextContent();
// Check the counts. Note that the title and body are both shown on the
// page, and they are the same. So the title/body string should appear on
// the page twice as many times as the input count.
foreach ($counts as $langcode => $count) {
$this->assertEquals(2 * $count, substr_count($text, $this->termNames[$langcode]), 'Translation ' . $langcode . ' has count ' . $count . ' with ' . $message);
}
}
/**
* Creates a taxonomy term with specified name and other properties.
*
* @param array $properties
* Array of properties and field values to set.
*
* @return \Drupal\taxonomy\TermInterface
* The created taxonomy term.
*/
protected function createTermWithProperties($properties) {
// Use the first available text format.
$filter_formats = filter_formats();
$format = array_pop($filter_formats);
$properties += [
'name' => $this->randomMachineName(),
'description' => $this->randomMachineName(),
'langcode' => LanguageInterface::LANGCODE_NOT_SPECIFIED,
'field_foo' => $this->randomMachineName(),
];
$term = Term::create([
'name' => $properties['name'],
'description' => $properties['description'],
'format' => $format->id(),
'vid' => $this->vocabulary->id(),
'langcode' => $properties['langcode'],
]);
$term->field_foo->value = $properties['field_foo'];
$term->save();
return $term;
}
}

View File

@ -0,0 +1,553 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\taxonomy\Functional\Views;
use Drupal\Tests\field\Traits\EntityReferenceFieldCreationTrait;
use Drupal\taxonomy\Entity\Term;
use Drupal\taxonomy\Entity\Vocabulary;
use Drupal\Tests\views_ui\Functional\UITestBase;
use Drupal\views\Tests\ViewTestData;
use Drupal\views\Entity\View;
/**
* Tests the taxonomy index filter handler UI.
*
* @group taxonomy
* @see \Drupal\taxonomy\Plugin\views\field\TaxonomyIndexTid
*/
class TaxonomyIndexTidUiTest extends UITestBase {
use EntityReferenceFieldCreationTrait;
/**
* Views used by this test.
*
* @var array
*/
public static $testViews = [
'test_filter_taxonomy_index_tid',
'test_taxonomy_term_name',
'test_taxonomy_exposed_grouped_filter',
];
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* {@inheritdoc}
*/
protected static $modules = [
'node',
'taxonomy',
'views',
'views_ui',
'taxonomy_test_views',
];
/**
* A nested array of \Drupal\taxonomy\TermInterface objects.
*
* @var \Drupal\taxonomy\TermInterface[][]
*/
protected $terms = [];
/**
* {@inheritdoc}
*/
protected function setUp($import_test_views = TRUE, $modules = []): void {
parent::setUp($import_test_views, $modules);
$this->adminUser = $this->drupalCreateUser([
'administer taxonomy',
'administer views',
]);
$this->drupalLogin($this->adminUser);
Vocabulary::create([
'vid' => 'tags',
'name' => 'Tags',
])->save();
// Setup a hierarchy which looks like this:
// term 0.0
// term 1.0
// - term 1.1
// term 2.0
// - term 2.1
// - term 2.2
for ($i = 0; $i < 3; $i++) {
for ($j = 0; $j <= $i; $j++) {
$this->terms[$i][$j] = $term = Term::create([
'vid' => 'tags',
'name' => "Term $i.$j",
'parent' => isset($this->terms[$i][0]) ? $this->terms[$i][0]->id() : 0,
]);
$term->save();
}
}
ViewTestData::createTestViews(static::class, ['taxonomy_test_views']);
// Extra taxonomy and terms.
Vocabulary::create([
'vid' => 'other_tags',
'name' => 'Other tags',
])->save();
$this->terms[3][0] = $term = Term::create([
'vid' => 'tags',
'name' => "Term 3.0",
]);
$term->save();
Vocabulary::create([
'vid' => 'empty_vocabulary',
'name' => 'Empty Vocabulary',
])->save();
}
/**
* Tests the filter UI.
*/
public function testFilterUI(): void {
$this->drupalGet('admin/structure/views/nojs/handler/test_filter_taxonomy_index_tid/default/filter/tid');
$result = $this->assertSession()->selectExists('edit-options-value')->findAll('css', 'option');
// Ensure that the expected hierarchy is available in the UI.
$counter = 0;
for ($i = 0; $i < 3; $i++) {
for ($j = 0; $j <= $i; $j++) {
$option = $result[$counter++];
$prefix = $this->terms[$i][$j]->parent->target_id ? '-' : '';
$tid = $option->getAttribute('value');
$this->assertEquals($prefix . $this->terms[$i][$j]->getName(), $option->getText());
$this->assertEquals($this->terms[$i][$j]->id(), $tid);
}
}
// Ensure the autocomplete input element appears when using the 'textfield'
// type.
$view = View::load('test_filter_taxonomy_index_tid');
$display =& $view->getDisplay('default');
$display['display_options']['filters']['tid']['type'] = 'textfield';
$view->save();
$this->drupalGet('admin/structure/views/nojs/handler/test_filter_taxonomy_index_tid/default/filter/tid');
$this->assertSession()->fieldExists('edit-options-value');
// Tests \Drupal\taxonomy\Plugin\views\filter\TaxonomyIndexTid::calculateDependencies().
$expected = [
'config' => [
'taxonomy.vocabulary.tags',
],
'content' => [
'taxonomy_term:tags:' . Term::load(2)->uuid(),
],
'module' => [
'node',
'taxonomy',
'user',
],
];
$this->assertSame($expected, $view->calculateDependencies()->getDependencies());
}
/**
* Tests exposed taxonomy filters.
*/
public function testExposedFilter(): void {
$node_type = $this->drupalCreateContentType(['type' => 'page']);
// Create the tag field itself.
$field_name = 'taxonomy_tags';
$this->createEntityReferenceField('node', $node_type->id(), $field_name, '', 'taxonomy_term');
// Create 4 nodes: 1 without a term, 2 with the same term, and 1 with a
// different term.
$node1 = $this->drupalCreateNode();
$node2 = $this->drupalCreateNode([
$field_name => [['target_id' => $this->terms[1][0]->id()]],
]);
$node3 = $this->drupalCreateNode([
$field_name => [['target_id' => $this->terms[1][0]->id()]],
]);
$node4 = $this->drupalCreateNode([
$field_name => [['target_id' => $this->terms[2][0]->id()]],
]);
// Only the nodes with the selected term should be shown.
$this->drupalGet('test-filter-taxonomy-index-tid');
$this->assertSession()->pageTextNotContains($node1->getTitle());
$this->assertSession()->linkByHrefNotExists($node1->toUrl()->toString());
$xpath_node2_link = $this->assertSession()->buildXPathQuery('//div[@class="views-row"]//a[@href=:url and text()=:label]', [
':url' => $node2->toUrl()->toString(),
':label' => $node2->label(),
]);
$this->assertSession()->elementsCount('xpath', $xpath_node2_link, 1);
$xpath_node3_link = $this->assertSession()->buildXPathQuery('//div[@class="views-row"]//a[@href=:url and text()=:label]', [
':url' => $node3->toUrl()->toString(),
':label' => $node3->label(),
]);
$this->assertSession()->elementsCount('xpath', $xpath_node3_link, 1);
$this->assertSession()->pageTextNotContains($node4->getTitle());
$this->assertSession()->linkByHrefNotExists($node4->toUrl()->toString());
// Expose the filter.
$this->drupalGet('admin/structure/views/nojs/handler/test_filter_taxonomy_index_tid/default/filter/tid');
$this->submitForm([], 'Expose filter');
// Set the operator to 'empty' and remove the default term ID.
$this->submitForm([
'options[operator]' => 'empty',
'options[value][]' => [],
], 'Apply');
// Save the view.
$this->submitForm([], 'Save');
// After switching to 'empty' operator, the node without a term should be
// shown.
$this->drupalGet('test-filter-taxonomy-index-tid');
$xpath_node1_link = $this->assertSession()->buildXPathQuery('//div[@class="views-row"]//a[@href=:url and text()=:label]', [
':url' => $node1->toUrl()->toString(),
':label' => $node1->label(),
]);
$this->assertSession()->elementsCount('xpath', $xpath_node1_link, 1);
$this->assertSession()->pageTextNotContains($node2->getTitle());
$this->assertSession()->linkByHrefNotExists($node2->toUrl()->toString());
$this->assertSession()->pageTextNotContains($node3->getTitle());
$this->assertSession()->linkByHrefNotExists($node3->toUrl()->toString());
$this->assertSession()->pageTextNotContains($node4->getTitle());
$this->assertSession()->linkByHrefNotExists($node4->toUrl()->toString());
// Set the operator to 'not empty'.
$this->drupalGet('admin/structure/views/nojs/handler/test_filter_taxonomy_index_tid/default/filter/tid');
$this->submitForm(['options[operator]' => 'not empty'], 'Apply');
// Save the view.
$this->submitForm([], 'Save');
// After switching to 'not empty' operator, all nodes with terms should be
// shown.
$this->drupalGet('test-filter-taxonomy-index-tid');
$this->assertSession()->pageTextNotContains($node1->getTitle());
$this->assertSession()->linkByHrefNotExists($node1->toUrl()->toString());
$xpath_node2_link = $this->assertSession()->buildXPathQuery('//div[@class="views-row"]//a[@href=:url and text()=:label]', [
':url' => $node2->toUrl()->toString(),
':label' => $node2->label(),
]);
$this->assertSession()->elementsCount('xpath', $xpath_node2_link, 1);
$xpath_node3_link = $this->assertSession()->buildXPathQuery('//div[@class="views-row"]//a[@href=:url and text()=:label]', [
':url' => $node3->toUrl()->toString(),
':label' => $node3->label(),
]);
$this->assertSession()->elementsCount('xpath', $xpath_node3_link, 1);
$xpath_node4_link = $this->assertSession()->buildXPathQuery('//div[@class="views-row"]//a[@href=:url and text()=:label]', [
':url' => $node4->toUrl()->toString(),
':label' => $node4->label(),
]);
$this->assertSession()->elementsCount('xpath', $xpath_node4_link, 1);
// Select 'Term ID' as the field to be displayed.
$edit = ['name[taxonomy_term_field_data.tid]' => TRUE];
$this->drupalGet('admin/structure/views/nojs/add-handler/test_taxonomy_term_name/default/field');
$this->submitForm($edit, 'Add and configure fields');
// Select 'Term' and 'Vocabulary' as filters.
$edit = [
'name[taxonomy_term_field_data.tid]' => TRUE,
'name[taxonomy_term_field_data.vid]' => TRUE,
];
$this->drupalGet('admin/structure/views/nojs/add-handler/test_taxonomy_term_name/default/filter');
$this->submitForm($edit, 'Add and configure filter criteria');
// Select 'Empty Vocabulary' and 'Autocomplete' from the list of options.
$this->drupalGet('admin/structure/views/nojs/handler-extra/test_taxonomy_term_name/default/filter/tid');
$this->submitForm([], 'Apply and continue');
// Expose the filter.
$edit = ['options[expose_button][checkbox][checkbox]' => TRUE];
$this->drupalGet('admin/structure/views/nojs/handler/test_taxonomy_term_name/default/filter/tid');
$this->submitForm($edit, 'Expose filter');
$this->drupalGet('admin/structure/views/nojs/handler/test_taxonomy_term_name/default/filter/tid');
$this->submitForm($edit, 'Apply');
// Filter 'Taxonomy terms' belonging to 'Empty Vocabulary'.
$edit = ['options[value][empty_vocabulary]' => TRUE];
$this->drupalGet('admin/structure/views/nojs/handler/test_taxonomy_term_name/default/filter/vid');
$this->submitForm($edit, 'Apply');
$this->drupalGet('admin/structure/views/view/test_taxonomy_term_name/edit/default');
$this->submitForm([], 'Save');
$this->submitForm([], 'Update preview');
$this->assertSession()->pageTextNotContains($node1->getTitle());
$this->assertSession()->linkByHrefNotExists($node1->toUrl()->toString());
$this->assertSession()->pageTextNotContains($node2->getTitle());
$this->assertSession()->linkByHrefNotExists($node2->toUrl()->toString());
$this->assertSession()->pageTextNotContains($node3->getTitle());
$this->assertSession()->linkByHrefNotExists($node3->toUrl()->toString());
$this->assertSession()->pageTextNotContains($node4->getTitle());
$this->assertSession()->linkByHrefNotExists($node4->toUrl()->toString());
$this->assertSession()->elementNotExists('xpath', "//div[@class='views-row']");
}
/**
* Tests exposed grouped taxonomy filters.
*/
public function testExposedGroupedFilter(): void {
// Create a content type with a taxonomy field.
$this->drupalCreateContentType(['type' => 'article']);
$field_name = 'field_views_testing_tags';
$this->createEntityReferenceField('node', 'article', $field_name, '', 'taxonomy_term');
$nodes = [];
for ($i = 0; $i < 3; $i++) {
$node = [];
$node['type'] = 'article';
$node['field_views_testing_tags'][0]['target_id'] = $this->terms[$i][0]->id();
$nodes[] = $this->drupalCreateNode($node);
}
$this->drupalGet('/admin/structure/views/nojs/handler/test_taxonomy_exposed_grouped_filter/page_1/filter/field_views_testing_tags_target_id');
$edit = [
'options[group_info][group_items][1][value][]' => [$this->terms[0][0]->id(), $this->terms[1][0]->id()],
'options[group_info][group_items][2][value][]' => [$this->terms[1][0]->id(), $this->terms[2][0]->id()],
'options[group_info][group_items][3][value][]' => [$this->terms[2][0]->id(), $this->terms[0][0]->id()],
];
$this->submitForm($edit, 'Apply');
$this->submitForm([], 'Save');
// Visit the view's page URL and validate the results.
$this->drupalGet('/test-taxonomy-exposed-grouped-filter');
$this->submitForm(['field_views_testing_tags_target_id' => 1], 'Apply');
$this->assertSession()->pageTextContains($nodes[0]->getTitle());
$this->assertSession()->pageTextContains($nodes[1]->getTitle());
$this->assertSession()->pageTextNotContains($nodes[2]->getTitle());
$this->submitForm(['field_views_testing_tags_target_id' => 2], 'Apply');
$this->assertSession()->pageTextContains($nodes[1]->getTitle());
$this->assertSession()->pageTextContains($nodes[2]->getTitle());
$this->assertSession()->pageTextNotContains($nodes[0]->getTitle());
$this->submitForm(['field_views_testing_tags_target_id' => 3], 'Apply');
$this->assertSession()->pageTextContains($nodes[0]->getTitle());
$this->assertSession()->pageTextContains($nodes[2]->getTitle());
$this->assertSession()->pageTextNotContains($nodes[1]->getTitle());
}
/**
* Tests that an exposed taxonomy filter doesn't show unpublished terms.
*/
public function testExposedUnpublishedFilterOptions(): void {
$this->terms[1][0]->setUnpublished()->save();
// Expose the filter.
$this->drupalGet('admin/structure/views/nojs/handler/test_filter_taxonomy_index_tid/default/filter/tid');
$this->submitForm([], 'Expose filter');
$edit = ['options[expose_button][checkbox][checkbox]' => TRUE];
$this->submitForm($edit, 'Apply');
$this->submitForm([], 'Save');
// Make sure the unpublished term is shown to the admin user.
$this->drupalGet('test-filter-taxonomy-index-tid');
$this->assertNotEmpty($this->cssSelect('option[value="' . $this->terms[0][0]->id() . '"]'));
$this->assertNotEmpty($this->cssSelect('option[value="' . $this->terms[1][0]->id() . '"]'));
$this->drupalLogout();
$this->drupalGet('test-filter-taxonomy-index-tid');
// Make sure the unpublished term isn't shown to the anonymous user.
$this->assertNotEmpty($this->cssSelect('option[value="' . $this->terms[0][0]->id() . '"]'));
$this->assertEmpty($this->cssSelect('option[value="' . $this->terms[1][0]->id() . '"]'));
// Tests that the term also isn't shown when not showing hierarchy.
$this->drupalLogin($this->adminUser);
$edit = [
'options[hierarchy]' => FALSE,
];
$this->drupalGet('admin/structure/views/nojs/handler-extra/test_filter_taxonomy_index_tid/default/filter/tid');
$this->submitForm($edit, 'Apply');
$this->submitForm([], 'Save');
$this->drupalGet('test-filter-taxonomy-index-tid');
$this->assertNotEmpty($this->cssSelect('option[value="' . $this->terms[0][0]->id() . '"]'));
$this->assertNotEmpty($this->cssSelect('option[value="' . $this->terms[1][0]->id() . '"]'));
$this->drupalLogout();
$this->drupalGet('test-filter-taxonomy-index-tid');
// Make sure the unpublished term isn't shown to the anonymous user.
$this->assertNotEmpty($this->cssSelect('option[value="' . $this->terms[0][0]->id() . '"]'));
$this->assertEmpty($this->cssSelect('option[value="' . $this->terms[1][0]->id() . '"]'));
}
/**
* Tests using the TaxonomyIndexTid in a filter group.
*/
public function testFilterGrouping(): void {
$node_type = $this->drupalCreateContentType(['type' => 'page']);
// Create the tag field itself.
$field_name = 'taxonomy_tags';
$this->createEntityReferenceField('node', $node_type->id(), $field_name, '', 'taxonomy_term');
// Create the other tag field itself.
$field_name2 = 'taxonomy_other_tags';
$this->createEntityReferenceField('node', $node_type->id(), $field_name2, '', 'taxonomy_term');
// Create 5 nodes: 1 node without any tagging, 2 nodes tagged with 1 term,
// 1 node with 2 tagged terms and 1 with other tags term.
$node_no_term = $this->drupalCreateNode();
$node_with_term_1_0 = $this->drupalCreateNode([
$field_name => [['target_id' => $this->terms[1][0]->id()]],
]);
$node_with_terms_1_0_and_1_1 = $this->drupalCreateNode([
$field_name => [
['target_id' => $this->terms[1][0]->id()],
['target_id' => $this->terms[1][1]->id()],
],
]);
$node_with_term_2_0 = $this->drupalCreateNode([
$field_name => [['target_id' => $this->terms[2][0]->id()]],
]);
$node_with_term_3_0 = $this->drupalCreateNode([
$field_name2 => [['target_id' => $this->terms[3][0]->id()]],
]);
// Create two groups. The first group contains the published filter and set
// up the second group as an 'OR' group. The first subgroup of the second
// filter group will vary as follows:
// - multiple values vs single value
// - not vs or operator values
$view = View::load('test_filter_taxonomy_index_tid');
$display =& $view->getDisplay('default');
// Case 1:
// - filter "tid" with multiple terms as "is none of"
// - filter "tid_2" with a single term as "is one of"
$display['display_options']['filters']['tid']['value'][0] = $this->terms[1][0]->id();
$display['display_options']['filters']['tid']['value'][1] = $this->terms[1][1]->id();
$display['display_options']['filters']['tid']['operator'] = 'not';
$display['display_options']['filters']['tid']['group'] = 2;
$display['display_options']['filters']['tid_2'] = $display['display_options']['filters']['tid'];
$display['display_options']['filters']['tid_2']['id'] = 'tid_2';
$display['display_options']['filters']['tid_2']['value'][0] = $this->terms[2][0]->id();
$display['display_options']['filters']['tid_2']['operator'] = 'or';
$display['display_options']['filters']['tid_2']['group'] = 2;
$display['display_options']['filter_groups'] = [
'operator' => 'AND',
'groups' => [
1 => 'AND',
2 => 'OR',
],
];
$view->save();
$this->drupalGet('test-filter-taxonomy-index-tid');
// We expect no nodes tagged with term 1.0 or 1.1. The node tagged with
// term 2.0 and the untagged node will be shown.
$this->assertSession()->pageTextNotContains($node_with_term_1_0->label());
$this->assertSession()->pageTextNotContains($node_with_terms_1_0_and_1_1->label());
$this->assertSession()->pageTextContainsOnce($node_with_term_2_0->label());
$this->assertSession()->pageTextContainsOnce($node_no_term->label());
// Case 2:
// - filter "tid" with multiple terms as "is one of"
// - filter "tid_2" with a single term as "is one of"
$view = View::load('test_filter_taxonomy_index_tid');
$display =& $view->getDisplay('default');
$display['display_options']['filters']['tid']['value'][0] = $this->terms[1][0]->id();
$display['display_options']['filters']['tid']['value'][1] = $this->terms[1][1]->id();
$display['display_options']['filters']['tid']['operator'] = 'or';
$display['display_options']['filters']['tid']['group'] = 2;
$display['display_options']['filters']['tid_2'] = $display['display_options']['filters']['tid'];
$display['display_options']['filters']['tid_2']['id'] = 'tid_2';
$display['display_options']['filters']['tid_2']['value'][0] = $this->terms[2][0]->id();
$display['display_options']['filters']['tid_2']['operator'] = 'or';
$display['display_options']['filters']['tid_2']['group'] = 2;
$view->save();
$this->drupalGet('test-filter-taxonomy-index-tid');
// We expect all the tagged nodes but not the untagged node.
$this->assertSession()->pageTextContainsOnce($node_with_term_1_0->label());
// The view does not have DISTINCT query enabled, the node tagged with
// both 1.0 and 1.1 will appear twice.
$this->assertSession()->pageTextMatchesCount(2, "/{$node_with_terms_1_0_and_1_1->label()}/");
$this->assertSession()->pageTextContainsOnce($node_with_term_2_0->label());
$this->assertSession()->pageTextNotContains($node_no_term->label());
// Case 3:
// - filter "tid" with a single term as "is none of"
// - filter "tid_2" with a single term as "is one of"
$view = View::load('test_filter_taxonomy_index_tid');
$display =& $view->getDisplay('default');
$display['display_options']['filters']['tid']['value'] = [];
$display['display_options']['filters']['tid']['value'][0] = $this->terms[1][0]->id();
$display['display_options']['filters']['tid']['operator'] = 'not';
$display['display_options']['filters']['tid']['group'] = 2;
$display['display_options']['filters']['tid_2'] = $display['display_options']['filters']['tid'];
$display['display_options']['filters']['tid_2']['id'] = 'tid_2';
$display['display_options']['filters']['tid_2']['value'][0] = $this->terms[2][0]->id();
$display['display_options']['filters']['tid_2']['operator'] = 'or';
$display['display_options']['filters']['tid_2']['group'] = 2;
$view->save();
$this->drupalGet('test-filter-taxonomy-index-tid');
// We expect none of the nodes tagged with term 1.0. The node tagged with
// term 2.0 and the untagged node should be shown.
$this->assertSession()->pageTextNotContains($node_with_term_1_0->label());
$this->assertSession()->pageTextNotContains($node_with_terms_1_0_and_1_1->label());
$this->assertSession()->pageTextContainsOnce($node_with_term_2_0->label());
$this->assertSession()->pageTextContainsOnce($node_no_term->label());
// Case 4:
// - filter "tid" with a single term as "is one of"
// - filter "tid_2" with a single term as "is one of"
$view = View::load('test_filter_taxonomy_index_tid');
$display =& $view->getDisplay('default');
$display['display_options']['filters']['tid']['value'] = [];
$display['display_options']['filters']['tid']['value'][0] = $this->terms[1][0]->id();
$display['display_options']['filters']['tid']['operator'] = 'or';
$display['display_options']['filters']['tid']['group'] = 2;
$display['display_options']['filters']['tid_2'] = $display['display_options']['filters']['tid'];
$display['display_options']['filters']['tid_2']['id'] = 'tid_2';
$display['display_options']['filters']['tid_2']['value'][0] = $this->terms[2][0]->id();
$view->save();
$this->drupalGet('test-filter-taxonomy-index-tid');
// We expect all the tagged nodes to be shown but not the untagged node.
$this->assertSession()->pageTextContainsOnce($node_with_term_1_0->label());
$this->assertSession()->pageTextContainsOnce($node_with_terms_1_0_and_1_1->label());
$this->assertSession()->pageTextContainsOnce($node_with_term_2_0->label());
$this->assertSession()->pageTextNotContains($node_no_term->label());
// Different fields/taxonomies filters/values.
// Case 5: OR
// - filter "tid" with terms from tags as "is one of"
// - filter "taxonomy_other_tags_target_id" with term from other tags
// as "is one of".
$view = View::load('test_filter_taxonomy_index_tid');
$display = &$view->getDisplay('default');
$display['display_options']['filters']['tid']['value'][0] = $this->terms[1][0]->id();
$display['display_options']['filters']['tid']['value'][1] = $this->terms[1][1]->id();
$display['display_options']['filters']['tid']['operator'] = 'or';
$display['display_options']['filters']['tid']['group'] = 2;
$display['display_options']['filters']['taxonomy_other_tags_target_id'] = $display['display_options']['filters']['tid'];
$display['display_options']['filters']['taxonomy_other_tags_target_id']['id'] = 'taxonomy_other_tags_target_id';
$display['display_options']['filters']['taxonomy_other_tags_target_id']['value'][0] = $this->terms[3][0]->id();
$display['display_options']['filters']['taxonomy_other_tags_target_id']['operator'] = 'or';
$display['display_options']['filters']['taxonomy_other_tags_target_id']['group'] = 2;
$display['display_options']['filters']['taxonomy_other_tags_target_id']['table'] = 'node__taxonomy_other_tags';
$display['display_options']['filters']['taxonomy_other_tags_target_id']['field'] = 'taxonomy_other_tags_target_id';
unset($display['display_options']['filters']['tid_2']);
$display['display_options']['filter_groups'] = [
'operator' => 'AND',
'groups' => [
1 => 'AND',
2 => 'OR',
],
];
$view->save();
$this->drupalGet('test-filter-taxonomy-index-tid');
// We expect no nodes tagged with term 1.0 or 1.1. The node tagged with
// term 3.0 and the untagged node will be shown.
$this->assertSession()->pageTextContainsOnce($node_with_term_1_0->label());
// The view does not have DISTINCT query enabled, the node tagged with
// both 1.0 and 1.1 will appear twice.
$this->assertSession()->pageTextMatchesCount(2, "/{$node_with_terms_1_0_and_1_1->label()}/");
$this->assertSession()->pageTextContainsOnce($node_with_term_3_0->label());
$this->assertSession()->pageTextNotContains($node_with_term_2_0->label());
$this->assertSession()->pageTextNotContains($node_no_term->label());
}
}

View File

@ -0,0 +1,49 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\taxonomy\Functional\Views;
use Drupal\Tests\views_ui\Functional\UITestBase;
/**
* Tests views taxonomy parent plugin UI.
*
* @group taxonomy
* @see Drupal\taxonomy\Plugin\views\access\Role
*/
class TaxonomyParentUITest extends UITestBase {
/**
* Views used by this test.
*
* @var array
*/
public static $testViews = ['test_taxonomy_parent'];
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* {@inheritdoc}
*/
protected static $modules = ['taxonomy', 'taxonomy_test_views'];
/**
* {@inheritdoc}
*/
protected function setUp($import_test_views = TRUE, $modules = ['taxonomy_test_views']): void {
parent::setUp($import_test_views, $modules);
}
/**
* Tests the taxonomy parent plugin UI.
*/
public function testTaxonomyParentUI(): void {
$this->drupalGet('admin/structure/views/nojs/handler/test_taxonomy_parent/default/relationship/parent');
$this->assertSession()->pageTextNotContains('The handler for this item is broken or missing.');
}
}

View File

@ -0,0 +1,119 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\taxonomy\Functional\Views;
use Drupal\node\NodeInterface;
use Drupal\taxonomy\TermInterface;
use Drupal\views\Views;
/**
* Tests taxonomy relationships with parent term and node.
*
* @group taxonomy
*/
class TaxonomyRelationshipTest extends TaxonomyTestBase {
/**
* Stores the terms used in the tests.
*
* @var \Drupal\taxonomy\TermInterface[]
*/
protected $terms = [];
/**
* Views used by this test.
*
* @var array
*/
public static $testViews = ['test_taxonomy_term_relationship'];
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* {@inheritdoc}
*/
protected function setUp($import_test_views = TRUE, $modules = []): void {
parent::setUp($import_test_views, $modules);
// Make term2 parent of term1.
$this->term1->set('parent', $this->term2->id());
$this->term1->save();
// Store terms in an array for testing.
$this->terms[] = $this->term1;
$this->terms[] = $this->term2;
// Only set term1 on node1 and term2 on node2 for testing.
unset($this->nodes[0]->field_views_testing_tags[1]);
$this->nodes[0]->save();
unset($this->nodes[1]->field_views_testing_tags[0]);
$this->nodes[1]->save();
Views::viewsData()->clear();
}
/**
* Tests the taxonomy parent plugin UI.
*/
public function testTaxonomyRelationships(): void {
// Check the generated views data of taxonomy_index.
$views_data = Views::viewsData()->get('taxonomy_index');
// Check the table join data.
$this->assertEquals('tid', $views_data['table']['join']['taxonomy_term_field_data']['left_field']);
$this->assertEquals('tid', $views_data['table']['join']['taxonomy_term_field_data']['field']);
$this->assertEquals('nid', $views_data['table']['join']['node_field_data']['left_field']);
$this->assertEquals('nid', $views_data['table']['join']['node_field_data']['field']);
$this->assertEquals('entity_id', $views_data['table']['join']['taxonomy_term__parent']['left_field']);
$this->assertEquals('tid', $views_data['table']['join']['taxonomy_term__parent']['field']);
// Check the generated views data of taxonomy_term__parent.
$views_data = Views::viewsData()->get('taxonomy_term__parent');
// Check the table join data.
$this->assertEquals('entity_id', $views_data['table']['join']['taxonomy_term__parent']['left_field']);
$this->assertEquals('parent_target_id', $views_data['table']['join']['taxonomy_term__parent']['field']);
$this->assertEquals('tid', $views_data['table']['join']['taxonomy_term_field_data']['left_field']);
$this->assertEquals('entity_id', $views_data['table']['join']['taxonomy_term_field_data']['field']);
// Check the parent relationship data.
$this->assertEquals('taxonomy_term_field_data', $views_data['parent_target_id']['relationship']['base']);
$this->assertEquals('tid', $views_data['parent_target_id']['relationship']['base field']);
$this->assertEquals('Parent', $views_data['parent_target_id']['relationship']['label']);
$this->assertEquals('standard', $views_data['parent_target_id']['relationship']['id']);
// Check the parent filter and argument data.
$this->assertEquals('numeric', $views_data['parent_target_id']['filter']['id']);
$this->assertEquals('taxonomy', $views_data['parent_target_id']['argument']['id']);
// Check an actual test view.
$view = Views::getView('test_taxonomy_term_relationship');
$this->executeView($view);
/** @var \Drupal\views\ResultRow $row */
foreach ($view->result as $index => $row) {
// Check that the actual ID of the entity is the expected one.
$this->assertEquals($this->terms[$index]->id(), $row->tid);
// Also check that we have the correct result entity.
$this->assertEquals($this->terms[$index]->id(), $row->_entity->id());
$this->assertInstanceOf(TermInterface::class, $row->_entity);
if (!$index) {
$this->assertInstanceOf(TermInterface::class, $row->_relationship_entities['parent']);
$this->assertEquals($this->term2->id(), $row->_relationship_entities['parent']->id());
$this->assertEquals($this->term2->id(), $row->taxonomy_term_field_data_taxonomy_term__parent_tid);
}
$this->assertInstanceOf(NodeInterface::class, $row->_relationship_entities['nid']);
$this->assertEquals($this->nodes[$index]->id(), $row->_relationship_entities['nid']->id());
}
// Test node fields are available through relationship.
\Drupal::service('module_installer')->install(['views_ui']);
$this->drupalLogin($this->createUser(['administer views']));
$this->drupalGet('admin/structure/views/view/test_taxonomy_term_relationship');
$this->click('#views-add-field');
$this->assertSession()->pageTextContains('Body');
}
}

View File

@ -0,0 +1,71 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\taxonomy\Functional\Views;
/**
* Tests the taxonomy term with depth argument.
*
* @group taxonomy
*/
class TaxonomyTermArgumentDepthTest extends TaxonomyTestBase {
/**
* {@inheritdoc}
*/
protected static $modules = [
'taxonomy',
'taxonomy_test_views',
'views',
'node',
];
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* {@inheritdoc}
*/
public static $testViews = ['test_argument_taxonomy_index_tid_depth'];
/**
* @var \Drupal\taxonomy\TermInterface[]
*/
protected $terms = [];
/**
* @var \Drupal\views\ViewExecutable
*/
protected $view;
/**
* {@inheritdoc}
*/
protected function setUp($import_test_views = TRUE, $modules = []): void {
parent::setUp($import_test_views, $modules);
// Create a term with markup in the label.
$first = $this->createTerm(['name' => '<em>First</em>']);
// Create a node w/o any terms.
$settings = ['type' => 'article'];
// Create a node with linked to the term.
$settings['field_views_testing_tags'][0]['target_id'] = $first->id();
$this->nodes[] = $this->drupalCreateNode($settings);
$this->terms[0] = $first;
}
/**
* Tests title escaping.
*/
public function testTermWithDepthArgumentTitleEscaping(): void {
$this->drupalGet('test_argument_taxonomy_index_tid_depth/' . $this->terms[0]->id());
$this->assertSession()->assertEscaped($this->terms[0]->label());
}
}

View File

@ -0,0 +1,169 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\taxonomy\Functional\Views;
use Drupal\Core\Field\FieldStorageDefinitionInterface;
use Drupal\language\Entity\ConfigurableLanguage;
use Drupal\node\Entity\Node;
use Drupal\user\Entity\Role;
use Drupal\user\RoleInterface;
use Drupal\views\Views;
/**
* Tests the taxonomy term view page and its translation.
*
* @group taxonomy
*/
class TaxonomyTermViewTest extends TaxonomyTestBase {
/**
* {@inheritdoc}
*/
protected static $modules = ['taxonomy', 'views'];
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* A user with permissions to administer taxonomy.
*
* @var \Drupal\user\UserInterface
*/
protected $adminUser;
/**
* Name of the taxonomy term reference field.
*
* @var string
*/
protected $fieldName1;
/**
* {@inheritdoc}
*/
protected function setUp($import_test_views = TRUE, $modules = []): void {
parent::setUp($import_test_views, $modules);
// Create an administrative user.
$this->adminUser = $this->drupalCreateUser([
'administer taxonomy',
'bypass node access',
]);
$this->drupalLogin($this->adminUser);
// Create a vocabulary and add two term reference fields to article nodes.
$this->fieldName1 = $this->randomMachineName();
$handler_settings = [
'target_bundles' => [
$this->vocabulary->id() => $this->vocabulary->id(),
],
'auto_create' => TRUE,
];
$this->createEntityReferenceField('node', 'article', $this->fieldName1, '', 'taxonomy_term', 'default', $handler_settings, FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED);
/** @var \Drupal\Core\Entity\EntityDisplayRepositoryInterface $display_repository */
$display_repository = \Drupal::service('entity_display.repository');
$display_repository->getFormDisplay('node', 'article')
->setComponent($this->fieldName1, [
'type' => 'options_select',
])
->save();
$display_repository->getViewDisplay('node', 'article')
->setComponent($this->fieldName1, [
'type' => 'entity_reference_label',
])
->save();
}
/**
* Tests that the taxonomy term view is working properly.
*/
public function testTaxonomyTermView(): void {
// Create terms in the vocabulary.
$term = $this->createTerm();
// Post an article.
$edit = [];
$edit['title[0][value]'] = $original_title = $this->randomMachineName();
$edit['body[0][value]'] = $this->randomMachineName();
$edit["{$this->fieldName1}[]"] = $term->id();
$this->drupalGet('node/add/article');
$this->submitForm($edit, 'Save');
$node = $this->drupalGetNodeByTitle($edit['title[0][value]']);
$this->drupalGet('taxonomy/term/' . $term->id());
$this->assertSession()->pageTextContains($term->label());
$this->assertSession()->pageTextContains($node->label());
\Drupal::service('module_installer')->install(['language', 'content_translation']);
ConfigurableLanguage::createFromLangcode('ur')->save();
// Enable translation for the article content type and ensure the change is
// picked up.
\Drupal::service('content_translation.manager')->setEnabled('node', 'article', TRUE);
$roles = $this->adminUser->getRoles(TRUE);
Role::load(reset($roles))
->grantPermission('create content translations')
->grantPermission('translate any entity')
->save();
$edit['title[0][value]'] = $translated_title = $this->randomMachineName();
$this->drupalGet('node/' . $node->id() . '/translations/add/en/ur');
$this->submitForm($edit, 'Save (this translation)');
$this->drupalGet('taxonomy/term/' . $term->id());
$this->assertSession()->pageTextContains($term->label());
$this->assertSession()->pageTextContains($original_title);
$this->assertSession()->pageTextNotContains($translated_title);
$this->drupalGet('ur/taxonomy/term/' . $term->id());
$this->assertSession()->pageTextContains($term->label());
$this->assertSession()->pageTextNotContains($original_title);
$this->assertSession()->pageTextContains($translated_title);
// Uninstall language module and ensure that the language is not part of the
// query anymore.
// @see \Drupal\views\Plugin\views\filter\LanguageFilter::query()
$node->delete();
// We also have to remove the nodes created by the parent ::setUp() method
// if we want to be able to uninstall the Content Translation module.
foreach (Node::loadMultiple() as $node) {
$node->delete();
}
\Drupal::service('module_installer')->uninstall(['content_translation', 'language']);
$view = Views::getView('taxonomy_term');
$view->initDisplay();
$view->setArguments([$term->id()]);
$view->build();
/** @var \Drupal\Core\Database\Query\Select $query */
$query = $view->build_info['query'];
$tables = $query->getTables();
// Ensure that the join to node_field_data is not added by default.
$this->assertEquals(['node_field_data', 'taxonomy_index'], array_keys($tables));
// Ensure that the filter to the language column is not there by default.
$condition = $query->conditions();
// We only want to check the no. of conditions in the query.
unset($condition['#conjunction']);
$this->assertCount(1, $condition);
// Clear permissions for anonymous users to check access for default views.
Role::load(RoleInterface::ANONYMOUS_ID)->revokePermission('access content')->save();
// Test the default views disclose no data by default.
$this->drupalLogout();
$this->drupalGet('taxonomy/term/' . $term->id());
$this->assertSession()->statusCodeEquals(403);
$this->drupalGet('taxonomy/term/' . $term->id() . '/feed');
$this->assertSession()->statusCodeEquals(403);
}
}

View File

@ -0,0 +1,164 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\taxonomy\Functional\Views;
use Drupal\Core\Field\FieldStorageDefinitionInterface;
use Drupal\Core\Language\LanguageInterface;
use Drupal\Tests\field\Traits\EntityReferenceFieldCreationTrait;
use Drupal\Tests\views\Functional\ViewTestBase;
use Drupal\taxonomy\Entity\Vocabulary;
use Drupal\taxonomy\Entity\Term;
use Drupal\views\Tests\ViewTestData;
/**
* Base class for all taxonomy tests.
*/
abstract class TaxonomyTestBase extends ViewTestBase {
use EntityReferenceFieldCreationTrait;
/**
* {@inheritdoc}
*/
protected static $modules = ['taxonomy', 'taxonomy_test_views'];
/**
* Stores the nodes used for the different tests.
*
* @var \Drupal\node\NodeInterface[]
*/
protected $nodes = [];
/**
* The vocabulary used for creating terms.
*
* @var \Drupal\taxonomy\VocabularyInterface
*/
protected $vocabulary;
/**
* Stores the first term used in the different tests.
*
* @var \Drupal\taxonomy\TermInterface
*/
protected $term1;
/**
* Stores the second term used in the different tests.
*
* @var \Drupal\taxonomy\TermInterface
*/
protected $term2;
/**
* {@inheritdoc}
*/
protected function setUp($import_test_views = TRUE, $modules = []): void {
// Important: taxonomy_test_views module must not be in the $modules to
// avoid an issue that particular view is already exists.
parent::setUp($import_test_views, $modules);
$this->mockStandardInstall();
// This needs to be done again after ::mockStandardInstall() to make
// test vocabularies available.
// Explicitly add taxonomy_test_views to $modules now, so required views are
// being created.
$modules[] = 'taxonomy_test_views';
if ($import_test_views) {
ViewTestData::createTestViews(static::class, $modules);
}
$this->term1 = $this->createTerm();
$this->term2 = $this->createTerm();
$node = [];
$node['type'] = 'article';
$node['field_views_testing_tags'][]['target_id'] = $this->term1->id();
$node['field_views_testing_tags'][]['target_id'] = $this->term2->id();
$this->nodes[] = $this->drupalCreateNode($node);
$this->nodes[] = $this->drupalCreateNode($node);
}
/**
* Provides a workaround for the inability to use the standard profile.
*
* @see https://www.drupal.org/node/1708692
*/
protected function mockStandardInstall() {
$this->drupalCreateContentType([
'type' => 'article',
]);
// Create the vocabulary for the tag field.
$this->vocabulary = Vocabulary::create([
'name' => 'Views testing tags',
'vid' => 'views_testing_tags',
]);
$this->vocabulary->save();
$field_name = 'field_' . $this->vocabulary->id();
$handler_settings = [
'target_bundles' => [
$this->vocabulary->id() => $this->vocabulary->id(),
],
'auto_create' => TRUE,
];
$this->createEntityReferenceField('node', 'article', $field_name, 'Tags', 'taxonomy_term', 'default', $handler_settings, FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED);
/** @var \Drupal\Core\Entity\EntityDisplayRepositoryInterface $display_repository */
$display_repository = \Drupal::service('entity_display.repository');
$display_repository->getFormDisplay('node', 'article')
->setComponent($field_name, [
'type' => 'entity_reference_autocomplete_tags',
'weight' => -4,
])
->save();
$display_repository->getViewDisplay('node', 'article')
->setComponent($field_name, [
'type' => 'entity_reference_label',
'weight' => 10,
])
->save();
$display_repository->getViewDisplay('node', 'article', 'teaser')
->setComponent($field_name, [
'type' => 'entity_reference_label',
'weight' => 10,
])
->save();
}
/**
* Creates and returns a taxonomy term.
*
* @param array $settings
* (optional) An array of values to override the following default
* properties of the term:
* - name: A random string.
* - description: A random string.
* - format: First available text format.
* - vid: Vocabulary ID of self::$vocabulary object.
* - langcode: LANGCODE_NOT_SPECIFIED.
* Defaults to an empty array.
*
* @return \Drupal\taxonomy\Entity\Term
* The created taxonomy term.
*/
protected function createTerm(array $settings = []) {
$filter_formats = filter_formats();
$format = array_pop($filter_formats);
$settings += [
'name' => $this->randomMachineName(),
'description' => $this->randomMachineName(),
// Use the first available text format.
'format' => $format->id(),
'vid' => $this->vocabulary->id(),
'langcode' => LanguageInterface::LANGCODE_NOT_SPECIFIED,
];
$term = Term::create($settings);
$term->save();
return $term;
}
}

View File

@ -0,0 +1,83 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\taxonomy\Functional\Views;
use Drupal\taxonomy\Entity\Vocabulary;
/**
* Tests the vocabulary argument.
*
* @group taxonomy
*/
class TaxonomyVocabularyArgumentTest extends TaxonomyTestBase {
/**
* {@inheritdoc}
*/
protected static $modules = ['taxonomy', 'taxonomy_test_views', 'views'];
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* {@inheritdoc}
*/
public static $testViews = ['test_argument_taxonomy_vocabulary'];
/**
* @var \Drupal\taxonomy\TermInterface[]
*/
protected $terms = [];
/**
* Vocabularies used for creating terms.
*
* @var \Drupal\taxonomy\VocabularyInterface[]
*/
protected $vocabularies;
/**
* {@inheritdoc}
*/
protected function setUp($import_test_views = TRUE, $modules = []): void {
parent::setUp($import_test_views, $modules);
// Add default vocabulary to list of vocabularies.
$this->vocabularies[] = $this->vocabulary;
// Create additional vocabulary.
$vocabulary = Vocabulary::create([
'name' => 'Views testing category',
'vid' => 'views_testing_category',
]);
$vocabulary->save();
$this->vocabularies[] = $vocabulary;
// Create some terms.
$this->terms[0] = $this->createTerm([
'name' => 'First',
'vid' => $this->vocabularies[0]->id(),
]);
$this->terms[1] = $this->createTerm([
'name' => 'Second',
'vid' => $this->vocabularies[1]->id(),
]);
}
/**
* Tests the vocabulary argument handler.
*
* @see Drupal\taxonomy\Plugin\views\argument\VocabularyVid
*/
public function testTermWithVocabularyArgument(): void {
$this->drupalGet('test_argument_taxonomy_vocabulary/' . $this->vocabularies[0]->id());
// First term should be present.
$this->assertSession()->pageTextContains($this->terms[0]->label());
// Second term should not be present.
$this->assertSession()->pageTextNotContains($this->terms[1]->label());
}
}

View File

@ -0,0 +1,95 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\taxonomy\Functional\Views;
/**
* Tests making taxonomy term base fields' displays configurable.
*
* @group taxonomy
*/
class TermDisplayConfigurableTest extends TaxonomyTestBase {
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* Views used by this test.
*
* @var array
*/
public static $testViews = ['test_term_show_entity'];
/**
* Sets base fields to configurable display and check settings are respected.
*/
public function testDisplayConfigurable(): void {
$user = $this->drupalCreateUser(['administer nodes']);
$this->drupalLogin($user);
$assert = $this->assertSession();
// Check the taxonomy_term with default non-configurable display.
$this->drupalGet('test_term_show_entity');
// Name should be linked to entity and description should be displayed.
$assert->pageTextContains($this->term1->getName());
$assert->linkByHrefExists($this->term1->toUrl()->toString());
$assert->pageTextContains($this->term1->getDescription());
$assert->pageTextContains($this->term2->getName());
$assert->linkByHrefExists($this->term2->toUrl()->toString());
$assert->pageTextContains($this->term2->getDescription());
// The field labels should not be present.
$assert->pageTextNotContains('Name');
$assert->pageTextNotContains('Description');
// Enable helper module to make base fields' displays configurable.
\Drupal::service('module_installer')->install(['taxonomy_term_display_configurable_test']);
// Configure display.
$display = \Drupal::service('entity_display.repository')->getViewDisplay('taxonomy_term', $this->vocabulary->id(), 'default');
$display->setComponent('name', [
'type' => 'text_default',
'label' => 'above',
])->save();
// Recheck the taxonomy_term with configurable display.
$this->drupalGet('test_term_show_entity');
// The description should be the first field in each row, with no label.
// Name field should be the second field in view row. Value should be
// displayed after the label. It should not be linked to the term.
$assert->pageTextContains('Name');
$assert->pageTextNotContains('Description');
$assert->pageTextContains($this->term1->getName());
$assert->linkByHrefNotExists($this->term1->toUrl()->toString());
$assert->pageTextContains($this->term1->getDescription());
$assert->elementTextContains('xpath', '//*[@class="views-row"][1]/div/div[1]//p', $this->term1->getDescription());
$assert->elementTextContains('xpath', '//*[@class="views-row"][1]/div/div[2]/div[1]', 'Name');
$assert->elementTextContains('xpath', '//*[@class="views-row"][1]/div/div[2]/div[2]', $this->term1->getName());
$assert->pageTextContains($this->term2->getName());
$assert->linkByHrefNotExists($this->term2->toUrl()->toString());
$assert->pageTextContains($this->term2->getDescription());
$assert->elementTextContains('xpath', '//*[@class="views-row"][2]/div/div[1]//p', $this->term2->getDescription());
$assert->elementTextContains('xpath', '//*[@class="views-row"][2]/div/div[2]/div[1]', 'Name');
$assert->elementTextContains('xpath', '//*[@class="views-row"][2]/div/div[2]/div[2]', $this->term2->getName());
// Remove 'name' field from display.
$display->removeComponent('name')->save();
// Recheck the taxonomy_term with 'name' field removed from display.
// There should just be an unlabelled description. Nothing should be
// linked to the terms.
$this->drupalGet('test_term_show_entity');
$assert->pageTextNotContains('Name');
$assert->pageTextNotContains('Description');
$assert->pageTextNotContains($this->term1->getName());
$assert->linkByHrefNotExists($this->term1->toUrl()->toString());
$assert->pageTextContains($this->term1->getDescription());
$assert->pageTextNotContains($this->term2->getName());
$assert->linkByHrefNotExists($this->term2->toUrl()->toString());
$assert->pageTextContains($this->term2->getDescription());
}
}

View File

@ -0,0 +1,69 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\taxonomy\Functional\Views;
use Drupal\Core\Link;
use Drupal\views\Views;
/**
* Tests the term_name field handler.
*
* @group taxonomy
*
* @see \Drupal\taxonomy\Plugin\views\field\TermName
*/
class TermNameFieldTest extends TaxonomyTestBase {
/**
* {@inheritdoc}
*/
public static $testViews = ['test_taxonomy_term_name'];
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* Tests term name field plugin functionality.
*/
public function testTermNameField(): void {
$this->term1->name->value = $this->randomMachineName() . ' ' . $this->randomMachineName();
$this->term1->save();
$user = $this->drupalCreateUser(['access content']);
$this->drupalLogin($user);
$view = Views::getView('test_taxonomy_term_name');
$view->initDisplay();
$this->executeView($view);
$this->assertEquals($this->term1->getName(), $view->getStyle()->getField(0, 'name'));
$this->assertEquals($this->term2->getName(), $view->getStyle()->getField(1, 'name'));
$view = Views::getView('test_taxonomy_term_name');
$display =& $view->storage->getDisplay('default');
$display['display_options']['fields']['name']['convert_spaces'] = TRUE;
$view->storage->invalidateCaches();
$this->executeView($view);
$this->assertEquals(str_replace(' ', '-', $this->term1->getName()), $view->getStyle()->getField(0, 'name'));
$this->assertEquals($this->term2->getName(), $view->getStyle()->getField(1, 'name'));
// Enable link_to_entity option and ensure that title is displayed properly.
$view = Views::getView('test_taxonomy_term_name');
$display =& $view->storage->getDisplay('default');
$display['display_options']['fields']['name']['convert_spaces'] = TRUE;
$display['display_options']['fields']['name']['settings']['link_to_entity'] = TRUE;
$view->storage->invalidateCaches();
$this->executeView($view);
$expected_link1 = Link::fromTextAndUrl(str_replace(' ', '-', $this->term1->getName()), $this->term1->toUrl());
$expected_link2 = Link::fromTextAndUrl($this->term2->getName(), $this->term2->toUrl());
$this->assertEquals($expected_link1->toString(), $view->getStyle()->getField(0, 'name'));
$this->assertEquals($expected_link2->toString(), $view->getStyle()->getField(1, 'name'));
}
}

View File

@ -0,0 +1,132 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\taxonomy\Functional\Views;
use Drupal\Core\Url;
use Drupal\Tests\taxonomy\Functional\TaxonomyTranslationTestTrait;
/**
* Tests for views translation.
*
* @group taxonomy
*/
class TermTranslationViewsTest extends TaxonomyTestBase {
use TaxonomyTranslationTestTrait;
/**
* Term to translated term mapping.
*
* @var array
*/
protected $termTranslationMap = [
'one' => 'translatedOne',
'two' => 'translatedTwo',
'three' => 'translatedThree',
];
/**
* Created terms.
*
* @var \Drupal\taxonomy\Entity\Term[]
*/
protected $terms = [];
/**
* {@inheritdoc}
*/
protected static $modules = ['taxonomy', 'language', 'content_translation', 'views'];
/**
* Views used by this test.
*
* @var array
*/
public static $testViews = ['taxonomy_translated_term_name_test'];
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* Language object.
*
* @var \Drupal\Core\Language\LanguageInterface|null
*/
protected $translationLanguage;
/**
* {@inheritdoc}
*/
protected function setUp($import_test_views = TRUE, $modules = []): void {
parent::setUp($import_test_views, $modules);
$this->setupLanguages();
$this->enableTranslation();
$this->setUpTerms();
$this->translationLanguage = \Drupal::languageManager()->getLanguage($this->translateToLangcode);
}
/**
* Ensure that proper translation is returned when contextual filter.
*
* Taxonomy term: Term ID & Content: Has taxonomy term ID (with depth)
* contextual filters are enabled for two separate view modes.
*/
public function testTermsTranslationWithContextualFilter(): void {
$this->drupalLogin($this->rootUser);
foreach ($this->terms as $term) {
// Test with "Content: Has taxonomy term ID (with depth)" contextual
// filter. Generate base language url and send request.
$url = Url::fromRoute('view.taxonomy_translated_term_name_test.page_1', ['arg_0' => $term->id()])->toString();
$this->drupalGet($url);
$this->assertSession()->pageTextContains($term->label());
// Generate translation URL and send request.
$url = Url::fromRoute('view.taxonomy_translated_term_name_test.page_1', ['arg_0' => $term->id()], ['language' => $this->translationLanguage])->toString();
$this->drupalGet($url);
$this->assertSession()->pageTextContains($this->termTranslationMap[$term->label()]);
// Test with "Taxonomy term: Term ID" contextual filter.
// Generate base language url and send request.
$url = Url::fromRoute('view.taxonomy_translated_term_name_test.page_2', ['arg_0' => $term->id()])->toString();
$this->drupalGet($url);
$this->assertSession()->pageTextContains($term->label());
// Generate translation URL and send request.
$url = Url::fromRoute('view.taxonomy_translated_term_name_test.page_2', ['arg_0' => $term->id()], ['language' => $this->translationLanguage])->toString();
$this->drupalGet($url);
$this->assertSession()->pageTextContains($this->termTranslationMap[$term->label()]);
}
}
/**
* Setup translated terms in a hierarchy.
*/
protected function setUpTerms(): void {
$parent_vid = 0;
foreach ($this->termTranslationMap as $name => $translation) {
$term = $this->createTerm([
'name' => $name,
'langcode' => $this->baseLangcode,
'parent' => $parent_vid,
'vid' => $this->vocabulary->id(),
]);
$term->addTranslation($this->translateToLangcode, [
'name' => $translation,
]);
$term->save();
// Each term is nested under the last.
$parent_vid = $term->id();
$this->terms[] = $term;
}
}
}

View File

@ -0,0 +1,140 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\taxonomy\Functional;
use Drupal\language\Entity\ConfigurableLanguage;
use Drupal\language\Entity\ContentLanguageSettings;
/**
* Tests the language functionality for vocabularies.
*
* @group taxonomy
*/
class VocabularyLanguageTest extends TaxonomyTestBase {
/**
* {@inheritdoc}
*/
protected static $modules = ['language'];
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
// Create an administrative user.
$this->drupalLogin($this->drupalCreateUser(['administer taxonomy']));
// Add some custom languages.
ConfigurableLanguage::create([
'id' => 'aa',
'label' => $this->randomMachineName(),
])->save();
ConfigurableLanguage::create([
'id' => 'bb',
'label' => $this->randomMachineName(),
])->save();
}
/**
* Tests language settings for vocabularies.
*/
public function testVocabularyLanguage(): void {
$this->drupalGet('admin/structure/taxonomy/add');
// Check that we have the language selector available.
$this->assertSession()->fieldExists('edit-langcode');
// Create the vocabulary.
$vid = $this->randomMachineName();
$edit['name'] = $this->randomMachineName();
$edit['description'] = $this->randomMachineName();
$edit['langcode'] = 'aa';
$edit['vid'] = $vid;
$this->submitForm($edit, 'Save');
// Check the language on the edit page.
$this->drupalGet('admin/structure/taxonomy/manage/' . $vid);
$this->assertTrue($this->assertSession()->optionExists('edit-langcode', $edit['langcode'])->isSelected());
// Change the language and save again.
$edit['langcode'] = 'bb';
unset($edit['vid']);
$this->submitForm($edit, 'Save');
// Check again the language on the edit page.
$this->drupalGet('admin/structure/taxonomy/manage/' . $vid);
$this->assertTrue($this->assertSession()->optionExists('edit-langcode', $edit['langcode'])->isSelected());
}
/**
* Tests term language settings for vocabulary terms are saved and updated.
*/
public function testVocabularyDefaultLanguageForTerms(): void {
// Add a new vocabulary and check that the default language settings are for
// the terms are saved.
$edit = [
'name' => $this->randomMachineName(),
'vid' => $this->randomMachineName(),
'default_language[langcode]' => 'bb',
'default_language[language_alterable]' => TRUE,
];
$vid = $edit['vid'];
$this->drupalGet('admin/structure/taxonomy/add');
$this->submitForm($edit, 'Save');
// Check that the vocabulary was actually created.
$this->drupalGet('admin/structure/taxonomy/manage/' . $edit['vid']);
$this->assertSession()->statusCodeEquals(200);
// Check that the language settings were saved.
$language_settings = ContentLanguageSettings::loadByEntityTypeBundle('taxonomy_term', $edit['vid']);
$this->assertEquals('bb', $language_settings->getDefaultLangcode(), 'The langcode was saved.');
$this->assertTrue($language_settings->isLanguageAlterable(), 'The visibility setting was saved.');
// Check that the correct options are selected in the interface.
$this->assertTrue($this->assertSession()->optionExists('edit-default-language-langcode', 'bb')->isSelected());
$this->assertSession()->checkboxChecked('edit-default-language-language-alterable');
// Edit the vocabulary and check that the new settings are updated.
$edit = [
'default_language[langcode]' => 'aa',
'default_language[language_alterable]' => FALSE,
];
$this->drupalGet('admin/structure/taxonomy/manage/' . $vid);
$this->submitForm($edit, 'Save');
// And check again the settings and also the interface.
$language_settings = ContentLanguageSettings::loadByEntityTypeBundle('taxonomy_term', $vid);
$this->assertEquals('aa', $language_settings->getDefaultLangcode(), 'The langcode was saved.');
$this->assertFalse($language_settings->isLanguageAlterable(), 'The visibility setting was saved.');
$this->drupalGet('admin/structure/taxonomy/manage/' . $vid);
$this->assertTrue($this->assertSession()->optionExists('edit-default-language-langcode', 'aa')->isSelected());
$this->assertSession()->checkboxNotChecked('edit-default-language-language-alterable');
// Check that language settings are changed after editing vocabulary.
$edit = [
'name' => $this->randomMachineName(),
'default_language[langcode]' => 'authors_default',
'default_language[language_alterable]' => FALSE,
];
$this->drupalGet('admin/structure/taxonomy/manage/' . $vid);
$this->submitForm($edit, 'Save');
// Check that we have the new settings.
$new_settings = ContentLanguageSettings::loadByEntityTypeBundle('taxonomy_term', $vid);
$this->assertEquals('authors_default', $new_settings->getDefaultLangcode(), 'The langcode was saved.');
$this->assertFalse($new_settings->isLanguageAlterable(), 'The new visibility setting was saved.');
}
}

View File

@ -0,0 +1,392 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\taxonomy\Functional;
use Drupal\Component\Utility\Unicode;
/**
* Tests the taxonomy vocabulary permissions.
*
* @group taxonomy
*/
class VocabularyPermissionsTest extends TaxonomyTestBase {
/**
* {@inheritdoc}
*/
protected static $modules = ['help'];
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
$this->drupalPlaceBlock('page_title_block');
$this->drupalPlaceBlock('local_actions_block');
$this->drupalPlaceBlock('help_block');
}
/**
* Create, edit and delete a vocabulary via the user interface.
*/
public function testVocabularyPermissionsVocabulary(): void {
// VocabularyTest.php already tests for user with "administer taxonomy"
// permission.
// Test as user without proper permissions.
$authenticated_user = $this->drupalCreateUser([]);
$this->drupalLogin($authenticated_user);
$assert_session = $this->assertSession();
// Visit the main taxonomy administration page.
$this->drupalGet('admin/structure/taxonomy');
$assert_session->statusCodeEquals(403);
// Test as user with "access taxonomy overview" permissions.
$proper_user = $this->drupalCreateUser(['access taxonomy overview']);
$this->drupalLogin($proper_user);
// Visit the main taxonomy administration page.
$this->drupalGet('admin/structure/taxonomy');
$assert_session->statusCodeEquals(200);
$assert_session->pageTextContains('Vocabulary name');
$assert_session->linkNotExists('Add vocabulary');
}
/**
* Tests the vocabulary overview permission.
*/
public function testTaxonomyVocabularyOverviewPermissions(): void {
// Create two vocabularies, one with two terms, the other without any term.
/** @var \Drupal\taxonomy\Entity\Vocabulary $vocabulary1 , $vocabulary2 */
$vocabulary1 = $this->createVocabulary();
$vocabulary2 = $this->createVocabulary();
$vocabulary1_id = $vocabulary1->id();
$vocabulary2_id = $vocabulary2->id();
$this->createTerm($vocabulary1);
$this->createTerm($vocabulary1);
// Assert expected help texts on first vocabulary.
$vocabulary1_label = Unicode::ucfirst($vocabulary1->label());
$edit_help_text = "You can reorganize the terms in $vocabulary1_label using their drag-and-drop handles, and group terms under a parent term by sliding them under and to the right of the parent.";
$no_edit_help_text = "$vocabulary1_label contains the following terms.";
$assert_session = $this->assertSession();
// Logged in as admin user with 'administer taxonomy' permission.
$admin_user = $this->drupalCreateUser(['administer taxonomy']);
$this->drupalLogin($admin_user);
$this->drupalGet('admin/structure/taxonomy/manage/' . $vocabulary1_id . '/overview');
$assert_session->statusCodeEquals(200);
$assert_session->linkExists('Edit');
$assert_session->linkExists('Delete');
$assert_session->linkExists('Add term');
$assert_session->buttonExists('Save');
$assert_session->pageTextContains('Weight');
$assert_session->fieldExists('Weight');
$assert_session->pageTextContains($edit_help_text);
$this->submitForm([], 'Reset to alphabetical');
$assert_session->statusCodeEquals(200);
// Visit vocabulary overview without terms. 'Add term' should be shown.
$this->drupalGet('admin/structure/taxonomy/manage/' . $vocabulary2_id . '/overview');
$assert_session->statusCodeEquals(200);
$assert_session->pageTextContains('No terms available');
$assert_session->linkExists('Add term');
// Login as a user without any of the required permissions.
$no_permission_user = $this->drupalCreateUser();
$this->drupalLogin($no_permission_user);
$this->drupalGet('admin/structure/taxonomy/manage/' . $vocabulary1_id . '/overview');
$assert_session->statusCodeEquals(403);
$this->drupalGet('admin/structure/taxonomy/manage/' . $vocabulary2_id . '/overview');
$assert_session->statusCodeEquals(403);
// Log in as a user with only the overview permission, neither edit nor
// delete operations must be available and no Save button.
$overview_only_user = $this->drupalCreateUser(['access taxonomy overview']);
$this->drupalLogin($overview_only_user);
$this->drupalGet('admin/structure/taxonomy/manage/' . $vocabulary1_id . '/overview');
$assert_session->statusCodeEquals(200);
$assert_session->linkNotExists('Edit');
$assert_session->linkNotExists('Delete');
$assert_session->buttonNotExists('Save');
$assert_session->buttonNotExists('Reset to alphabetical');
$assert_session->pageTextContains('Weight');
$assert_session->fieldNotExists('Weight');
$assert_session->linkNotExists('Add term');
$assert_session->pageTextContains($no_edit_help_text);
// Visit vocabulary overview without terms. 'Add term' should not be shown.
$this->drupalGet('admin/structure/taxonomy/manage/' . $vocabulary2_id . '/overview');
$assert_session->statusCodeEquals(200);
$assert_session->pageTextContains('No terms available');
$assert_session->linkNotExists('Add term');
// Login as a user with permission to edit terms, only edit link should be
// visible.
$edit_user = $this->createUser([
'access taxonomy overview',
'edit terms in ' . $vocabulary1_id,
'edit terms in ' . $vocabulary2_id,
]);
$this->drupalLogin($edit_user);
$this->drupalGet('admin/structure/taxonomy/manage/' . $vocabulary1_id . '/overview');
$assert_session->statusCodeEquals(200);
$assert_session->linkExists('Edit');
$assert_session->linkNotExists('Delete');
$assert_session->buttonExists('Save');
$assert_session->pageTextContains('Weight');
$assert_session->fieldExists('Weight');
$assert_session->linkNotExists('Add term');
$assert_session->pageTextContains($edit_help_text);
$this->submitForm([], 'Reset to alphabetical');
$assert_session->statusCodeEquals(200);
// Visit vocabulary overview without terms. 'Add term' should not be shown.
$this->drupalGet('admin/structure/taxonomy/manage/' . $vocabulary2_id . '/overview');
$assert_session->statusCodeEquals(200);
$assert_session->pageTextContains('No terms available');
$assert_session->linkNotExists('Add term');
// Login as a user with permission only to delete terms.
$edit_delete_user = $this->createUser([
'access taxonomy overview',
'delete terms in ' . $vocabulary1_id,
'delete terms in ' . $vocabulary2_id,
]);
$this->drupalLogin($edit_delete_user);
$this->drupalGet('admin/structure/taxonomy/manage/' . $vocabulary1_id . '/overview');
$assert_session->statusCodeEquals(200);
$assert_session->linkNotExists('Edit');
$assert_session->linkExists('Delete');
$assert_session->linkNotExists('Add term');
$assert_session->buttonNotExists('Save');
$assert_session->buttonNotExists('Reset to alphabetical');
$assert_session->pageTextContains('Weight');
$assert_session->fieldNotExists('Weight');
$assert_session->pageTextContains($no_edit_help_text);
// Visit vocabulary overview without terms. 'Add term' should not be shown.
$this->drupalGet('admin/structure/taxonomy/manage/' . $vocabulary2_id . '/overview');
$assert_session->statusCodeEquals(200);
$assert_session->pageTextContains('No terms available');
$assert_session->linkNotExists('Add term');
// Login as a user with permission to edit and delete terms.
$edit_delete_user = $this->createUser([
'access taxonomy overview',
'edit terms in ' . $vocabulary1_id,
'delete terms in ' . $vocabulary1_id,
'edit terms in ' . $vocabulary2_id,
'delete terms in ' . $vocabulary2_id,
]);
$this->drupalLogin($edit_delete_user);
$this->drupalGet('admin/structure/taxonomy/manage/' . $vocabulary1_id . '/overview');
$assert_session->statusCodeEquals(200);
$assert_session->linkExists('Edit');
$assert_session->linkExists('Delete');
$assert_session->linkNotExists('Add term');
$assert_session->buttonExists('Save');
$assert_session->pageTextContains('Weight');
$assert_session->fieldExists('Weight');
$assert_session->pageTextContains($edit_help_text);
$this->submitForm([], 'Reset to alphabetical');
$assert_session->statusCodeEquals(200);
// Visit vocabulary overview without terms. 'Add term' should not be shown.
$this->drupalGet('admin/structure/taxonomy/manage/' . $vocabulary2_id . '/overview');
$assert_session->statusCodeEquals(200);
$assert_session->pageTextContains('No terms available');
$assert_session->linkNotExists('Add term');
// Login as a user with permission to create new terms, only add new term
// link should be visible.
$edit_user = $this->createUser([
'access taxonomy overview',
'create terms in ' . $vocabulary1_id,
'create terms in ' . $vocabulary2_id,
]);
$this->drupalLogin($edit_user);
$this->drupalGet('admin/structure/taxonomy/manage/' . $vocabulary1_id . '/overview');
$assert_session->statusCodeEquals(200);
$assert_session->linkNotExists('Edit');
$assert_session->linkNotExists('Delete');
$assert_session->linkExists('Add term');
$assert_session->buttonNotExists('Save');
$assert_session->buttonNotExists('Reset to alphabetical');
$assert_session->pageTextContains('Weight');
$assert_session->fieldNotExists('Weight');
$assert_session->pageTextContains($no_edit_help_text);
// Visit vocabulary overview without terms. 'Add term' should not be shown.
$this->drupalGet('admin/structure/taxonomy/manage/' . $vocabulary2_id . '/overview');
$assert_session->statusCodeEquals(200);
$assert_session->pageTextContains('No terms available');
$assert_session->linkExists('Add term');
// Ensure the dynamic vocabulary permissions have the correct dependencies.
$permissions = \Drupal::service('user.permissions')->getPermissions();
$this->assertTrue(isset($permissions['create terms in ' . $vocabulary1_id]));
$this->assertEquals(['config' => [$vocabulary1->getConfigDependencyName()]], $permissions['create terms in ' . $vocabulary1_id]['dependencies']);
}
/**
* Create, edit and delete a taxonomy term via the user interface.
*/
public function testVocabularyPermissionsTaxonomyTerm(): void {
// Vocabulary used for creating, removing and editing terms.
$vocabulary = $this->createVocabulary();
// Test as admin user.
$user = $this->drupalCreateUser(['administer taxonomy']);
$this->drupalLogin($user);
// Visit the main taxonomy administration page.
$this->drupalGet('admin/structure/taxonomy/manage/' . $vocabulary->id() . '/add');
$this->assertSession()->statusCodeEquals(200);
$this->assertSession()->fieldExists('edit-name-0-value');
// Submit the term.
$edit = [];
$edit['name[0][value]'] = $this->randomMachineName();
$this->submitForm($edit, 'Save');
$this->assertSession()->pageTextContains('Created new term ' . $edit['name[0][value]'] . '.');
// Verify that the creation message contains a link to a term.
$this->assertSession()->elementExists('xpath', '//div[@data-drupal-messages]//a[contains(@href, "term/")]');
$terms = \Drupal::entityTypeManager()
->getStorage('taxonomy_term')
->loadByProperties(['name' => $edit['name[0][value]']]);
$term = reset($terms);
// Edit the term.
$this->drupalGet('taxonomy/term/' . $term->id() . '/edit');
$this->assertSession()->statusCodeEquals(200);
$this->assertSession()->pageTextContains($edit['name[0][value]']);
$edit['name[0][value]'] = $this->randomMachineName();
$this->submitForm($edit, 'Save');
$this->assertSession()->pageTextContains('Updated term ' . $edit['name[0][value]'] . '.');
// Delete the vocabulary.
$this->drupalGet('taxonomy/term/' . $term->id() . '/delete');
$this->assertSession()->pageTextContains("Are you sure you want to delete the taxonomy term {$edit['name[0][value]']}?");
// Confirm deletion.
$this->submitForm([], 'Delete');
$this->assertSession()->pageTextContains("Deleted term {$edit['name[0][value]']}.");
// Test as user with "create" permissions.
$user = $this->drupalCreateUser(["create terms in {$vocabulary->id()}"]);
$this->drupalLogin($user);
$assert_session = $this->assertSession();
// Create a new term.
$this->drupalGet('admin/structure/taxonomy/manage/' . $vocabulary->id() . '/add');
$assert_session->statusCodeEquals(200);
$assert_session->fieldExists('name[0][value]');
// Submit the term.
$edit = [];
$edit['name[0][value]'] = $this->randomMachineName();
$this->submitForm($edit, 'Save');
$assert_session->pageTextContains("Created new term {$edit['name[0][value]']}.");
$terms = \Drupal::entityTypeManager()
->getStorage('taxonomy_term')
->loadByProperties(['name' => $edit['name[0][value]']]);
$term = reset($terms);
// Ensure that edit and delete access is denied.
$this->drupalGet('taxonomy/term/' . $term->id() . '/edit');
$assert_session->statusCodeEquals(403);
$this->drupalGet('taxonomy/term/' . $term->id() . '/delete');
$assert_session->statusCodeEquals(403);
// Test as user with "edit" permissions.
$user = $this->drupalCreateUser(["edit terms in {$vocabulary->id()}"]);
$this->drupalLogin($user);
// Ensure the taxonomy term add form is denied.
$this->drupalGet('admin/structure/taxonomy/manage/' . $vocabulary->id() . '/add');
$this->assertSession()->statusCodeEquals(403);
// Create a test term.
$term = $this->createTerm($vocabulary);
// Edit the term.
$this->drupalGet('taxonomy/term/' . $term->id() . '/edit');
$this->assertSession()->statusCodeEquals(200);
$this->assertSession()->pageTextContains($term->getName());
$edit['name[0][value]'] = $this->randomMachineName();
$this->submitForm($edit, 'Save');
$this->assertSession()->pageTextContains('Updated term ' . $edit['name[0][value]'] . '.');
// Verify that the update message contains a link to a term.
$this->assertSession()->elementExists('xpath', '//div[@data-drupal-messages]//a[contains(@href, "term/")]');
// Ensure the term cannot be deleted.
$this->drupalGet('taxonomy/term/' . $term->id() . '/delete');
$this->assertSession()->statusCodeEquals(403);
// Test as user with "delete" permissions.
$user = $this->drupalCreateUser(["delete terms in {$vocabulary->id()}"]);
$this->drupalLogin($user);
// Ensure the taxonomy term add form is denied.
$this->drupalGet('admin/structure/taxonomy/manage/' . $vocabulary->id() . '/add');
$this->assertSession()->statusCodeEquals(403);
// Create a test term.
$term = $this->createTerm($vocabulary);
// Ensure that the term cannot be edited.
$this->drupalGet('taxonomy/term/' . $term->id() . '/edit');
$this->assertSession()->statusCodeEquals(403);
// Delete the vocabulary.
$this->drupalGet('taxonomy/term/' . $term->id() . '/delete');
$this->assertSession()->pageTextContains("Are you sure you want to delete the taxonomy term {$term->getName()}?");
// Confirm deletion.
$this->submitForm([], 'Delete');
$this->assertSession()->pageTextContains("Deleted term {$term->getName()}.");
// Test as user without proper permissions.
$user = $this->drupalCreateUser();
$this->drupalLogin($user);
// Ensure the taxonomy term add form is denied.
$this->drupalGet('admin/structure/taxonomy/manage/' . $vocabulary->id() . '/add');
$this->assertSession()->statusCodeEquals(403);
// Create a test term.
$term = $this->createTerm($vocabulary);
// Ensure that the term cannot be edited.
$this->drupalGet('taxonomy/term/' . $term->id() . '/edit');
$this->assertSession()->statusCodeEquals(403);
// Ensure the term cannot be deleted.
$this->drupalGet('taxonomy/term/' . $term->id() . '/delete');
$this->assertSession()->statusCodeEquals(403);
}
}

View File

@ -0,0 +1,121 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\taxonomy\Functional;
use Drupal\language\Entity\ConfigurableLanguage;
/**
* Tests content translation for vocabularies.
*
* @group taxonomy
*/
class VocabularyTranslationTest extends TaxonomyTestBase {
/**
* {@inheritdoc}
*/
protected static $modules = [
'content_translation',
'language',
'config_translation',
];
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* Languages to enable.
*
* @var string[]
*/
protected $additionalLangcodes = ['es'];
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
// Create an administrative user.
$this->drupalLogin($this->drupalCreateUser([
'administer taxonomy',
'administer content translation',
'translate configuration',
]));
// Add languages.
foreach ($this->additionalLangcodes as $langcode) {
ConfigurableLanguage::createFromLangcode($langcode)->save();
}
}
/**
* Tests language settings for vocabularies.
*/
public function testVocabularyLanguage(): void {
$this->drupalGet('admin/structure/taxonomy/add');
// Check that the field to enable content translation is available.
$this->assertSession()->fieldExists('edit-default-language-content-translation');
// Create the vocabulary.
$vid = $this->randomMachineName();
$edit['name'] = $this->randomMachineName();
$edit['description'] = $this->randomMachineName();
$edit['langcode'] = 'en';
$edit['vid'] = $vid;
$edit['default_language[content_translation]'] = TRUE;
$this->submitForm($edit, 'Save');
// Check if content translation is enabled on the edit page.
$this->drupalGet('admin/structure/taxonomy/manage/' . $vid);
$this->assertSession()->checkboxChecked('edit-default-language-content-translation');
}
/**
* Tests vocabulary name translation for the overview and reset pages.
*/
public function testVocabularyTitleLabelTranslation(): void {
$this->drupalGet('admin/structure/taxonomy/add');
// Create the vocabulary.
$vid = $this->randomMachineName();
$edit['name'] = $this->randomMachineName();
$edit['description'] = $this->randomMachineName();
$edit['langcode'] = 'en';
$edit['vid'] = $vid;
$edit['default_language[content_translation]'] = TRUE;
$this->submitForm($edit, 'Save');
$langcode = $this->additionalLangcodes[0];
$vid_name = $edit['name'];
$translated_vid_name = "Translated $vid_name";
$this->assertSession()->pageTextContains($vid_name);
// Assert that the name label is displayed on the translation form with the
// right value.
$this->drupalGet("admin/structure/taxonomy/manage/$vid/translate/$langcode/add");
// Translate the name label.
$this->submitForm(["translation[config_names][taxonomy.vocabulary.$vid][name]" => $translated_vid_name], 'Save translation');
// Assert that the right name label is displayed on the taxonomy term
// overview page.
$this->drupalGet("admin/structure/taxonomy/manage/$vid/overview");
$this->assertSession()->pageTextContains($vid_name);
$this->drupalGet("$langcode/admin/structure/taxonomy/manage/$vid/overview");
$this->assertSession()->pageTextContains($translated_vid_name);
// Assert that the right name label is displayed on the taxonomy reset page.
$this->drupalGet("admin/structure/taxonomy/manage/$vid/reset");
$this->assertSession()->pageTextContains($vid_name);
$this->drupalGet("$langcode/admin/structure/taxonomy/manage/$vid/reset");
$this->assertSession()->pageTextContains($translated_vid_name);
}
}

View File

@ -0,0 +1,190 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\taxonomy\Functional;
use Drupal\Component\Utility\Html;
use Drupal\Core\Url;
use Drupal\taxonomy\Entity\Vocabulary;
/**
* Tests the taxonomy vocabulary interface.
*
* @group taxonomy
*/
class VocabularyUiTest extends TaxonomyTestBase {
/**
* The vocabulary used for creating terms.
*
* @var \Drupal\taxonomy\VocabularyInterface
*/
protected $vocabulary;
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
$this->drupalLogin($this->drupalCreateUser(['administer taxonomy']));
$this->vocabulary = $this->createVocabulary();
$this->drupalPlaceBlock('local_actions_block');
$this->drupalPlaceBlock('page_title_block');
}
/**
* Create, edit and delete a vocabulary via the user interface.
*/
public function testVocabularyInterface(): void {
// Visit the main taxonomy administration page.
$this->drupalGet('admin/structure/taxonomy');
// Create a new vocabulary.
$this->clickLink('Add vocabulary');
$edit = [];
$vid = $this->randomMachineName();
$edit['name'] = $this->randomMachineName();
$edit['description'] = $this->randomMachineName();
$edit['vid'] = $vid;
$this->submitForm($edit, 'Save');
$this->assertSession()->pageTextContains("Created new vocabulary {$edit['name']}.");
// Edit the vocabulary.
$this->drupalGet('admin/structure/taxonomy');
$this->assertSession()->pageTextContains($edit['name']);
$this->assertSession()->pageTextContains($edit['description']);
$this->assertSession()->linkByHrefExists(Url::fromRoute('entity.taxonomy_term.add_form', ['taxonomy_vocabulary' => $edit['vid']])->toString());
$this->clickLink('Edit vocabulary');
$edit = [];
$edit['name'] = $this->randomMachineName();
$edit['description'] = $this->randomMachineName();
$this->submitForm($edit, 'Save');
$this->drupalGet('admin/structure/taxonomy');
$this->assertSession()->pageTextContains($edit['name']);
$this->assertSession()->pageTextContains($edit['description']);
// Try to submit a vocabulary with a duplicate machine name.
$edit['vid'] = $vid;
$this->drupalGet('admin/structure/taxonomy/add');
$this->submitForm($edit, 'Save');
$this->assertSession()->pageTextContains('The machine-readable name is already in use. It must be unique.');
// Try to submit an invalid machine name.
$edit['vid'] = '!&^%';
$this->drupalGet('admin/structure/taxonomy/add');
$this->submitForm($edit, 'Save');
$this->assertSession()->pageTextContains('The machine-readable name must contain only lowercase letters, numbers, and underscores.');
// Ensure that vocabulary titles are escaped properly.
$edit = [];
$edit['name'] = 'Don\'t Panic';
$edit['description'] = $this->randomMachineName();
$edit['vid'] = 'don_t_panic';
$this->drupalGet('admin/structure/taxonomy/add');
$this->submitForm($edit, 'Save');
$site_name = $this->config('system.site')->get('name');
$this->assertSession()->titleEquals("Don't Panic | $site_name");
// Delete the vocabulary.
$this->drupalGet('admin/structure/taxonomy');
$href = Url::fromRoute('entity.taxonomy_vocabulary.delete_form', ['taxonomy_vocabulary' => $edit['vid']])->toString();
$xpath = $this->assertSession()->buildXPathQuery('//a[contains(@href, :href)]', [':href' => $href]);
$link = $this->assertSession()->elementExists('xpath', $xpath);
$this->assertEquals('Delete vocabulary', $link->getText());
$link->click();
// Confirm deletion.
$name = Html::escape($edit['name']);
$this->assertSession()->responseContains("Are you sure you want to delete the vocabulary <em class=\"placeholder\">$name</em>?");
$this->submitForm([], 'Delete');
$this->assertSession()->responseContains("Deleted vocabulary <em class=\"placeholder\">$name</em>.");
$this->container->get('entity_type.manager')->getStorage('taxonomy_vocabulary')->resetCache();
$this->assertNull(Vocabulary::load($edit['vid']), 'Vocabulary not found.');
}
/**
* Changing weights on the vocabulary overview with two or more vocabularies.
*/
public function testTaxonomyAdminChangingWeights(): void {
// Create some vocabularies.
for ($i = 0; $i < 10; $i++) {
$this->createVocabulary();
}
// Get all vocabularies and change their weights.
$vocabularies = Vocabulary::loadMultiple();
$edit = [];
foreach ($vocabularies as $key => $vocabulary) {
$weight = -$vocabulary->get('weight');
$vocabularies[$key]->set('weight', $weight);
$edit['vocabularies[' . $key . '][weight]'] = $weight;
}
// Saving the new weights via the interface.
$this->drupalGet('admin/structure/taxonomy');
$this->submitForm($edit, 'Save');
// Load the vocabularies from the database.
$this->container->get('entity_type.manager')->getStorage('taxonomy_vocabulary')->resetCache();
$new_vocabularies = Vocabulary::loadMultiple();
// Check that the weights are saved in the database correctly.
foreach ($vocabularies as $key => $vocabulary) {
$this->assertEquals($new_vocabularies[$key]->get('weight'), $vocabularies[$key]->get('weight'), 'The vocabulary weight was changed.');
}
}
/**
* Tests the vocabulary overview with no vocabularies.
*/
public function testTaxonomyAdminNoVocabularies(): void {
// Delete all vocabularies.
$vocabularies = Vocabulary::loadMultiple();
foreach ($vocabularies as $vocabulary) {
$vocabulary->delete();
}
// Confirm that no vocabularies are found in the database.
$this->assertEmpty(Vocabulary::loadMultiple(), 'No vocabularies found.');
$this->drupalGet('admin/structure/taxonomy');
// Check the default message for no vocabularies.
$this->assertSession()->pageTextContains('No vocabularies available.');
}
/**
* Deleting a vocabulary.
*/
public function testTaxonomyAdminDeletingVocabulary(): void {
// Create a vocabulary.
$vid = $this->randomMachineName();
$edit = [
'name' => $this->randomMachineName(),
'vid' => $vid,
];
$this->drupalGet('admin/structure/taxonomy/add');
$this->submitForm($edit, 'Save');
$this->assertSession()->pageTextContains('Created new vocabulary');
// Check the created vocabulary.
$this->container->get('entity_type.manager')->getStorage('taxonomy_vocabulary')->resetCache();
$vocabulary = Vocabulary::load($vid);
$this->assertNotEmpty($vocabulary, 'Vocabulary found.');
// Delete the vocabulary.
$this->drupalGet('admin/structure/taxonomy/manage/' . $vocabulary->id());
$this->clickLink('Delete');
$this->assertSession()->pageTextContains("Are you sure you want to delete the vocabulary {$vocabulary->label()}?");
$this->assertSession()->pageTextContains('Deleting a vocabulary will delete all the terms in it. This action cannot be undone.');
// Confirm deletion.
$this->submitForm([], 'Delete');
$this->assertSession()->pageTextContains("Deleted vocabulary {$vocabulary->label()}.");
$this->container->get('entity_type.manager')->getStorage('taxonomy_vocabulary')->resetCache();
$this->assertNull(Vocabulary::load($vid), 'Vocabulary not found.');
}
}

View File

@ -0,0 +1,73 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\taxonomy\Kernel\ContextProvider;
use Drupal\Core\Routing\RouteMatch;
use Drupal\KernelTests\KernelTestBase;
use Drupal\taxonomy\ContextProvider\TermRouteContext;
use Drupal\Tests\taxonomy\Traits\TaxonomyTestTrait;
/**
* @coversDefaultClass \Drupal\taxonomy\ContextProvider\TermRouteContext
*
* @group taxonomy
*/
class TermContextTest extends KernelTestBase {
use TaxonomyTestTrait;
/**
* {@inheritdoc}
*/
protected static $modules = ['filter', 'taxonomy', 'text', 'user'];
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
$this->installConfig(['filter']);
$this->installEntitySchema('user');
$this->installEntitySchema('taxonomy_term');
}
/**
* @covers ::getAvailableContexts
*/
public function testGetAvailableContexts(): void {
$context_repository = $this->container->get('context.repository');
// Test taxonomy_term.taxonomy_term_route_context:taxonomy_term exists.
$contexts = $context_repository->getAvailableContexts();
$this->assertArrayHasKey('@taxonomy_term.taxonomy_term_route_context:taxonomy_term', $contexts);
$this->assertSame('entity:taxonomy_term', $contexts['@taxonomy_term.taxonomy_term_route_context:taxonomy_term']->getContextDefinition()
->getDataType());
}
/**
* @covers ::getRuntimeContexts
*/
public function testGetRuntimeContexts(): void {
// Create term.
$vocabulary = $this->createVocabulary();
$term = $this->createTerm($vocabulary);
// Create RouteMatch from term entity.
$url = $term->toUrl();
$route_provider = \Drupal::service('router.route_provider');
$route = $route_provider->getRouteByName($url->getRouteName());
$route_match = new RouteMatch($url->getRouteName(), $route, [
'taxonomy_term' => $term,
]);
// Initiate TermRouteContext with RouteMatch.
$provider = new TermRouteContext($route_match);
$runtime_contexts = $provider->getRuntimeContexts([]);
$this->assertArrayHasKey('taxonomy_term', $runtime_contexts);
$this->assertTrue($runtime_contexts['taxonomy_term']->hasContextValue());
}
}

View File

@ -0,0 +1,82 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\taxonomy\Kernel;
use Drupal\KernelTests\KernelTestBase;
use Drupal\taxonomy\Entity\Term;
use Drupal\Tests\taxonomy\Traits\TaxonomyTestTrait;
/**
* Tests the loading of multiple taxonomy terms at once.
*
* @group taxonomy
*/
class LoadMultipleTest extends KernelTestBase {
use TaxonomyTestTrait;
/**
* {@inheritdoc}
*/
protected static $modules = [
'system',
'taxonomy',
'user',
'text',
'filter',
];
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
$this->installEntitySchema('taxonomy_term');
}
/**
* Tests loading multiple taxonomy terms by term ID and vocabulary.
*/
public function testTaxonomyTermMultipleLoad(): void {
// Create a vocabulary.
$vocabulary = $this->createVocabulary();
// Create five terms in the vocabulary.
$i = 0;
while ($i < 5) {
$i++;
$this->createTerm($vocabulary);
}
// Load the terms from the vocabulary.
$term_storage = \Drupal::entityTypeManager()->getStorage('taxonomy_term');
$terms = $term_storage->loadByProperties(['vid' => $vocabulary->id()]);
$count = count($terms);
$this->assertEquals(5, $count, "Correct number of terms were loaded. $count terms.");
// Load the same terms again by tid.
$terms2 = Term::loadMultiple(array_keys($terms));
$this->assertEquals($terms, $terms2, 'Both arrays contain the same terms.');
// Remove one term from the array, then delete it.
$deleted = array_shift($terms2);
$deleted->delete();
$deleted_term = Term::load($deleted->id());
$this->assertNull($deleted_term);
// Load terms from the vocabulary by vid.
$terms3 = $term_storage->loadByProperties(['vid' => $vocabulary->id()]);
$this->assertCount(4, $terms3, 'Correct number of terms were loaded.');
$this->assertFalse(isset($terms3[$deleted->id()]));
// Create a single term and load it by name.
$term = $this->createTerm($vocabulary);
$loaded_terms = $term_storage->loadByProperties(['name' => $term->getName()]);
$this->assertCount(1, $loaded_terms, 'One term was loaded.');
$loaded_term = reset($loaded_terms);
$this->assertEquals($term->id(), $loaded_term->id(), 'Term loaded by name successfully.');
}
}

View File

@ -0,0 +1,43 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\taxonomy\Kernel\Migrate;
use Drupal\Tests\SchemaCheckTestTrait;
use Drupal\Tests\migrate_drupal\Kernel\d6\MigrateDrupal6TestBase;
/**
* Upgrade variables to taxonomy.settings.yml.
*
* @group migrate_drupal_6
*/
class MigrateTaxonomyConfigsTest extends MigrateDrupal6TestBase {
use SchemaCheckTestTrait;
/**
* {@inheritdoc}
*/
protected static $modules = ['taxonomy'];
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
$this->executeMigration('taxonomy_settings');
}
/**
* Tests migration of taxonomy variables to taxonomy.settings.yml.
*/
public function testTaxonomySettings(): void {
$config = $this->config('taxonomy.settings');
$this->assertSame(100, $config->get('terms_per_page_admin'));
$this->assertFalse($config->get('override_selector'));
$this->assertTrue($config->get('maintain_index_table'));
$this->assertConfigSchema(\Drupal::service('config.typed'), 'taxonomy.settings', $config->get());
}
}

View File

@ -0,0 +1,88 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\taxonomy\Kernel\Migrate;
use Drupal\migrate\MigrateExecutable;
use Drupal\Tests\migrate_drupal\Kernel\MigrateDrupalTestBase;
use Drupal\migrate_drupal\Tests\StubTestTrait;
use Drupal\taxonomy\Entity\Term;
use Drupal\taxonomy\Entity\Vocabulary;
/**
* Test stub creation for taxonomy terms.
*
* @group taxonomy
*/
class MigrateTaxonomyTermStubTest extends MigrateDrupalTestBase {
use StubTestTrait;
/**
* {@inheritdoc}
*/
protected static $modules = ['taxonomy', 'text', 'taxonomy_term_stub_test'];
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
$this->installEntitySchema('taxonomy_term');
}
/**
* Tests creation of taxonomy term stubs.
*/
public function testStub(): void {
Vocabulary::create([
'vid' => 'test_vocabulary',
'name' => 'Test vocabulary',
])->save();
$this->performStubTest('taxonomy_term');
}
/**
* Tests creation of stubs when weight is mapped.
*/
public function testStubWithWeightMapping(): void {
// Create a vocabulary via migration for the terms to reference.
$vocabulary_data_rows = [
['id' => '1', 'name' => 'tags'],
];
$ids = ['id' => ['type' => 'integer']];
$definition = [
'migration_tags' => ['Stub test'],
'source' => [
'plugin' => 'embedded_data',
'data_rows' => $vocabulary_data_rows,
'ids' => $ids,
],
'process' => [
'vid' => 'id',
'name' => 'name',
],
'destination' => ['plugin' => 'entity:taxonomy_vocabulary'],
];
$vocabulary_migration = \Drupal::service('plugin.manager.migration')->createStubMigration($definition);
$vocabulary_executable = new MigrateExecutable($vocabulary_migration, $this);
$vocabulary_executable->import();
// We have a term referencing an unmigrated parent, forcing a stub to be
// created.
$migration = $this->getMigration('taxonomy_term_stub_test');
$term_executable = new MigrateExecutable($migration, $this);
$term_executable->import();
$this->assertNotEmpty($migration->getIdMap()->getRowBySource(['2']), 'Stub row exists in the ID map table');
// Load the referenced term, which should exist as a stub.
/** @var \Drupal\Core\Entity\ContentEntityBase $stub_entity */
$stub_entity = Term::load(2);
$this->assertNotEmpty($stub_entity, 'Stub successfully created');
if ($stub_entity) {
$this->assertCount(0, $stub_entity->validate(), 'Stub is a valid entity');
}
}
}

View File

@ -0,0 +1,45 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\taxonomy\Kernel\Migrate;
use Drupal\Tests\migrate_drupal\Kernel\d7\MigrateDrupal7TestBase;
// cspell:ignore vocabfixed vocablocalized vocabtranslate
/**
* Tests d7 taxonomy term deriver.
*
* @group migrate_drupal_7
*/
class TaxonomyTermDeriverTest extends MigrateDrupal7TestBase {
/**
* {@inheritdoc}
*/
protected static $modules = ['taxonomy', 'text'];
/**
* Tests fields exist in process pipeline for term migrations.
*/
public function testBuilder(): void {
// Test a field on the vocabfixed term.
$process = $this->getMigration('d7_taxonomy_term:vocabfixed')->getProcess();
$this->assertSame('field_training', $process['field_training'][0]['source']);
// Test a field on the vocablocalized term.
$process = $this->getMigration('d7_taxonomy_term:vocablocalized')->getProcess();
$this->assertSame('field_sector', $process['field_sector'][0]['source']);
// Test a field on the vocabtranslate term.
$process = $this->getMigration('d7_taxonomy_term:vocabtranslate')->getProcess();
$this->assertSame('field_chancellor', $process['field_chancellor'][0]['source']);
// Test a field on the test_vocabulary term.
$process = $this->getMigration('d7_taxonomy_term:test_vocabulary')->getProcess();
$this->assertSame('field_integer', $process['field_integer'][0]['source']);
$this->assertSame('field_term_reference', $process['field_term_reference'][0]['source']);
}
}

View File

@ -0,0 +1,120 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\taxonomy\Kernel\Migrate\d6;
use Drupal\taxonomy\Entity\Term;
use Drupal\Tests\migrate_drupal\Kernel\d6\MigrateDrupal6TestBase;
/**
* Upgrade taxonomy terms.
*
* @group migrate_drupal_6
*/
class MigrateTaxonomyTermTest extends MigrateDrupal6TestBase {
/**
* {@inheritdoc}
*/
protected static $modules = ['comment', 'taxonomy'];
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
$this->installEntitySchema('taxonomy_term');
$this->executeMigrations(['d6_taxonomy_vocabulary', 'd6_taxonomy_term']);
}
/**
* Tests the Drupal 6 taxonomy term to Drupal 8 migration.
*/
public function testTaxonomyTerms(): void {
$expected_results = [
'1' => [
'source_vid' => 1,
'vid' => 'vocabulary_1_i_0_',
'weight' => 0,
'parent' => [0],
'language' => 'zu',
],
'2' => [
'source_vid' => 2,
'vid' => 'vocabulary_2_i_1_',
'weight' => 3,
'parent' => [0],
'language' => 'fr',
],
'3' => [
'source_vid' => 2,
'vid' => 'vocabulary_2_i_1_',
'weight' => 4,
'parent' => [2],
'language' => 'fr',
],
'4' => [
'source_vid' => 3,
'vid' => 'vocabulary_3_i_2_',
'weight' => 6,
'parent' => [0],
],
'5' => [
'source_vid' => 3,
'vid' => 'vocabulary_3_i_2_',
'weight' => 7,
'parent' => [4],
],
'6' => [
'source_vid' => 3,
'vid' => 'vocabulary_3_i_2_',
'weight' => 8,
'parent' => [4, 5],
],
];
$terms = Term::loadMultiple(array_keys($expected_results));
// Find each term in the tree.
$storage = \Drupal::entityTypeManager()->getStorage('taxonomy_term');
$vids = array_unique(array_column($expected_results, 'vid'));
$tree_terms = [];
foreach ($vids as $vid) {
foreach ($storage->loadTree($vid) as $term) {
$tree_terms[$term->tid] = $term;
}
}
foreach ($expected_results as $tid => $values) {
/** @var \Drupal\taxonomy\Entity\Term $term */
$term = $terms[$tid];
$language = isset($values['language']) ? $values['language'] . ' - ' : '';
$this->assertSame("{$language}term {$tid} of vocabulary {$values['source_vid']}", $term->name->value);
$this->assertSame("{$language}description of term {$tid} of vocabulary {$values['source_vid']}", $term->description->value);
$this->assertSame($values['vid'], $term->vid->target_id);
$this->assertSame((string) $values['weight'], $term->weight->value);
if ($values['parent'] === [0]) {
$this->assertSame(0, (int) $term->parent->target_id);
}
else {
$parents = [];
foreach (\Drupal::entityTypeManager()->getStorage('taxonomy_term')->loadParents($tid) as $parent) {
$parents[] = (int) $parent->id();
}
$this->assertSame($parents, $values['parent']);
}
$this->assertArrayHasKey($tid, $tree_terms, "Term $tid exists in vocabulary tree");
$tree_term = $tree_terms[$tid];
// PostgreSQL, MySQL and SQLite may not return the parent terms in the
// same order so sort before testing.
$expected_parents = $values['parent'];
sort($expected_parents);
$actual_parents = $tree_term->parents;
sort($actual_parents);
$this->assertEquals($expected_parents, $actual_parents, "Term $tid has correct parents in vocabulary tree");
}
}
}

View File

@ -0,0 +1,48 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\taxonomy\Kernel\Migrate\d6;
use Drupal\taxonomy\Entity\Vocabulary;
use Drupal\Tests\migrate_drupal\Kernel\d6\MigrateDrupal6TestBase;
/**
* Migrate taxonomy vocabularies to taxonomy.vocabulary.*.yml.
*
* @group migrate_drupal_6
*/
class MigrateTaxonomyVocabularyTest extends MigrateDrupal6TestBase {
/**
* {@inheritdoc}
*/
protected static $modules = ['taxonomy'];
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
$this->executeMigration('d6_taxonomy_vocabulary');
}
/**
* Tests the Drupal 6 taxonomy vocabularies to Drupal 8 migration.
*/
public function testTaxonomyVocabulary(): void {
for ($i = 0; $i < 3; $i++) {
$j = $i + 1;
$vocabulary = Vocabulary::load("vocabulary_{$j}_i_{$i}_");
$this->assertSame($this->getMigration('d6_taxonomy_vocabulary')->getIdMap()->lookupDestinationIds([$j]), [[$vocabulary->id()]]);
$this->assertSame("vocabulary $j (i=$i)", $vocabulary->label());
$this->assertSame("description of vocabulary $j (i=$i)", $vocabulary->getDescription());
$this->assertSame(4 + $i, $vocabulary->get('weight'));
}
$vocabulary = Vocabulary::load('vocabulary_name_much_longer_th');
$this->assertSame('vocabulary name much longer than thirty two characters', $vocabulary->label());
$this->assertSame('description of vocabulary name much longer than thirty two characters', $vocabulary->getDescription());
$this->assertSame(7, $vocabulary->get('weight'));
}
}

View File

@ -0,0 +1,56 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\taxonomy\Kernel\Migrate\d6;
use Drupal\Tests\migrate_drupal\Kernel\d6\MigrateDrupal6TestBase;
/**
* Migrate taxonomy vocabularies to taxonomy.vocabulary.*.yml.
*
* @group migrate_drupal_6
*/
class MigrateTaxonomyVocabularyTranslationTest extends MigrateDrupal6TestBase {
/**
* {@inheritdoc}
*/
protected static $modules = [
'config_translation',
'language',
'taxonomy',
];
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
$this->executeMigrations([
'language',
'd6_taxonomy_vocabulary',
'd6_taxonomy_vocabulary_translation',
]);
}
/**
* Tests the Drupal 6 i18n taxonomy vocabularies to Drupal 8 migration.
*/
public function testTaxonomyVocabularyTranslation(): void {
$language_manager = \Drupal::service('language_manager');
$config = $language_manager->getLanguageConfigOverride('zu', 'taxonomy.vocabulary.vocabulary_1_i_0_');
$this->assertSame('zu - vocabulary 1 (i=0)', $config->get('name'));
$config = $language_manager->getLanguageConfigOverride('fr', 'taxonomy.vocabulary.vocabulary_1_i_0_');
$this->assertSame('fr - vocabulary 1 (i=0)', $config->get('name'));
$config = $language_manager->getLanguageConfigOverride('fr', 'taxonomy.vocabulary.vocabulary_2_i_1_');
$this->assertSame('fr - vocabulary 2 (i=1)', $config->get('name'));
$config = $language_manager->getLanguageConfigOverride('fr', 'taxonomy.vocabulary.vocabulary_3_i_2_');
$this->assertSame('fr - vocabulary 3 (i=2)', $config->get('name'));
$config = $language_manager->getLanguageConfigOverride('fr', 'taxonomy.vocabulary.vocabulary_name_much_longer_th');
$this->assertSame('Nom de vocabulaire beaucoup plus long que trente-deux caractères', $config->get('name'));
$config = $language_manager->getLanguageConfigOverride('fr', 'taxonomy.vocabulary.tags');
$this->assertSame('fr - Tags', $config->get('name'));
}
}

View File

@ -0,0 +1,146 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\taxonomy\Kernel\Migrate\d6;
use Drupal\Tests\migrate_drupal\Kernel\d6\MigrateDrupal6TestBase;
use Drupal\taxonomy\Entity\Term;
use Drupal\taxonomy\TermInterface;
/**
* Tests migration of localized translated taxonomy terms.
*
* @group migrate_drupal_6
*/
class MigrateTermLocalizedTranslationTest extends MigrateDrupal6TestBase {
/**
* {@inheritdoc}
*/
protected static $modules = [
'content_translation',
'language',
'menu_ui',
'node',
'taxonomy',
];
/**
* The cached taxonomy tree items, keyed by vid and tid.
*
* @var array
*/
protected $treeData = [];
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
$this->installEntitySchema('taxonomy_term');
$this->installConfig(static::$modules);
$this->executeMigrations([
'language',
'd6_node_type',
'd6_field',
'd6_taxonomy_vocabulary',
'd6_field_instance',
'd6_taxonomy_term',
'd6_taxonomy_term_localized_translation',
]);
}
/**
* Validates a migrated term contains the expected values.
*
* @param int $id
* Entity ID to load and check.
* @param string $expected_language
* The language code for this term.
* @param string $expected_label
* The label the migrated entity should have.
* @param string $expected_vid
* The parent vocabulary the migrated entity should have.
* @param string|null $expected_description
* The description the migrated entity should have.
* @param string|null $expected_format
* The format the migrated entity should have.
* @param int $expected_weight
* The weight the migrated entity should have.
* @param array $expected_parents
* The parent terms the migrated entity should have.
* @param int $expected_field_integer_value
* The value the migrated entity field should have.
* @param int $expected_term_reference_tid
* The term reference ID the migrated entity field should have.
*
* @internal
*/
protected function assertEntity(int $id, string $expected_language, string $expected_label, string $expected_vid, ?string $expected_description = '', ?string $expected_format = NULL, int $expected_weight = 0, array $expected_parents = [], ?int $expected_field_integer_value = NULL, ?int $expected_term_reference_tid = NULL): void {
/** @var \Drupal\taxonomy\TermInterface $entity */
$entity = Term::load($id);
$this->assertInstanceOf(TermInterface::class, $entity);
$this->assertSame($expected_language, $entity->language()->getId());
$this->assertSame($expected_label, $entity->label());
$this->assertSame($expected_vid, $entity->bundle());
$this->assertSame($expected_description, $entity->getDescription());
$this->assertSame($expected_format, $entity->getFormat());
$this->assertSame($expected_weight, $entity->getWeight());
$this->assertHierarchy($expected_vid, $id, $expected_parents);
}
/**
* Asserts that a term is present in the tree storage, with the right parents.
*
* @param string $vid
* Vocabulary ID.
* @param int $tid
* ID of the term to check.
* @param array $parent_ids
* The expected parent term IDs.
*
* @internal
*/
protected function assertHierarchy(string $vid, int $tid, array $parent_ids): void {
if (!isset($this->treeData[$vid])) {
$tree = \Drupal::entityTypeManager()->getStorage('taxonomy_term')->loadTree($vid);
$this->treeData[$vid] = [];
foreach ($tree as $item) {
$this->treeData[$vid][$item->tid] = $item;
}
}
$this->assertArrayHasKey($tid, $this->treeData[$vid], "Term $tid exists in taxonomy tree");
$term = $this->treeData[$vid][$tid];
$this->assertEquals($parent_ids, array_filter($term->parents), "Term $tid has correct parents in taxonomy tree");
}
/**
* Tests the Drupal 6 i18n localized taxonomy term to Drupal 8 migration.
*/
public function testTranslatedLocalizedTaxonomyTerms(): void {
$this->assertEntity(14, 'en', 'Talos IV', 'vocabulary_name_much_longer_th', 'The home of Captain Christopher Pike.', NULL, 0, []);
$this->assertEntity(15, 'en', 'Vulcan', 'vocabulary_name_much_longer_th', NULL, NULL, 0, []);
/** @var \Drupal\taxonomy\TermInterface $entity */
$entity = Term::load(14);
$this->assertTrue($entity->hasTranslation('fr'));
$translation = $entity->getTranslation('fr');
$this->assertSame('fr - Talos IV', $translation->label());
$this->assertSame('fr - The home of Captain Christopher Pike.', $translation->getDescription());
$this->assertTrue($entity->hasTranslation('zu'));
$translation = $entity->getTranslation('zu');
$this->assertSame('Talos IV', $translation->label());
$this->assertSame('zu - The home of Captain Christopher Pike.', $translation->getDescription());
$entity = Term::load(15);
$this->assertFalse($entity->hasTranslation('fr'));
$this->assertTrue($entity->hasTranslation('zu'));
$translation = $entity->getTranslation('zu');
$this->assertSame('zu - Vulcan', $translation->label());
$this->assertSame('', $translation->getDescription());
}
}

View File

@ -0,0 +1,75 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\taxonomy\Kernel\Migrate\d6;
use Drupal\Tests\migrate_drupal\Kernel\d6\MigrateDrupal6TestBase;
use Drupal\migrate_drupal\NodeMigrateType;
use Drupal\node\Entity\Node;
/**
* Upgrade taxonomy term node associations.
*
* @group migrate_drupal_6
*/
class MigrateTermNodeCompleteTest extends MigrateDrupal6TestBase {
/**
* {@inheritdoc}
*/
protected static $modules = [
'content_translation',
'language',
'menu_ui',
'taxonomy',
];
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
// Remove the classic node table made in setup.
$this->removeNodeMigrateMapTable(NodeMigrateType::NODE_MIGRATE_TYPE_CLASSIC, '6');
$this->installSchema('node', ['node_access']);
$this->installEntitySchema('node');
$this->executeMigration('language');
$this->migrateUsers(FALSE);
$this->migrateFields();
$this->executeMigrations(['d6_node_settings', 'd6_node_complete']);
$this->migrateTaxonomy();
// This is a base plugin ID and we want to run all derivatives.
$this->executeMigrations(['d6_term_node']);
}
/**
* Tests the Drupal 6 term-node association to Drupal 8 migration.
*/
public function testTermNode(): void {
$this->container->get('entity_type.manager')
->getStorage('node')
->resetCache([1, 2]);
$nodes = Node::loadMultiple([1, 2]);
$node = $nodes[1];
$this->assertCount(1, $node->field_vocabulary_1_i_0_);
$this->assertSame('1', $node->field_vocabulary_1_i_0_[0]->target_id);
$node = $nodes[2];
$this->assertCount(2, $node->field_vocabulary_2_i_1_);
$this->assertSame('2', $node->field_vocabulary_2_i_1_[0]->target_id);
$this->assertSame('3', $node->field_vocabulary_2_i_1_[1]->target_id);
// Tests the Drupal 6 term-node association to Drupal 8 node revisions.
$this->executeMigrations(['d6_term_node_revision']);
$node = \Drupal::entityTypeManager()->getStorage('node')->loadRevision(2001);
$this->assertCount(2, $node->field_vocabulary_3_i_2_);
$this->assertSame('4', $node->field_vocabulary_3_i_2_[0]->target_id);
$this->assertSame('5', $node->field_vocabulary_3_i_2_[1]->target_id);
}
}

View File

@ -0,0 +1,42 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\taxonomy\Kernel\Migrate\d6;
use Drupal\Tests\migrate_drupal\Kernel\d6\MigrateDrupal6TestBase;
/**
* Upgrade taxonomy term node associations.
*
* @group migrate_drupal_6
*/
class MigrateTermNodeRevisionTest extends MigrateDrupal6TestBase {
/**
* {@inheritdoc}
*/
protected static $modules = ['taxonomy', 'menu_ui'];
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
$this->installSchema('node', ['node_access']);
$this->migrateContent(['revisions']);
$this->migrateTaxonomy();
$this->executeMigrations(['d6_term_node', 'd6_term_node_revision']);
}
/**
* Tests the Drupal 6 term-node revision association to Drupal 8 migration.
*/
public function testTermRevisionNode(): void {
$node = \Drupal::entityTypeManager()->getStorage('node')->loadRevision(2001);
$this->assertCount(2, $node->field_vocabulary_3_i_2_);
$this->assertSame('4', $node->field_vocabulary_3_i_2_[0]->target_id);
$this->assertSame('5', $node->field_vocabulary_3_i_2_[1]->target_id);
}
}

View File

@ -0,0 +1,69 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\taxonomy\Kernel\Migrate\d6;
use Drupal\Tests\migrate_drupal\Kernel\d6\MigrateDrupal6TestBase;
use Drupal\node\Entity\Node;
/**
* Upgrade taxonomy term node associations.
*
* @group #slow
* @group migrate_drupal_6
*/
class MigrateTermNodeTest extends MigrateDrupal6TestBase {
/**
* {@inheritdoc}
*/
protected static $modules = ['taxonomy', 'menu_ui'];
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
$this->installSchema('node', ['node_access']);
$this->migrateContent();
$this->migrateTaxonomy();
}
/**
* Tests the Drupal 6 term-node association to Drupal 8 migration.
*/
public function testTermNode(): void {
// This is a base plugin id and we want to run all derivatives.
$this->executeMigrations(['d6_term_node']);
$this->container->get('entity_type.manager')
->getStorage('node')
->resetCache([1, 2]);
$nodes = Node::loadMultiple([1, 2]);
$node = $nodes[1];
$this->assertCount(1, $node->field_vocabulary_1_i_0_);
$this->assertSame('1', $node->field_vocabulary_1_i_0_[0]->target_id);
$node = $nodes[2];
$this->assertCount(2, $node->field_vocabulary_2_i_1_);
$this->assertSame('2', $node->field_vocabulary_2_i_1_[0]->target_id);
$this->assertSame('3', $node->field_vocabulary_2_i_1_[1]->target_id);
}
/**
* Tests that term relationships are ignored for un-migrated nodes.
*/
public function testSkipNonExistentNode(): void {
// Node 2 is migrated by d6_node__story, but we need to pretend that it
// failed, so record that in the map table.
$this->mockFailure('d6_node:story', ['nid' => 2, 'language' => 'en']);
// d6_term_node__2 should skip over node 2 (a.k.a. revision 3) because,
// according to the map table, it failed.
$migration = $this->getMigration('d6_term_node:2');
$this->executeMigration($migration);
$this->assertNull($migration->getIdMap()->lookupDestinationIds(['vid' => 3])[0][0]);
}
}

View File

@ -0,0 +1,83 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\taxonomy\Kernel\Migrate\d6;
use Drupal\Tests\migrate_drupal\Kernel\d6\MigrateDrupal6TestBase;
use Drupal\node\Entity\Node;
/**
* Upgrade taxonomy term node associations.
*
* @group migrate_drupal_6
* @group #slow
*/
class MigrateTermNodeTranslationTest extends MigrateDrupal6TestBase {
/**
* {@inheritdoc}
*/
protected static $modules = [
'config_translation',
'content_translation',
'language',
'locale',
'menu_ui',
'taxonomy',
];
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
$this->installEntitySchema('node');
$this->installConfig(['node']);
$this->installSchema('node', ['node_access']);
$this->executeMigration('language');
$this->executeMigration('d6_node_settings');
$this->migrateUsers(FALSE);
$this->migrateFields();
$this->migrateTaxonomy();
$this->migrateContent(['translations']);
// This is a base plugin id and we want to run all derivatives.
$this->executeMigrations([
'd6_term_node',
'd6_term_node_translation',
]);
}
/**
* Tests the Drupal 6 term-node association to Drupal 8 migration.
*/
public function testTermNode(): void {
$this->container->get('entity_type.manager')
->getStorage('node')
->resetCache([18, 21]);
// Test with translated content type employee. Vocabulary
// field_vocabulary_name_much_longe is a localized vocabulary and
// field_vocabulary_3_i_2_ is a per language vocabulary.
// An untranslated node.
$node = Node::load(18);
// A localized vocabulary.
$this->assertSame('15', $node->field_vocabulary_name_much_longe[0]->target_id);
// Per language vocabulary.
$this->assertSame('5', $node->field_vocabulary_3_i_2_[0]->target_id);
// A translated node.
// The English node.
$node = Node::load(21);
$this->assertSame('15', $node->field_vocabulary_name_much_longe[0]->target_id);
$this->assertSame('4', $node->field_vocabulary_3_i_2_[0]->target_id);
// The French translation of the English node.
$translation = $node->getTranslation('fr');
$this->assertSame('14', $translation->field_vocabulary_name_much_longe[0]->target_id);
$this->assertSame('9', $translation->field_vocabulary_3_i_2_[0]->target_id);
}
}

View File

@ -0,0 +1,80 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\taxonomy\Kernel\Migrate\d6;
use Drupal\Core\Entity\Entity\EntityViewDisplay;
use Drupal\Tests\migrate_drupal\Kernel\d6\MigrateDrupal6TestBase;
/**
* Vocabulary entity display migration.
*
* @group migrate_drupal_6
*/
class MigrateVocabularyEntityDisplayTest extends MigrateDrupal6TestBase {
/**
* {@inheritdoc}
*/
protected static $modules = ['field', 'taxonomy', 'menu_ui'];
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
// Execute Dependency Migrations.
$this->migrateContentTypes();
$this->installEntitySchema('taxonomy_term');
$this->executeMigrations([
'd6_node_type',
'd6_taxonomy_vocabulary',
'd6_vocabulary_field',
'd6_vocabulary_field_instance',
]);
}
/**
* Tests the Drupal 6 vocabulary-node type association to Drupal 8 migration.
*/
public function testVocabularyEntityDisplay(): void {
$this->executeMigration('d6_vocabulary_entity_display');
// Test that the field exists.
$component = EntityViewDisplay::load('node.page.default')->getComponent('field_tags');
$this->assertSame('entity_reference_label', $component['type']);
$this->assertSame(20, $component['weight']);
// Test the Id map.
$this->assertSame(
[['node', 'article', 'default', 'field_tags']],
$this->getMigration('d6_vocabulary_entity_display')->getIdMap()->lookupDestinationIds([4, 'article'])
);
// Tests that a vocabulary named like a D8 base field will be migrated and
// prefixed with 'field_' to avoid conflicts.
$field_type = EntityViewDisplay::load('node.sponsor.default')->getComponent('field_type');
$this->assertIsArray($field_type);
}
/**
* Tests that vocabulary displays are ignored appropriately.
*
* Vocabulary displays should be ignored when they belong to node types which
* were not migrated.
*/
public function testSkipNonExistentNodeType(): void {
// The "story" node type is migrated by d6_node_type but we need to pretend
// that it didn't occur, so record that in the map table.
$this->mockFailure('d6_node_type', ['type' => 'story']);
// d6_vocabulary_entity_display should skip over the "story" node type
// config because, according to the map table, it didn't occur.
$migration = $this->getMigration('d6_vocabulary_entity_display');
$this->executeMigration($migration);
$this->assertNull($migration->getIdMap()->lookupDestinationIds(['type' => 'story'])[0][0]);
}
}

View File

@ -0,0 +1,93 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\taxonomy\Kernel\Migrate\d6;
use Drupal\Core\Entity\Entity\EntityFormDisplay;
use Drupal\Tests\migrate_drupal\Kernel\d6\MigrateDrupal6TestBase;
/**
* Vocabulary entity form display migration.
*
* @group migrate_drupal_6
*/
class MigrateVocabularyEntityFormDisplayTest extends MigrateDrupal6TestBase {
/**
* {@inheritdoc}
*/
protected static $modules = ['taxonomy', 'menu_ui'];
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
// Execute Dependency Migrations.
$this->migrateContentTypes();
$this->installEntitySchema('taxonomy_term');
$this->executeMigrations([
'd6_taxonomy_vocabulary',
'd6_vocabulary_field',
'd6_vocabulary_field_instance',
'd6_vocabulary_entity_display',
]);
}
/**
* Tests the Drupal 6 vocabulary-node type association to Drupal 8 migration.
*/
public function testVocabularyEntityFormDisplay(): void {
$this->executeMigration('d6_vocabulary_entity_form_display');
// Test that the field exists.
$component = EntityFormDisplay::load('node.page.default')->getComponent('field_tags');
$this->assertSame('options_select', $component['type']);
$this->assertSame(20, $component['weight']);
// Test the Id map.
$this->assertSame([
[
'node',
'article',
'default',
'field_tags',
],
],
$this->getMigration('d6_vocabulary_entity_form_display')
->getIdMap()
->lookupDestinationIds([4, 'article'])
);
// Test the term widget tags setting.
$entity_form_display = EntityFormDisplay::load('node.story.default');
$this->assertSame($entity_form_display->getComponent('field_vocabulary_1_i_0_')['type'], 'options_select');
$this->assertSame($entity_form_display->getComponent('field_vocabulary_2_i_1_')['type'], 'entity_reference_autocomplete_tags');
// Tests that a vocabulary named like a D8 base field will be migrated and
// prefixed with 'field_' to avoid conflicts.
$field_type = EntityFormDisplay::load('node.sponsor.default')->getComponent('field_type');
$this->assertIsArray($field_type);
}
/**
* Tests that vocabulary displays are ignored appropriately.
*
* Vocabulary displays should be ignored when they belong to node types which
* were not migrated.
*/
public function testSkipNonExistentNodeType(): void {
// The "story" node type is migrated by d6_node_type but we need to pretend
// that it didn't occur, so record that in the map table.
$this->mockFailure('d6_node_type', ['type' => 'story']);
// d6_vocabulary_entity_form_display should skip over the "story" node type
// config because, according to the map table, it didn't occur.
$migration = $this->getMigration('d6_vocabulary_entity_form_display');
$this->executeMigration($migration);
$this->assertNull($migration->getIdMap()->lookupDestinationIds(['type' => 'story'])[0][0]);
}
}

View File

@ -0,0 +1,140 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\taxonomy\Kernel\Migrate\d6;
use Drupal\field\Entity\FieldConfig;
use Drupal\Tests\migrate_drupal\Kernel\d6\MigrateDrupal6TestBase;
/**
* Vocabulary field instance migration.
*
* @group migrate_drupal_6
*/
class MigrateVocabularyFieldInstanceTest extends MigrateDrupal6TestBase {
/**
* {@inheritdoc}
*/
protected static $modules = ['taxonomy', 'menu_ui'];
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
// Execute Dependency Migrations.
$this->migrateContentTypes();
$this->installEntitySchema('taxonomy_term');
$this->executeMigrations([
'd6_node_type',
'd6_taxonomy_vocabulary',
'd6_vocabulary_field',
]);
}
/**
* Tests the Drupal 6 vocabulary-node type association to Drupal 8 migration.
*/
public function testVocabularyFieldInstance(): void {
$this->executeMigration('d6_vocabulary_field_instance');
// Test that the field exists. Tags has a multilingual option of 'None'.
$field_id = 'node.article.field_tags';
$field = FieldConfig::load($field_id);
$this->assertSame($field_id, $field->id(), 'Field instance exists on article bundle.');
$this->assertSame('Tags', $field->label());
$this->assertTrue($field->isRequired(), 'Field is required');
$this->assertFalse($field->isTranslatable());
$this->assertTargetBundles($field_id, ['tags' => 'tags']);
// Test the page bundle as well. Tags has a multilingual option of 'None'.
$field_id = 'node.page.field_tags';
$field = FieldConfig::load($field_id);
$this->assertSame($field_id, $field->id(), 'Field instance exists on page bundle.');
$this->assertSame('Tags', $field->label());
$this->assertTrue($field->isRequired(), 'Field is required');
$this->assertFalse($field->isTranslatable());
$settings = $field->getSettings();
$this->assertSame('default:taxonomy_term', $settings['handler'], 'The handler plugin ID is correct.');
$this->assertTargetBundles($field_id, ['tags' => 'tags']);
$this->assertTrue($settings['handler_settings']['auto_create'], 'The "auto_create" setting is correct.');
$this->assertSame(
[['node', 'article', 'field_tags']],
$this->getMigration('d6_vocabulary_field_instance')->getIdMap()->lookupDestinationIds([4, 'article'])
);
// Test the field vocabulary_1_i_0_ with multilingual option,
// 'per language terms'.
$field_id = 'node.story.field_vocabulary_1_i_0_';
$field = FieldConfig::load($field_id);
$this->assertFalse($field->isRequired(), 'Field is not required');
$this->assertTrue($field->isTranslatable());
$this->assertTargetBundles($field_id, ['vocabulary_1_i_0_' => 'vocabulary_1_i_0_']);
// Test the field vocabulary_2_i_0_ with multilingual option,
// 'Set language to vocabulary'.
$field_id = 'node.story.field_vocabulary_2_i_1_';
$field = FieldConfig::load($field_id);
$this->assertFalse($field->isRequired(), 'Field is not required');
$this->assertFalse($field->isTranslatable());
$this->assertTargetBundles($field_id, ['vocabulary_2_i_1_' => 'vocabulary_2_i_1_']);
// Test the field vocabulary_3_i_0_ with multilingual option,
// 'Localize terms'.
$field_id = 'node.story.field_vocabulary_3_i_2_';
$field = FieldConfig::load($field_id);
$this->assertFalse($field->isRequired(), 'Field is not required');
$this->assertTrue($field->isTranslatable());
$this->assertTargetBundles($field_id, ['vocabulary_3_i_2_' => 'vocabulary_3_i_2_']);
// Tests that a vocabulary named like a D8 base field will be migrated and
// prefixed with 'field_' to avoid conflicts.
$field_type = FieldConfig::load('node.sponsor.field_type');
$this->assertInstanceOf(FieldConfig::class, $field_type);
$this->assertTrue($field->isTranslatable());
$this->assertTargetBundles($field_id, ['vocabulary_3_i_2_' => 'vocabulary_3_i_2_']);
$this->assertTargetBundles('node.employee.field_vocabulary_3_i_2_', ['vocabulary_3_i_2_' => 'vocabulary_3_i_2_']);
}
/**
* Asserts the settings of an entity reference field config entity.
*
* @param string $id
* The entity ID in the form ENTITY_TYPE.BUNDLE.FIELD_NAME.
* @param string[] $target_bundles
* An array of expected target bundles.
*/
protected function assertTargetBundles($id, array $target_bundles): void {
$field = FieldConfig::load($id);
$handler_settings = $field->getSetting('handler_settings');
$this->assertArrayHasKey('target_bundles', $handler_settings);
$this->assertSame($handler_settings['target_bundles'], $target_bundles);
}
/**
* Tests that vocabulary field instances are ignored appropriately.
*
* Vocabulary field instances should be ignored when they belong to node
* types which were not migrated.
*/
public function testSkipNonExistentNodeType(): void {
// The "story" node type is migrated by d6_node_type but we need to pretend
// that it didn't occur, so record that in the map table.
$this->mockFailure('d6_node_type', ['type' => 'story']);
// d6_vocabulary_field_instance should skip over the "story" node type
// config because, according to the map table, it didn't occur.
$migration = $this->getMigration('d6_vocabulary_field_instance');
$this->executeMigration($migration);
$this->assertNull($migration->getIdMap()->lookupDestinationIds(['type' => 'story'])[0][0]);
}
}

View File

@ -0,0 +1,52 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\taxonomy\Kernel\Migrate\d6;
use Drupal\field\Entity\FieldStorageConfig;
use Drupal\Tests\migrate_drupal\Kernel\d6\MigrateDrupal6TestBase;
/**
* Vocabulary field migration.
*
* @group migrate_drupal_6
*/
class MigrateVocabularyFieldTest extends MigrateDrupal6TestBase {
/**
* {@inheritdoc}
*/
protected static $modules = ['taxonomy', 'menu_ui'];
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
$this->migrateTaxonomy();
}
/**
* Tests the Drupal 6 vocabulary-node type association to Drupal 8 migration.
*/
public function testVocabularyField(): void {
// Test that the field exists.
$field_storage_id = 'node.field_tags';
/** @var \Drupal\field\FieldStorageConfigInterface $field_storage */
$field_storage = FieldStorageConfig::load($field_storage_id);
$this->assertSame($field_storage_id, $field_storage->id());
$settings = $field_storage->getSettings();
$this->assertSame('taxonomy_term', $settings['target_type'], "Target type is correct.");
$this->assertSame(1, $field_storage->getCardinality(), "Field cardinality in 1.");
$this->assertSame([['node', 'field_tags']], $this->getMigration('d6_vocabulary_field')->getIdMap()->lookupDestinationIds([4]), "Test IdMap");
// Tests that a vocabulary named like a D8 base field will be migrated and
// prefixed with 'field_' to avoid conflicts.
$field_type = FieldStorageConfig::load('node.field_type');
$this->assertInstanceOf(FieldStorageConfig::class, $field_type);
}
}

View File

@ -0,0 +1,56 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\taxonomy\Kernel\Migrate\d7;
use Drupal\Tests\migrate_drupal\Kernel\d7\MigrateDrupal7TestBase;
use Drupal\node\Entity\Node;
use Drupal\node\NodeInterface;
/**
* @group taxonomy
*/
class MigrateNodeTaxonomyTest extends MigrateDrupal7TestBase {
/**
* {@inheritdoc}
*/
protected static $modules = [
'comment',
'datetime',
'datetime_range',
'image',
'link',
'menu_ui',
'node',
'taxonomy',
'telephone',
'text',
];
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
$this->installEntitySchema('file');
$this->migrateTaxonomyTerms();
$this->migrateUsers(FALSE);
$this->executeMigration('d7_node:article');
}
/**
* Tests node migration from Drupal 7 to 8.
*/
public function testMigration(): void {
$node = Node::load(2);
$this->assertInstanceOf(NodeInterface::class, $node);
$this->assertEquals(9, $node->field_tags[0]->target_id);
$this->assertEquals(14, $node->field_tags[1]->target_id);
$this->assertEquals(17, $node->field_tags[2]->target_id);
}
}

View File

@ -0,0 +1,241 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\taxonomy\Kernel\Migrate\d7;
use Drupal\taxonomy\Entity\Term;
use Drupal\Tests\migrate_drupal\Kernel\d7\MigrateDrupal7TestBase;
use Drupal\taxonomy\TermInterface;
/**
* Upgrade taxonomy terms.
*
* @group taxonomy
*/
class MigrateTaxonomyTermTest extends MigrateDrupal7TestBase {
/**
* {@inheritdoc}
*/
protected static $modules = [
'comment',
'content_translation',
'datetime',
'datetime_range',
'image',
'language',
'link',
'menu_ui',
'node',
'taxonomy',
'telephone',
'text',
];
/**
* The cached taxonomy tree items, keyed by vid and tid.
*
* @var array
*/
protected $treeData = [];
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
$this->installEntitySchema('comment');
$this->installEntitySchema('file');
$this->migrateTaxonomyTerms();
$this->executeMigrations([
'language',
'd7_user_role',
'd7_user',
'd7_entity_translation_settings',
'd7_taxonomy_term_entity_translation',
]);
}
/**
* Validate a migrated term contains the expected values.
*
* @param int $id
* Entity ID to load and check.
* @param string $expected_language
* The language code for this term.
* @param string $expected_label
* The label the migrated entity should have.
* @param string $expected_vid
* The parent vocabulary the migrated entity should have.
* @param string|null $expected_description
* The description the migrated entity should have.
* @param string|null $expected_format
* The format the migrated entity should have.
* @param int $expected_weight
* The weight the migrated entity should have.
* @param array $expected_parents
* The parent terms the migrated entity should have.
* @param int $expected_field_integer_value
* The value the migrated entity field should have.
* @param int $expected_term_reference_tid
* The term reference id the migrated entity field should have.
* @param int|null $expected_container_flag
* The term should be a container entity.
*
* @internal
*/
protected function assertEntity(int $id, string $expected_language, string $expected_label, string $expected_vid, ?string $expected_description = '', ?string $expected_format = NULL, int $expected_weight = 0, array $expected_parents = [], ?int $expected_field_integer_value = NULL, ?int $expected_term_reference_tid = NULL, int|NULL $expected_container_flag = NULL): void {
/** @var \Drupal\taxonomy\TermInterface $entity */
$entity = Term::load($id);
$this->assertInstanceOf(TermInterface::class, $entity);
$this->assertSame($expected_language, $entity->language()->getId());
$this->assertEquals($expected_label, $entity->label());
$this->assertEquals($expected_vid, $entity->bundle());
$this->assertEquals($expected_description, $entity->getDescription());
$this->assertEquals($expected_format, $entity->getFormat());
$this->assertEquals($expected_weight, $entity->getWeight());
$this->assertEquals($expected_parents, $this->getParentIDs($id));
$this->assertHierarchy($expected_vid, $id, $expected_parents);
if (!is_null($expected_field_integer_value)) {
$this->assertTrue($entity->hasField('field_integer'));
$this->assertEquals($expected_field_integer_value, $entity->field_integer->value);
}
if (!is_null($expected_term_reference_tid)) {
$this->assertTrue($entity->hasField('field_integer'));
$this->assertEquals($expected_term_reference_tid, $entity->field_term_reference->target_id);
}
}
/**
* Tests the Drupal 7 taxonomy term to Drupal 8 migration.
*/
public function testTaxonomyTerms(): void {
$this->assertEntity(1, 'en', 'General discussion', 'sujet_de_discussion', '', NULL, 2);
// Tests that terms that used the Drupal 7 Title module and that have their
// name and description replaced by real fields are correctly migrated.
$this->assertEntity(2, 'en', 'Term1 (This is a real field!)', 'test_vocabulary', 'The first term. (This is a real field!)', 'filtered_html', 0, [], NULL, 3);
$this->assertEntity(3, 'en', 'Term2', 'test_vocabulary', 'The second term.', 'filtered_html');
$this->assertEntity(4, 'en', 'Term3 in plain old English', 'test_vocabulary', 'The third term in plain old English.', 'full_html', 0, [3], 6);
$this->assertEntity(5, 'en', 'Custom Forum', 'sujet_de_discussion', 'Where the cool kids are.', NULL, 3, [], NULL, NULL, 0);
$this->assertEntity(6, 'en', 'Games', 'sujet_de_discussion', NULL, '', 4, [], NULL, NULL, 1);
$this->assertEntity(7, 'en', 'Minecraft', 'sujet_de_discussion', '', NULL, 1, [6], NULL, NULL, 0);
$this->assertEntity(8, 'en', 'Half Life 3', 'sujet_de_discussion', '', NULL, 0, [6], NULL, NULL, 0);
// Test taxonomy term language translations.
$this->assertEntity(19, 'en', 'Jupiter Station', 'vocablocalized', 'Holographic research.', 'filtered_html', 0, [], NULL, NULL);
$this->assertEntity(20, 'en', 'DS9', 'vocablocalized', 'Terok Nor', 'filtered_html', 0, [], NULL, NULL);
$this->assertEntity(21, 'en', 'High council', 'vocabtranslate', NULL, NULL, 0, [], NULL, NULL);
$this->assertEntity(22, 'fr', 'fr - High council', 'vocabtranslate', NULL, NULL, 0, [], NULL, NULL);
$this->assertEntity(23, 'is', 'is - High council', 'vocabtranslate', NULL, NULL, 0, [], NULL, NULL);
$this->assertEntity(24, 'fr', 'FR - Crewman', 'vocabfixed', NULL, NULL, 0, [], NULL, NULL);
// Localized.
$this->assertEntity(19, 'en', 'Jupiter Station', 'vocablocalized', 'Holographic research.', 'filtered_html', 0, []);
$this->assertEntity(20, 'en', 'DS9', 'vocablocalized', 'Terok Nor', 'filtered_html', 0, []);
$this->assertEntity(25, 'en', 'Emissary', 'vocablocalized2', 'Pilot episode', 'filtered_html', 0, []);
/** @var \Drupal\taxonomy\TermInterface $entity */
$entity = Term::load(20);
$this->assertSame('Bajor', $entity->field_sector->value);
// Translate.
$this->assertEntity(21, 'en', 'High council', 'vocabtranslate', NULL, NULL, 0, []);
$entity = Term::load(21);
$this->assertSame("K'mpec", $entity->field_chancellor->value);
$this->assertEntity(22, 'fr', 'fr - High council', 'vocabtranslate', NULL, NULL, 0, []);
$this->assertEntity(23, 'is', 'is - High council', 'vocabtranslate', NULL, NULL, 0, []);
// Fixed.
$this->assertEntity(24, 'fr', 'FR - Crewman', 'vocabfixed', NULL, NULL, 0, []);
// Tests the migration of taxonomy term entity translations.
$manager = $this->container->get('content_translation.manager');
// Get the term and its translations.
$term = Term::load(4);
$term_fr = $term->getTranslation('fr');
$term_is = $term->getTranslation('is');
// Test that fields translated with Entity Translation are migrated.
$this->assertSame('Term3 in plain old English', $term->getName());
$this->assertSame('Term3 en français s\'il vous plaît', $term_fr->getName());
$this->assertSame('Term3 á íslensku', $term_is->getName());
$this->assertSame('The third term in plain old English.', $term->getDescription());
$this->assertSame('The third term en français s\'il vous plaît.', $term_fr->getDescription());
$this->assertSame('The third term á íslensku.', $term_is->getDescription());
$this->assertSame('full_html', $term->getFormat());
$this->assertSame('filtered_html', $term_fr->getFormat());
$this->assertSame('plain_text', $term_is->getFormat());
$this->assertSame('6', $term->field_integer->value);
$this->assertSame('5', $term_fr->field_integer->value);
$this->assertSame('4', $term_is->field_integer->value);
// Test that the French translation metadata is correctly migrated.
$metadata_fr = $manager->getTranslationMetadata($term_fr);
$this->assertTrue($metadata_fr->isPublished());
$this->assertSame('en', $metadata_fr->getSource());
$this->assertSame('2', $metadata_fr->getAuthor()->uid->value);
$this->assertSame('1531922267', $metadata_fr->getCreatedTime());
$this->assertSame(1531922268, $metadata_fr->getChangedTime());
$this->assertTrue($metadata_fr->isOutdated());
// Test that the Icelandic translation metadata is correctly migrated.
$metadata_is = $manager->getTranslationMetadata($term_is);
$this->assertFalse($metadata_is->isPublished());
$this->assertSame('en', $metadata_is->getSource());
$this->assertSame('1', $metadata_is->getAuthor()->uid->value);
$this->assertSame('1531922278', $metadata_is->getCreatedTime());
$this->assertSame(1531922279, $metadata_is->getChangedTime());
$this->assertFalse($metadata_is->isOutdated());
// Test that untranslatable properties are the same as the source language.
$this->assertSame($term->bundle(), $term_fr->bundle());
$this->assertSame($term->bundle(), $term_is->bundle());
$this->assertSame($term->getWeight(), $term_fr->getWeight());
$this->assertSame($term->getWeight(), $term_is->getWeight());
$this->assertSame($term->parent->terget_id, $term_fr->parent->terget_id);
$this->assertSame($term->parent->terget_id, $term_is->parent->terget_id);
}
/**
* Retrieves the parent term IDs for a given term.
*
* @param int $tid
* ID of the term to check.
*
* @return array
* List of parent term IDs.
*/
protected function getParentIDs($tid): array {
return array_keys(\Drupal::entityTypeManager()->getStorage('taxonomy_term')->loadParents($tid));
}
/**
* Assert that a term is present in the tree storage, with the right parents.
*
* @param string $vid
* Vocabulary ID.
* @param int $tid
* ID of the term to check.
* @param array $parent_ids
* The expected parent term IDs.
*/
protected function assertHierarchy(string $vid, int $tid, array $parent_ids): void {
if (!isset($this->treeData[$vid])) {
$tree = \Drupal::entityTypeManager()->getStorage('taxonomy_term')->loadTree($vid);
$this->treeData[$vid] = [];
foreach ($tree as $item) {
$this->treeData[$vid][$item->tid] = $item;
}
}
$this->assertArrayHasKey($tid, $this->treeData[$vid], "Term $tid exists in taxonomy tree");
$term = $this->treeData[$vid][$tid];
$this->assertEquals($parent_ids, array_filter($term->parents), "Term $tid has correct parents in taxonomy tree");
}
}

View File

@ -0,0 +1,184 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\taxonomy\Kernel\Migrate\d7;
use Drupal\taxonomy\Entity\Term;
use Drupal\Tests\migrate_drupal\Kernel\d7\MigrateDrupal7TestBase;
use Drupal\taxonomy\TermInterface;
/**
* Test migration of translated taxonomy terms.
*
* @group migrate_drupal_7
* @group #slow
*/
class MigrateTaxonomyTermTranslationTest extends MigrateDrupal7TestBase {
/**
* {@inheritdoc}
*/
protected static $modules = [
'comment',
'content_translation',
'datetime',
'datetime_range',
'image',
'language',
'link',
'menu_ui',
'node',
'taxonomy',
'telephone',
'text',
];
/**
* The cached taxonomy tree items, keyed by vid and tid.
*
* @var array
*/
protected $treeData = [];
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
$this->installEntitySchema('comment');
$this->installEntitySchema('file');
$this->installEntitySchema('taxonomy_term');
$this->migrateFields();
$this->executeMigrations([
'language',
'd7_language_content_taxonomy_vocabulary_settings',
'd7_taxonomy_vocabulary',
'd7_taxonomy_term',
'd7_entity_translation_settings',
'd7_taxonomy_term_entity_translation',
'd7_taxonomy_term_localized_translation',
'd7_taxonomy_term_translation',
]);
}
/**
* Validates a migrated term contains the expected values.
*
* @param int $id
* Entity ID to load and check.
* @param string $expected_language
* The language code for this term.
* @param string $expected_label
* The label the migrated entity should have.
* @param string $expected_vid
* The parent vocabulary the migrated entity should have.
* @param string|null $expected_description
* The description the migrated entity should have.
* @param string|null $expected_format
* The format the migrated entity should have.
* @param int $expected_weight
* The weight the migrated entity should have.
* @param array $expected_parents
* The parent terms the migrated entity should have.
* @param int $expected_field_integer_value
* The value the migrated entity field should have.
* @param int $expected_term_reference_tid
* The term reference ID the migrated entity field should have.
*
* @internal
*/
protected function assertEntity(int $id, string $expected_language, string $expected_label, string $expected_vid, ?string $expected_description = '', ?string $expected_format = NULL, int $expected_weight = 0, array $expected_parents = [], ?int $expected_field_integer_value = NULL, ?int $expected_term_reference_tid = NULL): void {
/** @var \Drupal\taxonomy\TermInterface $entity */
$entity = Term::load($id);
$this->assertInstanceOf(TermInterface::class, $entity);
$this->assertSame($expected_language, $entity->language()->getId());
$this->assertSame($expected_label, $entity->label());
$this->assertSame($expected_vid, $entity->bundle());
$this->assertSame($expected_description, $entity->getDescription());
$this->assertSame($expected_format, $entity->getFormat());
$this->assertSame($expected_weight, $entity->getWeight());
$this->assertHierarchy($expected_vid, $id, $expected_parents);
}
/**
* Asserts that a term is present in the tree storage, with the right parents.
*
* @param string $vid
* Vocabulary ID.
* @param int $tid
* ID of the term to check.
* @param array $parent_ids
* The expected parent term IDs.
*
* @internal
*/
protected function assertHierarchy(string $vid, int $tid, array $parent_ids): void {
if (!isset($this->treeData[$vid])) {
$tree = \Drupal::entityTypeManager()
->getStorage('taxonomy_term')
->loadTree($vid);
$this->treeData[$vid] = [];
foreach ($tree as $item) {
$this->treeData[$vid][$item->tid] = $item;
}
}
$this->assertArrayHasKey($tid, $this->treeData[$vid], "Term $tid exists in taxonomy tree");
$term = $this->treeData[$vid][$tid];
$this->assertEquals($parent_ids, array_filter($term->parents), "Term $tid has correct parents in taxonomy tree");
}
/**
* Tests the Drupal i18n taxonomy term to Drupal 8 migration.
*/
public function testTaxonomyTermTranslation(): void {
// Forums vocabulary, no multilingual option.
$this->assertEntity(1, 'en', 'General discussion', 'sujet_de_discussion', NULL, NULL, 2, []);
$this->assertEntity(5, 'en', 'Custom Forum', 'sujet_de_discussion', 'Where the cool kids are.', NULL, 3, []);
$this->assertEntity(6, 'en', 'Games', 'sujet_de_discussion', NULL, NULL, 4, []);
$this->assertEntity(7, 'en', 'Minecraft', 'sujet_de_discussion', NULL, NULL, 1, ['6']);
$this->assertEntity(8, 'en', 'Half Life 3', 'sujet_de_discussion', NULL, NULL, 0, ['6']);
// Test vocabulary, field translation.
$this->assertEntity(2, 'en', 'Term1 (This is a real field!)', 'test_vocabulary', 'The first term. (This is a real field!)', 'filtered_html', 0, []);
$this->assertEntity(3, 'en', 'Term2', 'test_vocabulary', 'The second term.', 'filtered_html', 0, []);
$this->assertEntity(4, 'en', 'Term3 in plain old English', 'test_vocabulary', 'The third term in plain old English.', 'full_html', 0, ['3']);
// Tags vocabulary, no multilingual option.
$this->assertEntity(9, 'en', 'Benjamin Sisko', 'tags', 'Portrayed by Avery Brooks', 'filtered_html', 0, []);
$this->assertEntity(10, 'en', 'Kira Nerys', 'tags', 'Portrayed by Nana Visitor', 'filtered_html', 0, []);
$this->assertEntity(11, 'en', 'Dax', 'tags', 'Portrayed by Terry Farrell', 'filtered_html', 0, []);
$this->assertEntity(12, 'en', 'Jake Sisko', 'tags', 'Portrayed by Cirroc Lofton', 'filtered_html', 0, []);
$this->assertEntity(13, 'en', 'Gul Dukat', 'tags', 'Portrayed by Marc Alaimo', 'filtered_html', 0, []);
$this->assertEntity(14, 'en', 'Odo', 'tags', 'Portrayed by Rene Auberjonois', 'filtered_html', 0, []);
$this->assertEntity(15, 'en', 'Worf', 'tags', 'Portrayed by Michael Dorn', 'filtered_html', 0, []);
$this->assertEntity(16, 'en', "Miles O'Brien", 'tags', 'Portrayed by Colm Meaney', 'filtered_html', 0, []);
$this->assertEntity(17, 'en', 'Quark', 'tags', 'Portrayed by Armin Shimerman', 'filtered_html', 0, []);
$this->assertEntity(18, 'en', 'Elim Garak', 'tags', 'Portrayed by Andrew Robinson', 'filtered_html', 0, []);
// Localized.
$this->assertEntity(19, 'en', 'Jupiter Station', 'vocablocalized', 'Holographic research.', 'filtered_html', 0, []);
$this->assertEntity(20, 'en', 'DS9', 'vocablocalized', 'Terok Nor', 'filtered_html', 0, []);
/** @var \Drupal\taxonomy\TermInterface $entity */
$entity = Term::load(20);
$this->assertSame('Bajor', $entity->field_sector->value);
// Translate.
$this->assertEntity(21, 'en', 'High council', 'vocabtranslate', NULL, NULL, 0, []);
$entity = Term::load(21);
$this->assertSame("K'mpec", $entity->field_chancellor->value);
$this->assertEntity(22, 'fr', 'fr - High council', 'vocabtranslate', NULL, NULL, 0, []);
$entity = Term::load(22);
$this->assertSame("fr - K'mpec", $entity->field_chancellor->value);
$this->assertEntity(23, 'is', 'is - High council', 'vocabtranslate', NULL, NULL, 0, []);
// Fixed.
$this->assertEntity(24, 'fr', 'FR - Crewman', 'vocabfixed', NULL, NULL, 0, []);
$entity = Term::load(24);
$this->assertSame('fr - specialist', $entity->field_training->value);
}
}

View File

@ -0,0 +1,64 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\taxonomy\Kernel\Migrate\d7;
use Drupal\taxonomy\Entity\Vocabulary;
use Drupal\Tests\migrate_drupal\Kernel\d7\MigrateDrupal7TestBase;
use Drupal\taxonomy\VocabularyInterface;
/**
* Migrate taxonomy vocabularies to taxonomy.vocabulary.*.yml.
*
* @group taxonomy
*/
class MigrateTaxonomyVocabularyTest extends MigrateDrupal7TestBase {
/**
* {@inheritdoc}
*/
protected static $modules = ['taxonomy', 'text'];
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
$this->executeMigration('d7_taxonomy_vocabulary');
}
/**
* Validate a migrated vocabulary contains the expected values.
*
* @param string $id
* Entity ID to load and check.
* @param string $expected_label
* The label the migrated entity should have.
* @param string $expected_description
* The description the migrated entity should have.
* @param string $expected_weight
* The weight the migrated entity should have.
*
* @internal
*/
protected function assertEntity(string $id, string $expected_label, string $expected_description, int $expected_weight): void {
/** @var \Drupal\taxonomy\VocabularyInterface $entity */
$entity = Vocabulary::load($id);
$this->assertInstanceOf(VocabularyInterface::class, $entity);
$this->assertSame($expected_label, $entity->label());
$this->assertSame($expected_description, $entity->getDescription());
$this->assertSame($expected_weight, (int) $entity->get('weight'));
}
/**
* Tests the Drupal 7 taxonomy vocabularies to Drupal 8 migration.
*/
public function testTaxonomyVocabulary(): void {
$this->assertEntity('tags', 'Tags', 'Use tags to group articles on similar topics into categories.', 0);
$this->assertEntity('sujet_de_discussion', 'Sujet de discussion', 'Forum navigation vocabulary', -10);
$this->assertEntity('test_vocabulary', 'Test Vocabulary', 'This is the vocabulary description', 0);
$this->assertEntity('vocabulary_name_much_longer_th', 'vocabulary name clearly different than machine name and much longer than thirty two characters', 'description of vocabulary name much longer than thirty two characters', 0);
}
}

View File

@ -0,0 +1,86 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\taxonomy\Kernel\Migrate\d7;
use Drupal\Tests\migrate_drupal\Kernel\d7\MigrateDrupal7TestBase;
/**
* Migrate taxonomy vocabularies to taxonomy.vocabulary.*.yml.
*
* @group migrate_drupal_7
*/
class MigrateTaxonomyVocabularyTranslationTest extends MigrateDrupal7TestBase {
/**
* {@inheritdoc}
*/
protected static $modules = [
'config_translation',
'language',
'taxonomy',
'text',
];
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
$this->executeMigrations([
'language',
'd7_taxonomy_vocabulary',
'd7_taxonomy_vocabulary_translation',
]);
}
/**
* Tests the Drupal 7 i18n taxonomy vocabularies to Drupal 8 migration.
*/
public function testTaxonomyVocabularyTranslation(): void {
/** @var \Drupal\language\ConfigurableLanguageManagerInterface $language_manager */
$language_manager = \Drupal::service('language_manager');
$config_translation = $language_manager->getLanguageConfigOverride('fr', 'taxonomy.vocabulary.sujet_de_discussion');
$this->assertNull($config_translation->get('name'));
$this->assertNull($config_translation->get('description'));
$config_translation = $language_manager->getLanguageConfigOverride('fr', 'taxonomy.vocabulary.tags');
$this->assertNull($config_translation->get('name'));
$this->assertNull($config_translation->get('description'));
$config_translation = $language_manager->getLanguageConfigOverride('fr', 'taxonomy.vocabulary.test_vocabulary');
$this->assertNull($config_translation->get('name'));
$this->assertNull($config_translation->get('description'));
$config_translation = $language_manager->getLanguageConfigOverride('is', 'taxonomy.vocabulary.test_vocabulary');
$this->assertNull($config_translation->get('name'));
$this->assertNull($config_translation->get('description'));
$config_translation = $language_manager->getLanguageConfigOverride('is', 'taxonomy.vocabulary.vocabulary_name_clearly_diffe');
$this->assertNull($config_translation->get('name'));
$this->assertNull($config_translation->get('description'));
$config_translation = $language_manager->getLanguageConfigOverride('fr', 'taxonomy.vocabulary.vocabulary_name_clearly_diffe');
$this->assertNull($config_translation->get('name'));
$this->assertNull($config_translation->get('description'));
$config_translation = $language_manager->getLanguageConfigOverride('fr', 'taxonomy.vocabulary.vocabfixed');
$this->assertSame('fr - VocabFixed', $config_translation->get('name'));
$this->assertNull($config_translation->get('description'));
$config_translation = $language_manager->getLanguageConfigOverride('fr', 'taxonomy.vocabulary.vocablocalized');
$this->assertSame('fr - VocabLocalized', $config_translation->get('name'));
$this->assertSame('fr - Vocabulary localize option', $config_translation->get('description'));
$config_translation = $language_manager->getLanguageConfigOverride('is', 'taxonomy.vocabulary.vocablocalized');
$this->assertSame('is - VocabLocalized', $config_translation->get('name'));
$this->assertSame('is - Vocabulary localize option', $config_translation->get('description'));
$config_translation = $language_manager->getLanguageConfigOverride('fr', 'taxonomy.vocabulary.vocabtranslate');
$this->assertNull($config_translation->get('name'));
$this->assertNull($config_translation->get('description'));
$config_translation = $language_manager->getLanguageConfigOverride('is', 'taxonomy.vocabulary.vocabtranslate');
$this->assertSame('is - VocabTranslate', $config_translation->get('name'));
$this->assertSame('is - Vocabulary translate option', $config_translation->get('description'));
}
}

View File

@ -0,0 +1,147 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\taxonomy\Kernel\Migrate\d7;
use Drupal\Tests\migrate_drupal\Kernel\d7\MigrateDrupal7TestBase;
use Drupal\taxonomy\Entity\Term;
use Drupal\taxonomy\TermInterface;
/**
* Tests migration of localized translated taxonomy terms.
*
* @group migrate_drupal_7
*/
class MigrateTermLocalizedTranslationTest extends MigrateDrupal7TestBase {
/**
* {@inheritdoc}
*/
protected static $modules = [
'content_translation',
'language',
'taxonomy',
'text',
];
/**
* The cached taxonomy tree items, keyed by vid and tid.
*
* @var array
*/
protected $treeData = [];
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
$this->installEntitySchema('taxonomy_term');
$this->installConfig(static::$modules);
$this->executeMigrations([
'language',
'd7_taxonomy_vocabulary',
'd7_taxonomy_term',
'd7_taxonomy_term_localized_translation',
]);
}
/**
* Validates a migrated term contains the expected values.
*
* @param int $id
* Entity ID to load and check.
* @param string $expected_language
* The language code for this term.
* @param string $expected_label
* The label the migrated entity should have.
* @param string $expected_vid
* The parent vocabulary the migrated entity should have.
* @param string $expected_description
* The description the migrated entity should have.
* @param string $expected_format
* The format the migrated entity should have.
* @param int $expected_weight
* The weight the migrated entity should have.
* @param array $expected_parents
* The parent terms the migrated entity should have.
* @param int $expected_field_integer_value
* The value the migrated entity field should have.
* @param int $expected_term_reference_tid
* The term reference ID the migrated entity field should have.
*
* @internal
*/
protected function assertEntity(int $id, string $expected_language, string $expected_label, string $expected_vid, string $expected_description = '', ?string $expected_format = NULL, int $expected_weight = 0, array $expected_parents = [], ?int $expected_field_integer_value = NULL, ?int $expected_term_reference_tid = NULL): void {
/** @var \Drupal\taxonomy\TermInterface $entity */
$entity = Term::load($id);
$this->assertInstanceOf(TermInterface::class, $entity);
$this->assertSame($expected_language, $entity->language()->getId());
$this->assertSame($expected_label, $entity->label());
$this->assertSame($expected_vid, $entity->bundle());
$this->assertSame($expected_description, $entity->getDescription());
$this->assertSame($expected_format, $entity->getFormat());
$this->assertSame($expected_weight, $entity->getWeight());
$this->assertHierarchy($expected_vid, $id, $expected_parents);
}
/**
* Asserts that a term is present in the tree storage, with the right parents.
*
* @param string $vid
* Vocabulary ID.
* @param int $tid
* ID of the term to check.
* @param array $parent_ids
* The expected parent term IDs.
*
* @internal
*/
protected function assertHierarchy(string $vid, int $tid, array $parent_ids): void {
if (!isset($this->treeData[$vid])) {
$tree = \Drupal::entityTypeManager()->getStorage('taxonomy_term')->loadTree($vid);
$this->treeData[$vid] = [];
foreach ($tree as $item) {
$this->treeData[$vid][$item->tid] = $item;
}
}
$this->assertArrayHasKey($tid, $this->treeData[$vid], "Term $tid exists in taxonomy tree");
$term = $this->treeData[$vid][$tid];
$this->assertEquals($parent_ids, array_filter($term->parents), "Term $tid has correct parents in taxonomy tree");
}
/**
* Tests the Drupal 6 i18n localized taxonomy term to Drupal 8 migration.
*/
public function testTranslatedLocalizedTaxonomyTerms(): void {
$this->assertEntity(19, 'en', 'Jupiter Station', 'vocablocalized', 'Holographic research.', 'filtered_html', 0, []);
$this->assertEntity(20, 'en', 'DS9', 'vocablocalized', 'Terok Nor', 'filtered_html', 0, []);
$this->assertEntity(25, 'en', 'Emissary', 'vocablocalized2', 'Pilot episode', 'filtered_html', 0, []);
/** @var \Drupal\taxonomy\TermInterface $entity */
$entity = Term::load(19);
$this->assertFalse($entity->hasTranslation('fr'));
$this->assertTrue($entity->hasTranslation('is'));
$translation = $entity->getTranslation('is');
$this->assertSame('Jupiter Station', $translation->label());
$this->assertSame('is - Holographic research. (localized)', $translation->getDescription());
$entity = Term::load(20);
$this->assertFalse($entity->hasTranslation('is'));
$this->assertTrue($entity->hasTranslation('fr'));
$translation = $entity->getTranslation('fr');
$this->assertSame('fr - DS9 (localized)', $translation->label());
$this->assertSame('fr - Terok Nor (localized)', $translation->getDescription());
$this->assertFALSE($entity->hasTranslation('is'));
$entity = Term::load(25);
$this->assertFalse($entity->hasTranslation('is'));
$this->assertTrue($entity->hasTranslation('fr'));
$translation = $entity->getTranslation('fr');
$this->assertSame('fr - Emissary', $translation->label());
$this->assertSame('fr - Pilot episode', $translation->getDescription());
}
}

View File

@ -0,0 +1,134 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\taxonomy\Kernel;
use Drupal\field\Entity\FieldConfig;
use Drupal\field\Entity\FieldStorageConfig;
use Drupal\KernelTests\KernelTestBase;
use Drupal\node\Entity\Node;
use Drupal\node\Entity\NodeType;
use Drupal\taxonomy\Entity\Term;
use Drupal\taxonomy\Entity\Vocabulary;
/**
* Kernel tests for taxonomy pending revisions.
*
* @group taxonomy
*/
class PendingRevisionTest extends KernelTestBase {
/**
* {@inheritdoc}
*/
protected static $modules = [
'taxonomy',
'node',
'user',
'text',
'field',
'system',
];
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
$this->installConfig(['taxonomy']);
$this->installEntitySchema('user');
$this->installEntitySchema('node');
$this->installEntitySchema('taxonomy_term');
$this->installSchema('node', 'node_access');
}
/**
* Tests that the taxonomy index work correctly with pending revisions.
*/
public function testTaxonomyIndexWithPendingRevision(): void {
\Drupal::configFactory()->getEditable('taxonomy.settings')->set('maintain_index_table', TRUE)->save();
Vocabulary::create([
'name' => 'test',
'vid' => 'test',
])->save();
$term = Term::create([
'name' => 'term1',
'vid' => 'test',
]);
$term->save();
$term2 = Term::create([
'name' => 'term2',
'vid' => 'test',
]);
$term2->save();
NodeType::create([
'type' => 'page',
'name' => 'Page',
])->save();
FieldStorageConfig::create([
'entity_type' => 'node',
'field_name' => 'field_tags',
'type' => 'entity_reference',
'settings' => [
'target_type' => 'taxonomy_term',
],
])->save();
FieldConfig::create([
'field_name' => 'field_tags',
'entity_type' => 'node',
'bundle' => 'page',
])->save();
$node = Node::create([
'type' => 'page',
'title' => 'test_title',
'field_tags' => [$term->id()],
]);
$node->save();
$taxonomy_index = $this->getTaxonomyIndex();
$this->assertEquals($term->id(), $taxonomy_index[$node->id()]->tid);
// Normal new revision.
$node->setNewRevision(TRUE);
$node->isDefaultRevision(TRUE);
$node->field_tags->target_id = $term2->id();
$node->save();
$taxonomy_index = $this->getTaxonomyIndex();
$this->assertEquals($term2->id(), $taxonomy_index[$node->id()]->tid);
// Check that saving a pending revision does not affect the taxonomy index.
$node->setNewRevision(TRUE);
$node->isDefaultRevision(FALSE);
$node->field_tags->target_id = $term->id();
$node->save();
$taxonomy_index = $this->getTaxonomyIndex();
$this->assertEquals($term2->id(), $taxonomy_index[$node->id()]->tid);
// Check that making the previously created pending revision the default
// revision updates the taxonomy index correctly.
$node->isDefaultRevision(TRUE);
$node->save();
$taxonomy_index = $this->getTaxonomyIndex();
$this->assertEquals($term->id(), $taxonomy_index[$node->id()]->tid);
}
/**
* Retrieves the taxonomy index from the database.
*/
protected function getTaxonomyIndex() {
return \Drupal::database()->select('taxonomy_index')
->fields('taxonomy_index')
->execute()
->fetchAllAssoc('nid');
}
}

View File

@ -0,0 +1,255 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\taxonomy\Kernel\Plugin\migrate\source\d6;
use Drupal\Tests\migrate\Kernel\MigrateSqlSourceTestBase;
// cspell:ignore objectid objectindex plid
/**
* Tests D6 i18n term localized source plugin.
*
* @covers \Drupal\taxonomy\Plugin\migrate\source\d6\TermLocalizedTranslation
* @group taxonomy
*/
class TermLocalizedTranslationTest extends MigrateSqlSourceTestBase {
/**
* {@inheritdoc}
*/
protected static $modules = ['taxonomy', 'migrate_drupal'];
/**
* {@inheritdoc}
*/
public static function providerSource() {
$tests = [];
// The source data.
$tests[0]['source_data']['term_data'] = [
[
'tid' => 1,
'vid' => 5,
'name' => 'name value 1',
'description' => 'description value 1',
'weight' => 0,
'language' => NULL,
],
[
'tid' => 2,
'vid' => 6,
'name' => 'name value 2',
'description' => 'description value 2',
'weight' => 0,
'language' => NULL,
],
[
'tid' => 3,
'vid' => 6,
'name' => 'name value 3',
'description' => 'description value 3',
'weight' => 0,
'language' => NULL,
],
[
'tid' => 4,
'vid' => 5,
'name' => 'name value 4',
'description' => 'description value 4',
'weight' => 1,
'language' => NULL,
],
];
$tests[0]['source_data']['term_hierarchy'] = [
[
'tid' => 1,
'parent' => 0,
],
[
'tid' => 2,
'parent' => 0,
],
[
'tid' => 3,
'parent' => 0,
],
[
'tid' => 4,
'parent' => 1,
],
];
$tests[0]['source_data']['i18n_strings'] = [
[
'lid' => 6,
'objectid' => 1,
'type' => 'term',
'property' => 'name',
'objectindex' => '1',
'format' => 0,
],
[
'lid' => 7,
'objectid' => 1,
'type' => 'term',
'property' => 'description',
'objectindex' => '1',
'format' => 0,
],
[
'lid' => 8,
'objectid' => 3,
'type' => 'term',
'property' => 'name',
'objectindex' => '3',
'format' => 0,
],
[
'lid' => 9,
'objectid' => 4,
'type' => 'term',
'property' => 'description',
'objectindex' => '4',
'format' => 0,
],
];
$tests[0]['source_data']['locales_target'] = [
[
'lid' => 6,
'language' => 'fr',
'translation' => 'fr - name value 1 translation',
'plid' => 0,
'plural' => 0,
'i18n_status' => 0,
],
[
'lid' => 7,
'language' => 'fr',
'translation' => 'fr - description value 1 translation',
'plid' => 0,
'plural' => 0,
'i18n_status' => 0,
],
[
'lid' => 8,
'language' => 'zu',
'translation' => 'zu - description value 2 translation',
'plid' => 0,
'plural' => 0,
'i18n_status' => 0,
],
];
// The expected results.
$tests[0]['expected_data'] = [
[
'tid' => 1,
'vid' => 5,
'name' => 'name value 1',
'description' => 'description value 1',
'weight' => 0,
'parent' => [0],
'property' => 'name',
'language' => 'fr',
'name_translated' => 'fr - name value 1 translation',
'description_translated' => 'fr - description value 1 translation',
],
[
'tid' => 1,
'vid' => 5,
'name' => 'name value 1',
'description' => 'description value 1',
'weight' => 0,
'parent' => [0],
'property' => 'description',
'language' => 'fr',
'name_translated' => 'fr - name value 1 translation',
'description_translated' => 'fr - description value 1 translation',
],
[
'tid' => 3,
'vid' => 6,
'name' => 'name value 3',
'description' => 'description value 3',
'weight' => 0,
'parent' => [0],
'property' => 'name',
'language' => 'zu',
'name_translated' => 'zu - description value 2 translation',
'description_translated' => NULL,
],
];
$tests[0]['expected_count'] = NULL;
// Empty configuration will return terms for all vocabularies.
$tests[0]['configuration'] = [];
// Test that only i18n_strings of type 'term' are returned.
$tests[1] = $tests[0];
$tests[0]['source_data']['i18n_strings'] = [
[
'lid' => 6,
'objectid' => 1,
'type' => 'term',
'property' => 'name',
'objectindex' => '1',
'format' => 0,
],
[
'lid' => 7,
'objectid' => 1,
'type' => 'term',
'property' => 'description',
'objectindex' => '1',
'format' => 0,
],
[
'lid' => 8,
'objectid' => 3,
'type' => 'not term',
'property' => 'name',
'objectindex' => '3',
'format' => 0,
],
[
'lid' => 9,
'objectid' => 4,
'type' => 'term',
'property' => 'description',
'objectindex' => '4',
'format' => 0,
],
];
// The expected results.
$tests[0]['expected_data'] = [
[
'tid' => 1,
'vid' => 5,
'name' => 'name value 1',
'description' => 'description value 1',
'weight' => 0,
'parent' => [0],
'property' => 'name',
'language' => 'fr',
'name_translated' => 'fr - name value 1 translation',
'description_translated' => 'fr - description value 1 translation',
],
[
'tid' => 1,
'vid' => 5,
'name' => 'name value 1',
'description' => 'description value 1',
'weight' => 0,
'parent' => [0],
'property' => 'description',
'language' => 'fr',
'name_translated' => 'fr - name value 1 translation',
'description_translated' => 'fr - description value 1 translation',
],
];
return $tests;
}
}

View File

@ -0,0 +1,112 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\taxonomy\Kernel\Plugin\migrate\source\d6;
use Drupal\Tests\migrate\Kernel\MigrateSqlSourceTestBase;
// cspell:ignore tnid
/**
* Tests d6_term_node source plugin.
*
* @covers \Drupal\taxonomy\Plugin\migrate\source\d6\TermNode
* @group taxonomy
*/
class TermNodeTest extends MigrateSqlSourceTestBase {
/**
* {@inheritdoc}
*/
protected static $modules = ['taxonomy', 'migrate_drupal'];
/**
* {@inheritdoc}
*/
public static function providerSource() {
$tests = [];
// The source data.
$tests[0]['source_data']['term_node'] = [
[
'nid' => '1',
'vid' => '1',
'tid' => '1',
],
[
'nid' => '1',
'vid' => '1',
'tid' => '4',
],
[
'nid' => '1',
'vid' => '1',
'tid' => '5',
],
];
$tests[0]['source_data']['node'] = [
[
'nid' => '1',
'vid' => '1',
'type' => 'story',
'language' => '',
'title' => 'Test title',
'uid' => '1',
'status' => '1',
'created' => '1388271197',
'changed' => '1420861423',
'comment' => '0',
'promote' => '0',
'moderate' => '0',
'sticky' => '0',
'tnid' => '0',
'translate' => '0',
],
];
$tests[0]['source_data']['term_data'] = [
[
'tid' => '1',
'vid' => '3',
'name' => 'term 1 of vocabulary 3',
'description' => 'description of term 1 of vocabulary 3',
'weight' => '0',
],
[
'tid' => '4',
'vid' => '3',
'name' => 'term 4 of vocabulary 3',
'description' => 'description of term 4 of vocabulary 3',
'weight' => '6',
],
[
'tid' => '5',
'vid' => '3',
'name' => 'term 5 of vocabulary 3',
'description' => 'description of term 5 of vocabulary 3',
'weight' => '7',
],
];
// The expected results.
$tests[0]['expected_data'] = [
[
'nid' => 1,
'vid' => 1,
'type' => 'story',
'tid' => [1, 4, 5],
],
];
// Set default value for expected count.
$tests[0]['expected_count'] = NULL;
// Set plugin configuration.
$tests[0]['configuration'] = [
'vid' => 3,
];
return $tests;
}
}

View File

@ -0,0 +1,58 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\taxonomy\Kernel\Plugin\migrate\source\d6;
/**
* Tests the taxonomy term source with vocabulary filter.
*
* @covers \Drupal\taxonomy\Plugin\migrate\source\d6\Term
* @group taxonomy
*/
class TermSourceWithVocabularyFilterTest extends TermTest {
/**
* {@inheritdoc}
*/
protected static $modules = ['taxonomy', 'migrate_drupal'];
/**
* {@inheritdoc}
*/
public static function providerSource() {
// Get the source data from parent.
$tests = parent::providerSource();
// The expected results.
$tests[0]['expected_data'] = [
[
'tid' => 1,
'vid' => 5,
'name' => 'name value 1',
'description' => 'description value 1',
'weight' => 0,
'parent' => [0],
],
[
'tid' => 4,
'vid' => 5,
'name' => 'name value 4',
'description' => 'description value 4',
'weight' => 1,
'parent' => [1],
],
];
// We know there are two rows with vid == 5.
$tests[0]['expected_count'] = 2;
// Set up source plugin configuration.
$tests[0]['configuration'] = [
'bundle' => [5],
];
return $tests;
}
}

View File

@ -0,0 +1,220 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\taxonomy\Kernel\Plugin\migrate\source\d6;
use Drupal\Tests\migrate\Kernel\MigrateSqlSourceTestBase;
/**
* Tests taxonomy term source plugin.
*
* @covers \Drupal\taxonomy\Plugin\migrate\source\d6\Term
* @group taxonomy
*/
class TermTest extends MigrateSqlSourceTestBase {
/**
* {@inheritdoc}
*/
protected static $modules = ['taxonomy', 'migrate_drupal'];
/**
* {@inheritdoc}
*/
public static function providerSource() {
$tests = [];
// The source data.
$tests[0]['source_data']['term_data'] = [
[
'tid' => 1,
'vid' => 5,
'name' => 'name value 1',
'description' => 'description value 1',
'weight' => 0,
],
[
'tid' => 2,
'vid' => 6,
'name' => 'name value 2',
'description' => 'description value 2',
'weight' => 0,
],
[
'tid' => 3,
'vid' => 6,
'name' => 'name value 3',
'description' => 'description value 3',
'weight' => 0,
],
[
'tid' => 4,
'vid' => 5,
'name' => 'name value 4',
'description' => 'description value 4',
'weight' => 1,
],
[
'tid' => 5,
'vid' => 6,
'name' => 'name value 5',
'description' => 'description value 5',
'weight' => 1,
],
[
'tid' => 6,
'vid' => 6,
'name' => 'name value 6',
'description' => 'description value 6',
'weight' => 0,
],
[
'tid' => 7,
'vid' => 3,
'name' => 'name value 7',
'description' => 'description value 7',
'weight' => 0,
],
];
$tests[0]['source_data']['term_hierarchy'] = [
[
'tid' => 1,
'parent' => 0,
],
[
'tid' => 2,
'parent' => 0,
],
[
'tid' => 3,
'parent' => 0,
],
[
'tid' => 4,
'parent' => 1,
],
[
'tid' => 5,
'parent' => 2,
],
[
'tid' => 6,
'parent' => 3,
],
[
'tid' => 6,
'parent' => 2,
],
[
'tid' => 7,
'parent' => 0,
],
];
// The expected results.
$tests[0]['expected_data'] = [
[
'tid' => 1,
'vid' => 5,
'name' => 'name value 1',
'description' => 'description value 1',
'weight' => 0,
'parent' => [0],
],
[
'tid' => 2,
'vid' => 6,
'name' => 'name value 2',
'description' => 'description value 2',
'weight' => 0,
'parent' => [0],
],
[
'tid' => 3,
'vid' => 6,
'name' => 'name value 3',
'description' => 'description value 3',
'weight' => 0,
'parent' => [0],
],
[
'tid' => 4,
'vid' => 5,
'name' => 'name value 4',
'description' => 'description value 4',
'weight' => 1,
'parent' => [1],
],
[
'tid' => 5,
'vid' => 6,
'name' => 'name value 5',
'description' => 'description value 5',
'weight' => 1,
'parent' => [2],
],
[
'tid' => 6,
'vid' => 6,
'name' => 'name value 6',
'description' => 'description value 6',
'weight' => 0,
'parent' => [3, 2],
],
[
'tid' => 7,
'vid' => 3,
'name' => 'name value 7',
'description' => 'description value 7',
'weight' => 0,
'parent' => [0],
],
];
$tests[0]['expected_count'] = NULL;
// Empty configuration will return terms for all vocabularies.
$tests[0]['configuration'] = [];
// Change configuration to get one vocabulary, 5.
$tests[1]['source_data'] = $tests[0]['source_data'];
$tests[1]['expected_data'] = [
[
'tid' => 1,
'vid' => 5,
'name' => 'name value 1',
'description' => 'description value 1',
'weight' => 0,
'parent' => [0],
],
[
'tid' => 4,
'vid' => 5,
'name' => 'name value 4',
'description' => 'description value 4',
'weight' => 1,
'parent' => [1],
],
];
$tests[1]['expected_count'] = NULL;
$tests[1]['configuration']['bundle'] = ['5'];
// Same as previous test, but with configuration vocabulary as a string
// instead of an array.
$tests[2]['source_data'] = $tests[0]['source_data'];
$tests[2]['expected_data'] = $tests[1]['expected_data'];
$tests[2]['expected_count'] = NULL;
$tests[2]['configuration']['bundle'] = '5';
// Change configuration to get two vocabularies, 5 and 6.
$tests[3]['source_data'] = $tests[0]['source_data'];
$tests[3]['expected_data'] = $tests[0]['expected_data'];
// Remove the last element because it is for vid 3.
array_pop($tests[3]['expected_data']);
$tests[3]['expected_count'] = NULL;
$tests[3]['configuration']['bundle'] = ['5', '6'];
return $tests;
}
}

View File

@ -0,0 +1,210 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\taxonomy\Kernel\Plugin\migrate\source\d6;
use Drupal\Tests\migrate\Kernel\MigrateSqlSourceTestBase;
// cspell:ignore trid
/**
* Tests taxonomy term source plugin.
*
* @covers \Drupal\taxonomy\Plugin\migrate\source\d6\Term
* @group taxonomy
*/
class TermTranslationTest extends MigrateSqlSourceTestBase {
/**
* {@inheritdoc}
*/
protected static $modules = ['taxonomy', 'migrate_drupal'];
/**
* {@inheritdoc}
*/
public static function providerSource() {
$tests = [];
// The source data.
$tests[0]['source_data']['term_data'] = [
[
'tid' => 1,
'vid' => 5,
'name' => 'name value 1',
'description' => 'description value 1',
'weight' => 0,
'language' => NULL,
'trid' => 0,
],
[
'tid' => 2,
'vid' => 6,
'name' => 'name value 2',
'description' => 'description value 2',
'weight' => 0,
'language' => NULL,
'trid' => 0,
],
[
'tid' => 3,
'vid' => 6,
'name' => 'name value 3',
'description' => 'description value 3',
'weight' => 0,
'language' => NULL,
'trid' => 0,
],
[
'tid' => 4,
'vid' => 5,
'name' => 'name value 4',
'description' => 'description value 4',
'weight' => 1,
'language' => NULL,
'trid' => 0,
],
[
'tid' => 5,
'vid' => 6,
'name' => 'name value 5',
'description' => 'description value 5',
'weight' => 1,
'language' => NULL,
'trid' => 0,
],
[
'tid' => 6,
'vid' => 6,
'name' => 'name value 6',
'description' => 'description value 6',
'weight' => 0,
'language' => NULL,
'trid' => 0,
],
[
'tid' => 10,
'vid' => 6,
'name' => 'zu - name value 2',
'description' => 'zu - description value 2',
'weight' => 0,
'language' => 'zu',
'trid' => 0,
],
];
$tests[0]['source_data']['term_hierarchy'] = [
[
'tid' => 1,
'parent' => 0,
],
[
'tid' => 2,
'parent' => 0,
],
[
'tid' => 3,
'parent' => 0,
],
[
'tid' => 4,
'parent' => 1,
],
[
'tid' => 5,
'parent' => 2,
],
[
'tid' => 6,
'parent' => 3,
],
[
'tid' => 6,
'parent' => 2,
],
[
'tid' => 10,
'parent' => 0,
],
];
// The expected results.
$tests[0]['expected_data'] = [
[
'tid' => 1,
'vid' => 5,
'name' => 'name value 1',
'description' => 'description value 1',
'weight' => 0,
'parent' => [0],
'language' => NULL,
'trid' => 0,
],
[
'tid' => 10,
'vid' => 6,
'name' => 'zu - name value 2',
'description' => 'zu - description value 2',
'weight' => 0,
'parent' => [0],
'language' => 'zu',
'trid' => 0,
],
[
'tid' => 2,
'vid' => 6,
'name' => 'name value 2',
'description' => 'description value 2',
'weight' => 0,
'parent' => [0],
'language' => NULL,
'trid' => 0,
],
[
'tid' => 3,
'vid' => 6,
'name' => 'name value 3',
'description' => 'description value 3',
'weight' => 0,
'parent' => [0],
'language' => NULL,
'trid' => 0,
],
[
'tid' => 4,
'vid' => 5,
'name' => 'name value 4',
'description' => 'description value 4',
'weight' => 1,
'parent' => [1],
'language' => NULL,
'trid' => 0,
],
[
'tid' => 5,
'vid' => 6,
'name' => 'name value 5',
'description' => 'description value 5',
'weight' => 1,
'parent' => [2],
'language' => NULL,
'trid' => 0,
],
[
'tid' => 6,
'vid' => 6,
'name' => 'name value 6',
'description' => 'description value 6',
'weight' => 0,
'parent' => [3, 2],
'language' => NULL,
'trid' => 0,
],
];
$tests[0]['expected_count'] = NULL;
return $tests;
}
}

View File

@ -0,0 +1,190 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\taxonomy\Kernel\Plugin\migrate\source\d6;
use Drupal\Core\Field\FieldStorageDefinitionInterface;
use Drupal\Tests\migrate\Kernel\MigrateSqlSourceTestBase;
/**
* Tests D6 vocabulary per type source plugin.
*
* @covers \Drupal\taxonomy\Plugin\migrate\source\d6\VocabularyPerType
* @group taxonomy
*/
class VocabularyPerTypeTest extends MigrateSqlSourceTestBase {
/**
* {@inheritdoc}
*/
protected static $modules = ['taxonomy', 'migrate_drupal'];
/**
* {@inheritdoc}
*/
public static function providerSource() {
$tests = [];
// The source data.
$tests[0]['source_data']['vocabulary'] = [
[
'vid' => 1,
'name' => 'Tags',
'description' => 'Tags description.',
'help' => 1,
'relations' => 0,
'hierarchy' => 0,
'multiple' => 0,
'required' => 0,
'tags' => 1,
'module' => 'taxonomy',
'weight' => 0,
],
[
'vid' => 2,
'name' => 'Categories',
'description' => 'Categories description.',
'help' => 1,
'relations' => 1,
'hierarchy' => 1,
'multiple' => 0,
'required' => 1,
'tags' => 0,
'module' => 'taxonomy',
'weight' => 0,
],
];
$tests[0]['source_data']['vocabulary_node_types'] = [
[
'vid' => 1,
'type' => 'page',
],
[
'vid' => 1,
'type' => 'article',
],
[
'vid' => 2,
'type' => 'article',
],
];
$tests[0]['source_data']['variable'] = [
[
'name' => 'i18ntaxonomy_vocabulary',
'value' => 'a:2:{i:1;s:1:"3";i:2;s:1:"2";}',
],
];
// The expected results.
$tests[0]['expected_data'] = [
[
'vid' => 1,
'name' => 'Tags',
'description' => 'Tags description.',
'help' => 1,
'relations' => 0,
'hierarchy' => 0,
'multiple' => 0,
'required' => 0,
'tags' => 1,
'module' => 'taxonomy',
'weight' => 0,
'node_types' => ['page', 'article'],
'cardinality' => FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED,
'i18ntaxonomy_vocabulary' => '3',
],
[
'vid' => 1,
'name' => 'Tags',
'description' => 'Tags description.',
'help' => 1,
'relations' => 0,
'hierarchy' => 0,
'multiple' => 0,
'required' => 0,
'tags' => 1,
'module' => 'taxonomy',
'weight' => 0,
'node_types' => ['page', 'article'],
'cardinality' => FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED,
'i18ntaxonomy_vocabulary' => '3',
],
[
'vid' => 2,
'name' => 'Categories',
'description' => 'Categories description.',
'help' => 1,
'relations' => 1,
'hierarchy' => 1,
'multiple' => 0,
'required' => 1,
'tags' => 0,
'module' => 'taxonomy',
'weight' => 0,
'node_types' => ['article'],
'cardinality' => 1,
'i18ntaxonomy_vocabulary' => '2',
],
];
// The source data.
$tests[1] = $tests[0];
unset($tests[1]['source_data']['variable']);
// The expected results.
$tests[1]['expected_data'] = [
[
'vid' => 1,
'name' => 'Tags',
'description' => 'Tags description.',
'help' => 1,
'relations' => 0,
'hierarchy' => 0,
'multiple' => 0,
'required' => 0,
'tags' => 1,
'module' => 'taxonomy',
'weight' => 0,
'node_types' => ['page', 'article'],
'cardinality' => FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED,
'i18ntaxonomy_vocabulary' => '',
],
[
'vid' => 1,
'name' => 'Tags',
'description' => 'Tags description.',
'help' => 1,
'relations' => 0,
'hierarchy' => 0,
'multiple' => 0,
'required' => 0,
'tags' => 1,
'module' => 'taxonomy',
'weight' => 0,
'node_types' => ['page', 'article'],
'cardinality' => FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED,
'i18ntaxonomy_vocabulary' => '',
],
[
'vid' => 2,
'name' => 'Categories',
'description' => 'Categories description.',
'help' => 1,
'relations' => 1,
'hierarchy' => 1,
'multiple' => 0,
'required' => 1,
'tags' => 0,
'module' => 'taxonomy',
'weight' => 0,
'node_types' => ['article'],
'cardinality' => 1,
'i18ntaxonomy_vocabulary' => '',
],
];
return $tests;
}
}

View File

@ -0,0 +1,110 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\taxonomy\Kernel\Plugin\migrate\source\d6;
use Drupal\Core\Field\FieldStorageDefinitionInterface;
use Drupal\Tests\migrate\Kernel\MigrateSqlSourceTestBase;
/**
* Tests D6 vocabulary source plugin.
*
* @covers \Drupal\taxonomy\Plugin\migrate\source\d6\Vocabulary
* @group taxonomy
*/
class VocabularyTest extends MigrateSqlSourceTestBase {
/**
* {@inheritdoc}
*/
protected static $modules = ['taxonomy', 'migrate_drupal'];
/**
* {@inheritdoc}
*/
public static function providerSource() {
$tests = [];
// The source data.
$tests[0]['source_data']['vocabulary'] = [
[
'vid' => 1,
'name' => 'Tags',
'description' => 'Tags description.',
'help' => 1,
'relations' => 0,
'hierarchy' => 0,
'multiple' => 0,
'required' => 0,
'tags' => 1,
'module' => 'taxonomy',
'weight' => 0,
],
[
'vid' => 2,
'name' => 'Categories',
'description' => 'Categories description.',
'help' => 1,
'relations' => 1,
'hierarchy' => 1,
'multiple' => 0,
'required' => 1,
'tags' => 0,
'module' => 'taxonomy',
'weight' => 0,
],
];
$tests[0]['source_data']['vocabulary_node_types'] = [
[
'vid' => 1,
'type' => 'page',
],
[
'vid' => 1,
'type' => 'article',
],
[
'vid' => 2,
'type' => 'article',
],
];
// The expected results.
$tests[0]['expected_data'] = [
[
'vid' => 1,
'name' => 'Tags',
'description' => 'Tags description.',
'help' => 1,
'relations' => 0,
'hierarchy' => 0,
'multiple' => 0,
'required' => 0,
'tags' => 1,
'module' => 'taxonomy',
'weight' => 0,
'node_types' => ['page', 'article'],
'cardinality' => FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED,
],
[
'vid' => 2,
'name' => 'Categories',
'description' => 'Categories description.',
'help' => 1,
'relations' => 1,
'hierarchy' => 1,
'multiple' => 0,
'required' => 1,
'tags' => 0,
'module' => 'taxonomy',
'weight' => 0,
'node_types' => ['article'],
'cardinality' => 1,
],
];
return $tests;
}
}

View File

@ -0,0 +1,151 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\taxonomy\Kernel\Plugin\migrate\source\d6;
use Drupal\Tests\migrate\Kernel\MigrateSqlSourceTestBase;
// cspell:ignore objectid objectindex plid
/**
* Tests D6 i18n vocabulary source plugin.
*
* @covers \Drupal\taxonomy\Plugin\migrate\source\d6\VocabularyTranslation
* @group taxonomy
*/
class VocabularyTranslationTest extends MigrateSqlSourceTestBase {
/**
* {@inheritdoc}
*/
protected static $modules = ['taxonomy', 'migrate_drupal'];
/**
* {@inheritdoc}
*/
public static function providerSource() {
$tests = [];
// The source data.
$tests[0][0]['i18n_strings'] = [
[
'lid' => 1,
'objectid' => 1,
'type' => 'vocabulary',
'property' => 'name',
'objectindex' => 1,
'format' => 0,
],
[
'lid' => 2,
'objectid' => 2,
'type' => 'vocabulary',
'property' => 'name',
'objectindex' => 2,
'format' => 0,
],
[
'lid' => 3,
'objectid' => 3,
'type' => 'vocabulary',
'property' => 'name',
'objectindex' => 3,
'format' => 0,
],
];
$tests[0][0]['locales_target'] = [
[
'lid' => 1,
'language' => 'fr',
'translation' => 'fr - vocabulary 1',
'plid' => 0,
'plural' => 0,
'i18n_status' => 0,
],
[
'lid' => 2,
'language' => 'fr',
'translation' => 'fr - vocabulary 2',
'plid' => 0,
'plural' => 0,
'i18n_status' => 0,
],
];
$tests[0][0]['vocabulary'] = [
[
'vid' => 1,
'name' => 'vocabulary 1',
'description' => 'description of vocabulary 1',
'help' => 1,
'relations' => 1,
'hierarchy' => 1,
'multiple' => 0,
'required' => 0,
'tags' => 0,
'module' => 'taxonomy',
'weight' => 4,
'language' => '',
],
[
'vid' => 2,
'name' => 'vocabulary 2',
'description' => 'description of vocabulary 2',
'help' => 1,
'relations' => 1,
'hierarchy' => 1,
'multiple' => 0,
'required' => 0,
'tags' => 0,
'module' => 'taxonomy',
'weight' => 5,
'language' => '',
],
[
'vid' => 3,
'name' => 'vocabulary 3',
'description' => 'description of vocabulary 3',
'help' => 1,
'relations' => 1,
'hierarchy' => 1,
'multiple' => 0,
'required' => 0,
'tags' => 0,
'module' => 'taxonomy',
'weight' => 5,
'language' => '',
],
];
$tests[0]['expected_data'] = [
[
'vid' => 1,
'name' => 'vocabulary 1',
'description' => 'description of vocabulary 1',
'lid' => '1',
'type' => 'vocabulary',
'property' => 'name',
'objectid' => '1',
'lt_lid' => '1',
'translation' => 'fr - vocabulary 1',
'language' => 'fr',
],
[
'vid' => 2,
'name' => 'vocabulary 2',
'description' => 'description of vocabulary 2',
'lid' => '2',
'type' => 'vocabulary',
'property' => 'name',
'objectid' => '2',
'lt_lid' => '2',
'translation' => 'fr - vocabulary 2',
'language' => 'fr',
],
];
return $tests;
}
}

View File

@ -0,0 +1,334 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\taxonomy\Kernel\Plugin\migrate\source\d7;
use Drupal\Tests\migrate\Kernel\MigrateSqlSourceTestBase;
/**
* Tests taxonomy term entity translation source plugin.
*
* @covers \Drupal\taxonomy\Plugin\migrate\source\d7\TermEntityTranslation
* @group taxonomy
*/
class TermEntityTranslationTest extends MigrateSqlSourceTestBase {
/**
* {@inheritdoc}
*/
protected static $modules = ['taxonomy', 'migrate_drupal'];
/**
* {@inheritdoc}
*/
public static function providerSource() {
$tests = [];
// The source data.
$tests[0]['source_data']['entity_translation'] = [
[
'entity_type' => 'taxonomy_term',
'entity_id' => 1,
'revision_id' => 1,
'language' => 'en',
'source' => '',
'uid' => 1,
'status' => 1,
'translate' => 0,
'created' => 1531343498,
'changed' => 1531343498,
],
[
'entity_type' => 'taxonomy_term',
'entity_id' => 1,
'revision_id' => 1,
'language' => 'fr',
'source' => 'en',
'uid' => 2,
'status' => 1,
'translate' => 1,
'created' => 1531343508,
'changed' => 1531343508,
],
[
'entity_type' => 'taxonomy_term',
'entity_id' => 1,
'revision_id' => 1,
'language' => 'es',
'source' => 'en',
'uid' => 1,
'status' => 0,
'translate' => 0,
'created' => 1531343528,
'changed' => 1531343528,
],
];
$tests[0]['source_data']['field_config'] = [
[
'id' => 1,
'field_name' => 'field_test',
'type' => 'text',
'module' => 'text',
'active' => 1,
'storage_type' => 'field_sql_storage',
'storage_module' => 'field_sql_storage',
'storage_active' => 1,
'locked' => 1,
'data' => 'a:0:{}',
'cardinality' => 1,
'translatable' => 1,
'deleted' => 0,
],
[
'id' => 2,
'field_name' => 'name_field',
'type' => 'text',
'module' => 'text',
'active' => 1,
'storage_type' => 'field_sql_storage',
'storage_module' => 'field_sql_storage',
'storage_active' => 1,
'locked' => 1,
'data' => 'a:0:{}',
'cardinality' => 1,
'translatable' => 1,
'deleted' => 0,
],
[
'id' => 3,
'field_name' => 'description_field',
'type' => 'text',
'module' => 'text',
'active' => 1,
'storage_type' => 'field_sql_storage',
'storage_module' => 'field_sql_storage',
'storage_active' => 1,
'locked' => 1,
'data' => 'a:0:{}',
'cardinality' => 1,
'translatable' => 1,
'deleted' => 0,
],
];
$tests[0]['source_data']['field_config_instance'] = [
[
'id' => '1',
'field_id' => 1,
'field_name' => 'field_test',
'entity_type' => 'taxonomy_term',
'bundle' => 'tags',
'data' => 'a:0:{}',
'deleted' => 0,
],
[
'id' => 2,
'field_id' => 2,
'field_name' => 'name_field',
'entity_type' => 'taxonomy_term',
'bundle' => 'tags',
'data' => 'a:0:{}',
'deleted' => 0,
],
[
'id' => 3,
'field_id' => 3,
'field_name' => 'description_field',
'entity_type' => 'taxonomy_term',
'bundle' => 'tags',
'data' => 'a:0:{}',
'deleted' => 0,
],
];
$tests[0]['source_data']['field_data_field_test'] = [
[
'entity_type' => 'taxonomy_term',
'bundle' => 'tags',
'deleted' => 0,
'entity_id' => 1,
'revision_id' => 1,
'language' => 'en',
'delta' => 0,
'field_test_value' => 'English field',
'field_test_format' => 'filtered_html',
],
[
'entity_type' => 'taxonomy_term',
'bundle' => 'tags',
'deleted' => 0,
'entity_id' => 1,
'revision_id' => 1,
'language' => 'fr',
'delta' => 0,
'field_test_value' => 'French field',
'field_test_format' => 'filtered_html',
],
[
'entity_type' => 'taxonomy_term',
'bundle' => 'tags',
'deleted' => 0,
'entity_id' => 1,
'revision_id' => 1,
'language' => 'es',
'delta' => 0,
'field_test_value' => 'Spanish field',
'field_test_format' => 'filtered_html',
],
];
$tests[0]['source_data']['field_data_name_field'] = [
[
'entity_type' => 'taxonomy_term',
'bundle' => 'tags',
'deleted' => '0',
'entity_id' => '1',
'revision_id' => '1',
'language' => 'en',
'delta' => '0',
'name_field_value' => 'Term Name EN',
'name_field_format' => NULL,
],
[
'entity_type' => 'taxonomy_term',
'bundle' => 'tags',
'deleted' => '0',
'entity_id' => '1',
'revision_id' => '1',
'language' => 'fr',
'delta' => '0',
'name_field_value' => 'Term Name FR',
'name_field_format' => NULL,
],
[
'entity_type' => 'taxonomy_term',
'bundle' => 'tags',
'deleted' => '0',
'entity_id' => '1',
'revision_id' => '1',
'language' => 'es',
'delta' => '0',
'name_field_value' => 'Term Name ES',
'name_field_format' => NULL,
],
];
$tests[0]['source_data']['field_data_description_field'] = [
[
'entity_type' => 'taxonomy_term',
'bundle' => 'tags',
'deleted' => '0',
'entity_id' => '1',
'revision_id' => '1',
'language' => 'en',
'delta' => '0',
'description_field_value' => 'Term Description EN',
'description_field_format' => 'full_html',
],
[
'entity_type' => 'taxonomy_term',
'bundle' => 'tags',
'deleted' => '0',
'entity_id' => '1',
'revision_id' => '1',
'language' => 'fr',
'delta' => '0',
'description_field_value' => 'Term Description FR',
'description_field_format' => 'full_html',
],
[
'entity_type' => 'taxonomy_term',
'bundle' => 'tags',
'deleted' => '0',
'entity_id' => '1',
'revision_id' => '1',
'language' => 'es',
'delta' => '0',
'description_field_value' => 'Term Description ES',
'description_field_format' => 'full_html',
],
];
$tests[0]['source_data']['system'] = [
[
'name' => 'title',
'type' => 'module',
'status' => 1,
],
];
$tests[0]['source_data']['taxonomy_term_data'] = [
[
'tid' => 1,
'vid' => 1,
'name' => 'Term Name',
'description' => 'Term Description',
'format' => 'filtered_html',
'weight' => 0,
],
];
$tests[0]['source_data']['taxonomy_term_hierarchy'] = [
[
'tid' => 1,
'parent' => 0,
],
];
$tests[0]['source_data']['taxonomy_vocabulary'] = [
[
'vid' => 1,
'name' => 'Tags',
'machine_name' => 'tags',
'description' => '',
'hierarchy' => 0,
'module' => 'taxonomy',
'weight' => 0,
],
];
// The expected results.
$tests[0]['expected_data'] = [
[
'entity_type' => 'taxonomy_term',
'entity_id' => 1,
'revision_id' => 1,
'language' => 'fr',
'source' => 'en',
'uid' => 2,
'status' => 1,
'translate' => 1,
'created' => 1531343508,
'changed' => 1531343508,
'name' => 'Term Name FR',
'description' => 'Term Description FR',
'format' => 'full_html',
'machine_name' => 'tags',
'field_test' => [
[
'value' => 'French field',
'format' => 'filtered_html',
],
],
],
[
'entity_type' => 'taxonomy_term',
'entity_id' => 1,
'revision_id' => 1,
'language' => 'es',
'source' => 'en',
'uid' => 1,
'status' => 0,
'translate' => 0,
'created' => 1531343528,
'changed' => 1531343528,
'name' => 'Term Name ES',
'description' => 'Term Description ES',
'format' => 'full_html',
'machine_name' => 'tags',
'field_test' => [
[
'value' => 'Spanish field',
'format' => 'filtered_html',
],
],
],
];
return $tests;
}
}

View File

@ -0,0 +1,236 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\taxonomy\Kernel\Plugin\migrate\source\d7;
// cspell:ignore ltlanguage objectid objectindex plid tdlanguage tsid
/**
* Tests D7 i18n term localized source plugin.
*
* @covers \Drupal\taxonomy\Plugin\migrate\source\d7\TermLocalizedTranslation
* @group taxonomy
*/
class TermLocalizedTranslationTest extends TermTest {
/**
* {@inheritdoc}
*/
protected static $modules = ['taxonomy', 'migrate_drupal'];
/**
* {@inheritdoc}
*/
public static function providerSource() {
$tests = parent::providerSource();
for ($i = 1; $i < 4; $i++) {
unset($tests[$i]);
}
foreach ($tests[0]['source_data']['taxonomy_term_data'] as $key => $value) {
$tests[0]['source_data']['taxonomy_term_data'][$key]['language'] = 'und';
$tests[0]['source_data']['taxonomy_term_data'][$key]['i18n_tsid'] = 0;
}
// The source data.
$tests[0]['source_data']['i18n_string'] = [
[
'lid' => 6,
'objectid' => 1,
'type' => 'term',
'property' => 'name',
'objectindex' => '1',
'format' => 0,
],
[
'lid' => 7,
'objectid' => 1,
'type' => 'term',
'property' => 'description',
'objectindex' => '1',
'format' => 0,
],
[
'lid' => 8,
'objectid' => 3,
'type' => 'term',
'property' => 'name',
'objectindex' => '3',
'format' => 0,
],
[
'lid' => 9,
'objectid' => 4,
'type' => 'term',
'property' => 'description',
'objectindex' => '4',
'format' => 0,
],
];
$tests[0]['source_data']['locales_target'] = [
[
'lid' => 6,
'language' => 'fr',
'translation' => 'fr - name value 1',
'plid' => 0,
'plural' => 0,
'i18n_status' => 0,
],
[
'lid' => 7,
'language' => 'fr',
'translation' => 'fr - description value 1',
'plid' => 0,
'plural' => 0,
'i18n_status' => 0,
],
[
'lid' => 8,
'language' => 'zu',
'translation' => 'zu - name value 3',
'plid' => 0,
'plural' => 0,
'i18n_status' => 0,
],
];
// The expected results.
$tests[0]['expected_data'] = [
[
'tid' => 1,
'vid' => 5,
'name' => 'name value 1 (name_field)',
'description' => 'description value 1 (description_field)',
'weight' => 0,
'language' => 'fr',
'i18n_tsid' => '0',
'machine_name' => 'tags',
'tdlanguage' => 'und',
'lid' => '6',
'property' => 'name',
'ltlanguage' => 'fr',
'translation' => 'fr - name value 1',
'name_translated' => 'fr - name value 1',
'description_translated' => 'fr - description value 1',
],
[
'tid' => 1,
'vid' => 5,
'name' => 'name value 1 (name_field)',
'description' => 'description value 1 (description_field)',
'weight' => 0,
'language' => 'fr',
'i18n_tsid' => '0',
'machine_name' => 'tags',
'tdlanguage' => 'und',
'lid' => '7',
'property' => 'description',
'ltlanguage' => 'fr',
'translation' => 'fr - description value 1',
'name_translated' => 'fr - name value 1',
'description_translated' => 'fr - description value 1',
],
];
$tests[0]['expected_count'] = NULL;
// Get translations for the tags bundle.
$tests[0]['configuration']['bundle'] = ['tags'];
// The source data.
$tests[1] = $tests[0];
array_push($tests[1]['source_data']['i18n_string'],
[
'lid' => 10,
'objectid' => 5,
'type' => 'term',
'property' => 'name',
'objectindex' => '5',
'format' => 0,
],
[
'lid' => 11,
'objectid' => 5,
'type' => 'term',
'property' => 'description',
'objectindex' => '5',
'format' => 0,
]);
array_push($tests[1]['source_data']['locales_target'],
[
'lid' => 10,
'language' => 'fr',
'translation' => 'fr - name value 5',
'plid' => 0,
'plural' => 0,
'i18n_status' => 0,
],
[
'lid' => 11,
'language' => 'fr',
'translation' => 'fr - description value 5',
'plid' => 0,
'plural' => 0,
'i18n_status' => 0,
]);
// The expected results.
array_push($tests[1]['expected_data'],
[
'tid' => 3,
'vid' => 6,
'name' => 'name value 3',
'description' => 'description value 3',
'weight' => 0,
'language' => 'zu',
'i18n_tsid' => '0',
'machine_name' => 'categories',
'tdlanguage' => 'und',
'lid' => '8',
'property' => 'name',
'ltlanguage' => 'zu',
'translation' => 'zu - name value 3',
],
[
'tid' => 5,
'vid' => 6,
'name' => 'name value 5',
'description' => 'description value 5',
'weight' => 1,
'language' => 'fr',
'i18n_tsid' => '0',
'machine_name' => 'categories',
'tdlanguage' => 'und',
'lid' => '10',
'property' => 'name',
'ltlanguage' => 'fr',
'translation' => 'fr - name value 5',
'name_translated' => 'fr - name value 5',
'description_translated' => 'fr - description value 5',
],
[
'tid' => 5,
'vid' => 6,
'name' => 'name value 5',
'description' => 'description value 5',
'weight' => 1,
'language' => 'fr',
'i18n_tsid' => '0',
'machine_name' => 'categories',
'tdlanguage' => 'und',
'lid' => '11',
'property' => 'description',
'ltlanguage' => 'fr',
'translation' => 'fr - description value 5',
'name_translated' => 'fr - name value 5',
'description_translated' => 'fr - description value 5',
]);
$tests[1]['expected_count'] = NULL;
// Empty configuration will return terms for all vocabularies.
$tests[1]['configuration'] = [];
return $tests;
}
}

View File

@ -0,0 +1,58 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\taxonomy\Kernel\Plugin\migrate\source\d7;
/**
* Tests the taxonomy term source with vocabulary filter.
*
* @covers \Drupal\taxonomy\Plugin\migrate\source\d7\Term
* @group taxonomy
*/
class TermSourceWithVocabularyFilterTest extends TermTest {
/**
* {@inheritdoc}
*/
protected static $modules = ['taxonomy', 'migrate_drupal'];
/**
* {@inheritdoc}
*/
public static function providerSource() {
// Get the source data from parent.
$tests = parent::providerSource();
// The expected results.
$tests[0]['expected_data'] = [
[
'tid' => 1,
'vid' => 5,
'name' => 'name value 1 (name_field)',
'description' => 'description value 1 (description_field)',
'weight' => 0,
'parent' => [0],
],
[
'tid' => 4,
'vid' => 5,
'name' => 'name value 4 (name_field)',
'description' => 'description value 4 (description_field)',
'weight' => 1,
'parent' => [1],
],
];
// We know there are two rows with machine_name == 'tags'.
$tests[0]['expected_count'] = 2;
// Set up source plugin configuration.
$tests[0]['configuration'] = [
'bundle' => ['tags'],
];
return $tests;
}
}

View File

@ -0,0 +1,372 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\taxonomy\Kernel\Plugin\migrate\source\d7;
use Drupal\Tests\migrate\Kernel\MigrateSqlSourceTestBase;
/**
* Tests taxonomy term source plugin.
*
* @covers \Drupal\taxonomy\Plugin\migrate\source\d7\Term
* @group taxonomy
*/
class TermTest extends MigrateSqlSourceTestBase {
/**
* {@inheritdoc}
*/
protected static $modules = ['taxonomy', 'migrate_drupal'];
/**
* {@inheritdoc}
*/
public static function providerSource() {
$tests = [];
// The source data.
$tests[0]['source_data']['taxonomy_term_data'] = [
[
'tid' => 1,
'vid' => 5,
'name' => 'name value 1',
'description' => 'description value 1',
'weight' => 0,
],
[
'tid' => 2,
'vid' => 6,
'name' => 'name value 2',
'description' => 'description value 2',
'weight' => 0,
],
[
'tid' => 3,
'vid' => 6,
'name' => 'name value 3',
'description' => 'description value 3',
'weight' => 0,
],
[
'tid' => 4,
'vid' => 5,
'name' => 'name value 4',
'description' => 'description value 4',
'weight' => 1,
],
[
'tid' => 5,
'vid' => 6,
'name' => 'name value 5',
'description' => 'description value 5',
'weight' => 1,
],
[
'tid' => 6,
'vid' => 6,
'name' => 'name value 6',
'description' => 'description value 6',
'weight' => 0,
],
[
'tid' => 7,
'vid' => 3,
'name' => 'name value 7',
'description' => 'description value 7',
'weight' => 0,
],
];
$tests[0]['source_data']['taxonomy_term_hierarchy'] = [
[
'tid' => 1,
'parent' => 0,
],
[
'tid' => 2,
'parent' => 0,
],
[
'tid' => 3,
'parent' => 0,
],
[
'tid' => 4,
'parent' => 1,
],
[
'tid' => 5,
'parent' => 2,
],
[
'tid' => 6,
'parent' => 3,
],
[
'tid' => 6,
'parent' => 2,
],
[
'tid' => 7,
'parent' => 0,
],
];
$tests[0]['source_data']['taxonomy_vocabulary'] = [
[
'vid' => 5,
'machine_name' => 'tags',
],
[
'vid' => 6,
'machine_name' => 'categories',
],
];
$tests[0]['source_data']['field_config'] = [
[
'id' => '3',
'translatable' => '0',
],
[
'id' => '4',
'translatable' => '1',
],
[
'id' => '5',
'translatable' => '1',
],
];
$tests[0]['source_data']['field_config_instance'] = [
[
'id' => '2',
'field_id' => 3,
'field_name' => 'field_term_field',
'entity_type' => 'taxonomy_term',
'bundle' => 'tags',
'data' => 'a:0:{}',
'deleted' => 0,
],
[
'id' => '3',
'field_id' => 3,
'field_name' => 'field_term_field',
'entity_type' => 'taxonomy_term',
'bundle' => 'categories',
'data' => 'a:0:{}',
'deleted' => 0,
],
[
'id' => '4',
'field_id' => '4',
'field_name' => 'name_field',
'entity_type' => 'taxonomy_term',
'bundle' => 'tags',
'data' => 'a:0:{}',
'deleted' => '0',
],
[
'id' => '5',
'field_id' => '5',
'field_name' => 'description_field',
'entity_type' => 'taxonomy_term',
'bundle' => 'tags',
'data' => 'a:0:{}',
'deleted' => '0',
],
];
$tests[0]['source_data']['field_data_field_term_field'] = [
[
'entity_type' => 'taxonomy_term',
'bundle' => 'tags',
'deleted' => 0,
'entity_id' => 1,
'delta' => 0,
],
[
'entity_type' => 'taxonomy_term',
'bundle' => 'categories',
'deleted' => 0,
'entity_id' => 1,
'delta' => 0,
],
];
$tests[0]['source_data']['field_data_name_field'] = [
[
'entity_type' => 'taxonomy_term',
'bundle' => 'tags',
'deleted' => '0',
'entity_id' => '1',
'revision_id' => '1',
'language' => 'und',
'delta' => '0',
'name_field_value' => 'name value 1 (name_field)',
'name_field_format' => NULL,
],
[
'entity_type' => 'taxonomy_term',
'bundle' => 'tags',
'deleted' => '0',
'entity_id' => '4',
'revision_id' => '4',
'language' => 'und',
'delta' => '0',
'name_field_value' => 'name value 4 (name_field)',
'name_field_format' => NULL,
],
];
$tests[0]['source_data']['field_data_description_field'] = [
[
'entity_type' => 'taxonomy_term',
'bundle' => 'tags',
'deleted' => '0',
'entity_id' => '1',
'revision_id' => '1',
'language' => 'und',
'delta' => '0',
'description_field_value' => 'description value 1 (description_field)',
'description_field_format' => NULL,
],
[
'entity_type' => 'taxonomy_term',
'bundle' => 'tags',
'deleted' => '0',
'entity_id' => '4',
'revision_id' => '4',
'language' => 'und',
'delta' => '0',
'description_field_value' => 'description value 4 (description_field)',
'description_field_format' => NULL,
],
];
$tests[0]['source_data']['system'] = [
[
'name' => 'title',
'type' => 'module',
'status' => 1,
],
];
$tests[0]['source_data']['variable'] = [
[
'name' => 'forum_containers',
'value' => 'a:3:{i:0;s:1:"5";i:1;s:1:"6";i:2;s:1:"7";}',
],
[
'name' => 'language_default',
'value' => 'O:8:"stdClass":1:{s:8:"language";s:2:"en";}',
],
];
// The expected results.
$tests[0]['expected_data'] = [
[
'tid' => 1,
'vid' => 5,
'name' => 'name value 1 (name_field)',
'description' => 'description value 1 (description_field)',
'weight' => 0,
'parent' => [0],
'language' => 'en',
],
[
'tid' => 2,
'vid' => 6,
'name' => 'name value 2',
'description' => 'description value 2',
'weight' => 0,
'parent' => [0],
'language' => 'en',
],
[
'tid' => 3,
'vid' => 6,
'name' => 'name value 3',
'description' => 'description value 3',
'weight' => 0,
'parent' => [0],
'language' => 'en',
],
[
'tid' => 4,
'vid' => 5,
'name' => 'name value 4 (name_field)',
'description' => 'description value 4 (description_field)',
'weight' => 1,
'parent' => [1],
'language' => 'en',
],
[
'tid' => 5,
'vid' => 6,
'name' => 'name value 5',
'description' => 'description value 5',
'weight' => 1,
'parent' => [2],
'language' => 'en',
],
[
'tid' => 6,
'vid' => 6,
'name' => 'name value 6',
'description' => 'description value 6',
'weight' => 0,
'parent' => [3, 2],
'language' => 'en',
],
[
'tid' => 7,
'vid' => 3,
'name' => 'name value 7',
'description' => 'description value 7',
'weight' => 0,
'parent' => [0],
'language' => 'en',
],
];
$tests[0]['expected_count'] = NULL;
// Empty configuration will return terms for all vocabularies.
$tests[0]['configuration'] = [];
// Change configuration to get one vocabulary, "tags".
$tests[1]['source_data'] = $tests[0]['source_data'];
$tests[1]['expected_data'] = [
[
'tid' => 1,
'vid' => 5,
'name' => 'name value 1 (name_field)',
'description' => 'description value 1 (description_field)',
'weight' => 0,
'parent' => [0],
'language' => 'en',
],
[
'tid' => 4,
'vid' => 5,
'name' => 'name value 4 (name_field)',
'description' => 'description value 4 (description_field)',
'weight' => 1,
'parent' => [1],
'language' => 'en',
],
];
$tests[1]['expected_count'] = NULL;
$tests[1]['configuration']['bundle'] = ['tags'];
// Same as previous test, but with configuration vocabulary as a string
// instead of an array.
$tests[2]['source_data'] = $tests[0]['source_data'];
$tests[2]['expected_data'] = $tests[1]['expected_data'];
$tests[2]['expected_count'] = NULL;
$tests[2]['configuration']['bundle'] = 'tags';
// Change configuration to get two vocabularies, "tags" and "categories".
$tests[3]['source_data'] = $tests[0]['source_data'];
$tests[3]['expected_data'] = $tests[0]['expected_data'];
// Remove the last element because it is for vid 3.
array_pop($tests[3]['expected_data']);
$tests[3]['expected_count'] = NULL;
$tests[3]['configuration']['bundle'] = ['tags', 'categories'];
return $tests;
}
}

View File

@ -0,0 +1,354 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\taxonomy\Kernel\Plugin\migrate\source\d7;
// cspell:ignore tsid
/**
* Tests D7 i18n term localized source plugin.
*
* @covers \Drupal\taxonomy\Plugin\migrate\source\d7\TermTranslation
* @group taxonomy
*/
class TermTranslationTest extends TermTest {
/**
* {@inheritdoc}
*/
protected static $modules = ['taxonomy', 'migrate_drupal'];
/**
* {@inheritdoc}
*/
public static function providerSource() {
$tests = [];
// Ignore i18_modes 0 and 1, get i18n_mode 4.
$tests[0]['source_data']['taxonomy_term_data'] = [
[
'tid' => 1,
'vid' => 5,
'name' => 'fr - name 1',
'description' => 'desc 1',
'weight' => 0,
'language' => 'fr',
'i18n_tsid' => '1',
],
[
'tid' => 2,
'vid' => 5,
'name' => 'name 2',
'description' => 'desc 2',
'weight' => 0,
'language' => 'en',
'i18n_tsid' => '1',
],
[
'tid' => 3,
'vid' => 6,
'name' => 'name 3',
'description' => 'desc 3',
'weight' => 0,
'language' => '',
'i18n_tsid' => '',
],
[
'tid' => 4,
'vid' => 5,
'name' => 'is - name 4',
'description' => 'desc 4',
'weight' => 1,
'language' => 'is',
'i18n_tsid' => '1',
],
[
'tid' => 5,
'vid' => 6,
'name' => 'name 5',
'description' => 'desc 5',
'weight' => 1,
'language' => '',
'i18n_tsid' => '',
],
[
'tid' => 6,
'vid' => 6,
'name' => 'name 6',
'description' => 'desc 6',
'weight' => 0,
'language' => '',
'i18n_tsid' => '',
],
[
'tid' => 7,
'vid' => 7,
'name' => 'is - captains',
'description' => 'desc 7',
'weight' => 0,
'language' => 'is',
'i18n_tsid' => '',
],
];
$tests[0]['source_data']['taxonomy_term_hierarchy'] = [
[
'tid' => 1,
'parent' => 0,
],
[
'tid' => 2,
'parent' => 0,
],
[
'tid' => 3,
'parent' => 0,
],
[
'tid' => 4,
'parent' => 1,
],
[
'tid' => 5,
'parent' => 2,
],
[
'tid' => 6,
'parent' => 3,
],
[
'tid' => 6,
'parent' => 2,
],
[
'tid' => 7,
'parent' => 0,
],
];
$tests[0]['source_data']['taxonomy_vocabulary'] = [
[
'vid' => 3,
'machine_name' => 'foo',
'language' => 'und',
'i18n_mode' => '0',
],
[
'vid' => 5,
'machine_name' => 'tags',
'language' => 'und',
'i18n_mode' => '4',
],
[
'vid' => 6,
'machine_name' => 'categories',
'language' => 'is',
'i18n_mode' => '1',
],
];
$tests[0]['source_data']['field_config'] = [
[
'id' => '3',
'translatable' => '0',
],
[
'id' => '4',
'translatable' => '1',
],
[
'id' => '5',
'translatable' => '1',
],
];
$tests[0]['source_data']['field_config_instance'] = [
[
'id' => '2',
'field_id' => 3,
'field_name' => 'field_term_field',
'entity_type' => 'taxonomy_term',
'bundle' => 'tags',
'data' => 'a:0:{}',
'deleted' => 0,
],
[
'id' => '3',
'field_id' => 3,
'field_name' => 'field_term_field',
'entity_type' => 'taxonomy_term',
'bundle' => 'categories',
'data' => 'a:0:{}',
'deleted' => 0,
],
[
'id' => '4',
'field_id' => '4',
'field_name' => 'name_field',
'entity_type' => 'taxonomy_term',
'bundle' => 'tags',
'data' => 'a:0:{}',
'deleted' => '0',
],
[
'id' => '5',
'field_id' => '5',
'field_name' => 'description_field',
'entity_type' => 'taxonomy_term',
'bundle' => 'tags',
'data' => 'a:0:{}',
'deleted' => '0',
],
];
$tests[0]['source_data']['field_data_field_term_field'] = [
[
'entity_type' => 'taxonomy_term',
'bundle' => 'tags',
'deleted' => 0,
'entity_id' => 1,
'delta' => 0,
],
[
'entity_type' => 'taxonomy_term',
'bundle' => 'categories',
'deleted' => 0,
'entity_id' => 1,
'delta' => 0,
],
];
$tests[0]['source_data']['field_data_name_field'] = [
[
'entity_type' => 'taxonomy_term',
'bundle' => 'tags',
'deleted' => '0',
'entity_id' => '1',
'revision_id' => '1',
'language' => 'und',
'delta' => '0',
'name_field_value' => 'fr - name 1',
'name_field_format' => NULL,
],
[
'entity_type' => 'taxonomy_term',
'bundle' => 'tags',
'deleted' => '0',
'entity_id' => '4',
'revision_id' => '4',
'language' => 'und',
'delta' => '0',
'name_field_value' => 'is - name 4',
'name_field_format' => NULL,
],
];
$tests[0]['source_data']['field_data_description_field'] = [
[
'entity_type' => 'taxonomy_term',
'bundle' => 'tags',
'deleted' => '0',
'entity_id' => '1',
'revision_id' => '1',
'language' => 'und',
'delta' => '0',
'description_field_value' => 'desc 1',
'description_field_format' => NULL,
],
[
'entity_type' => 'taxonomy_term',
'bundle' => 'tags',
'deleted' => '0',
'entity_id' => '4',
'revision_id' => '4',
'language' => 'und',
'delta' => '0',
'description_field_value' => 'desc 4',
'description_field_format' => NULL,
],
];
// The expected results.
$tests[0]['expected_data'] = [
[
'tid' => 1,
'vid' => 5,
'name' => 'fr - name 1',
'description' => 'desc 1',
'weight' => 0,
'language' => 'fr',
'i18n_tsid' => '1',
'machine_name' => 'tags',
'i18n_mode' => '4',
'td_language' => 'fr',
'tv_i18n_mode' => '4',
],
[
'tid' => 2,
'vid' => 5,
'name' => 'name 2',
'description' => 'desc 2',
'weight' => 0,
'language' => 'en',
'i18n_tsid' => '1',
'machine_name' => 'tags',
'i18n_mode' => '4',
'td_language' => 'en',
'tv_i18n_mode' => '4',
],
[
'tid' => 4,
'vid' => 5,
'name' => 'is - name 4',
'description' => 'desc 4',
'weight' => 1,
'language' => 'is',
'i18n_tsid' => '1',
'machine_name' => 'tags',
'i18n_mode' => '4',
'td_language' => 'is',
'tv_i18n_mode' => '4',
],
];
$tests[0]['expected_count'] = NULL;
// Get translations for the tags bundle.
$tests[0]['configuration']['bundle'] = ['tags'];
// Ignore i18_modes 0. get i18n_mode 2 and 4.
$tests[1] = $tests[0];
// Change a vocabulary to using fixed translation.
$tests[1]['source_data']['taxonomy_vocabulary'][2] = [
'vid' => 7,
'machine_name' => 'categories',
'language' => 'is',
'i18n_mode' => '2',
];
// Add the term with fixed translation.
$tests[1]['expected_data'][] = [
'tid' => 7,
'vid' => 7,
'name' => 'is - captains',
'description' => 'desc 7',
'weight' => 0,
'language' => 'is',
'i18n_tsid' => '',
'machine_name' => 'categories',
'i18n_mode' => '2',
'td_language' => 'is',
'tv_i18n_mode' => '2',
];
$tests[1]['expected_count'] = NULL;
$tests[1]['configuration']['bundle'] = NULL;
// No data returned when there is no i18n_mode column.
$tests[2] = [];
$tests[2]['source_data'] = $tests[0]['source_data'];
foreach ($tests[2]['source_data']['taxonomy_vocabulary'] as &$table) {
unset($table['i18n_mode']);
}
$tests[2]['expected_data'] = [0];
$tests[2]['expected_count'] = 0;
return $tests;
}
}

View File

@ -0,0 +1,56 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\taxonomy\Kernel\Plugin\migrate\source\d7;
use Drupal\Tests\migrate\Kernel\MigrateSqlSourceTestBase;
/**
* Tests D7 vocabulary source plugin.
*
* @covers \Drupal\taxonomy\Plugin\migrate\source\d7\Vocabulary
* @group taxonomy
*/
class VocabularyTest extends MigrateSqlSourceTestBase {
/**
* {@inheritdoc}
*/
protected static $modules = ['taxonomy', 'migrate_drupal'];
/**
* {@inheritdoc}
*/
public static function providerSource() {
$tests = [];
// The source data.
$tests[0]['source_data']['taxonomy_vocabulary'] = [
[
'vid' => 1,
'name' => 'Tags',
'description' => 'Tags description.',
'hierarchy' => 0,
'module' => 'taxonomy',
'weight' => 0,
'machine_name' => 'tags',
],
[
'vid' => 2,
'name' => 'Categories',
'description' => 'Categories description.',
'hierarchy' => 1,
'module' => 'taxonomy',
'weight' => 0,
'machine_name' => 'categories',
],
];
// The expected results.
$tests[0]['expected_data'] = $tests[0]['source_data']['taxonomy_vocabulary'];
return $tests;
}
}

View File

@ -0,0 +1,184 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\taxonomy\Kernel\Plugin\migrate\source\d7;
use Drupal\Tests\migrate\Kernel\MigrateSqlSourceTestBase;
// cspell:ignore objectid objectindex plid textgroup
/**
* Tests D7 i18n vocabulary source plugin.
*
* @covers \Drupal\taxonomy\Plugin\migrate\source\d7\VocabularyTranslation
* @group taxonomy
*/
class VocabularyTranslationTest extends MigrateSqlSourceTestBase {
/**
* {@inheritdoc}
*/
protected static $modules = ['taxonomy', 'migrate_drupal'];
/**
* {@inheritdoc}
*/
public static function providerSource() {
$tests = [];
// The source data.
$tests[0]['source_data']['i18n_string'] = [
[
'lid' => '1',
'textgroup' => 'taxonomy',
'context' => 'vocabulary:1:name',
'objectid' => '1',
'type' => 'vocabulary',
'property' => 'name',
'objectindex' => '1',
'format' => '',
],
[
'lid' => '2',
'textgroup' => 'taxonomy',
'context' => 'vocabulary:1:description',
'objectid' => '1',
'type' => 'vocabulary',
'property' => 'description',
'objectindex' => '1',
'format' => '',
],
[
'lid' => '764',
'textgroup' => 'field',
'context' => 'field_color:blog:label',
'objectid' => 'blog',
'type' => 'field_color',
'property' => 'label',
'objectindex' => '0',
'format' => '',
],
];
$tests[0]['source_data']['locales_target'] = [
[
'lid' => 1,
'language' => 'fr',
'translation' => 'fr - vocabulary 1',
'plid' => 0,
'plural' => 0,
'i18n_status' => 0,
],
[
'lid' => 2,
'language' => 'fr',
'translation' => 'fr - description of vocabulary 1',
'plid' => 0,
'plural' => 0,
'i18n_status' => 0,
],
[
'lid' => '764',
'translation' => 'Color',
'language' => 'fr',
'plid' => '0',
'plural' => '0',
'i18n_status' => '0',
],
];
$tests[0]['source_data']['taxonomy_vocabulary'] = [
[
'vid' => 1,
'name' => 'vocabulary 1',
'machine_name' => 'vocabulary_1',
'description' => 'description of vocabulary 1',
'hierarchy' => 1,
'module' => 'taxonomy',
'weight' => 4,
'language' => 'und',
'i18n_mode' => '4',
],
[
'vid' => 2,
'name' => 'vocabulary 2',
'machine_name' => 'vocabulary_1',
'description' => 'description of vocabulary 2',
'hierarchy' => 1,
'module' => 'taxonomy',
'weight' => 4,
'language' => 'und',
'i18n_mode' => '4',
],
];
$tests[0]['expected_data'] = [
[
'vid' => 1,
'name' => 'vocabulary 1',
'machine_name' => 'vocabulary_1',
'description' => 'description of vocabulary 1',
'hierarchy' => 1,
'module' => 'taxonomy',
'weight' => 4,
'i18n_mode' => '4',
'lid' => '1',
'type' => 'vocabulary',
'property' => 'name',
'objectid' => '1',
'lt_lid' => '1',
'translation' => 'fr - vocabulary 1',
'v_language' => 'und',
'textgroup' => 'taxonomy',
'context' => 'vocabulary:1:name',
'objectindex' => '1',
'format' => '',
'language' => 'fr',
'plid' => '0',
'plural' => '0',
'i18n_status' => '0',
],
[
'vid' => 1,
'name' => 'vocabulary 1',
'machine_name' => 'vocabulary_1',
'description' => 'description of vocabulary 1',
'hierarchy' => 1,
'module' => 'taxonomy',
'weight' => 4,
'i18n_mode' => '4',
'lid' => '2',
'type' => 'vocabulary',
'property' => 'description',
'objectid' => '1',
'lt_lid' => '2',
'translation' => 'fr - description of vocabulary 1',
'v_language' => 'und',
'textgroup' => 'taxonomy',
'context' => 'vocabulary:1:description',
'objectindex' => '1',
'format' => '',
'language' => 'fr',
'plid' => '0',
'plural' => '0',
'i18n_status' => '0',
],
];
$tests[1] = $tests[0];
// Test without the language and i18n_mode columns in taxonomy_vocabulary.
foreach ($tests[1]['source_data']['taxonomy_vocabulary'] as &$data) {
unset($data['language']);
unset($data['i18n_mode']);
}
foreach ($tests[1]['expected_data'] as &$data) {
unset($data['v_language']);
unset($data['i18n_mode']);
}
return $tests;
}
}

View File

@ -0,0 +1,149 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\taxonomy\Kernel;
use Drupal\Core\Database\Database;
use Drupal\KernelTests\KernelTestBase;
use Drupal\Tests\taxonomy\Traits\TaxonomyTestTrait;
/**
* Tests that appropriate query tags are added.
*
* @group taxonomy
*/
class TaxonomyQueryAlterTest extends KernelTestBase {
use TaxonomyTestTrait;
/**
* {@inheritdoc}
*/
protected static $modules = [
'filter',
'taxonomy',
'taxonomy_test',
'text',
'user',
];
/**
* Tests that appropriate tags are added when querying the database.
*/
public function testTaxonomyQueryAlter(): void {
$this->installEntitySchema('taxonomy_term');
// Create a new vocabulary and add a few terms to it.
$vocabulary = $this->createVocabulary();
$terms = [];
for ($i = 0; $i < 5; $i++) {
$terms[$i] = $this->createTerm($vocabulary);
}
// Set up hierarchy. Term 2 is a child of 1.
$terms[2]->parent = $terms[1]->id();
$terms[2]->save();
/** @var \Drupal\taxonomy\TermStorageInterface $term_storage */
$term_storage = $this->container->get('entity_type.manager')->getStorage('taxonomy_term');
$this->setupQueryTagTestHooks();
$loaded_term = $term_storage->load($terms[0]->id());
// First term was loaded.
$this->assertEquals($terms[0]->id(), $loaded_term->id());
// TermStorage::load().
$this->assertQueryTagTestResult(1, 0);
$this->setupQueryTagTestHooks();
$loaded_terms = $term_storage->loadTree($vocabulary->id());
// All terms were loaded.
$this->assertCount(5, $loaded_terms);
// TermStorage::loadTree().
$this->assertQueryTagTestResult(1, 1);
$this->setupQueryTagTestHooks();
$loaded_terms = $term_storage->loadParents($terms[2]->id());
// All parent terms were loaded.
$this->assertCount(1, $loaded_terms);
// TermStorage::loadParents().
$this->assertQueryTagTestResult(3, 1);
$this->setupQueryTagTestHooks();
$loaded_terms = $term_storage->loadChildren($terms[1]->id());
// All child terms were loaded.
$this->assertCount(1, $loaded_terms);
// TermStorage::loadChildren().
$this->assertQueryTagTestResult(3, 1);
$this->setupQueryTagTestHooks();
$connection = Database::getConnection();
$query = $connection->select('taxonomy_term_data', 't');
$query->addField('t', 'tid');
$query->addTag('taxonomy_term_access');
$tids = $query->execute()->fetchCol();
// All term IDs were retrieved.
$this->assertCount(5, $tids);
// Database custom ::select() with 'taxonomy_term_access' tag (preferred).
$this->assertQueryTagTestResult(1, 1);
$this->setupQueryTagTestHooks();
$query = $connection->select('taxonomy_term_data', 't');
$query->addField('t', 'tid');
$query->addTag('term_access');
$tids = $query->execute()->fetchCol();
// All term IDs were retrieved.
$this->assertCount(5, $tids);
// Database custom ::select() with term_access tag (deprecated).
$this->assertQueryTagTestResult(1, 1);
$this->setupQueryTagTestHooks();
$query = \Drupal::entityQuery('taxonomy_term')->accessCheck(FALSE);
$query->addTag('taxonomy_term_access');
$result = $query->execute();
// All term IDs were retrieved.
$this->assertCount(5, $result);
// Custom entity query with taxonomy_term_access tag (preferred).
$this->assertQueryTagTestResult(1, 1);
$this->setupQueryTagTestHooks();
$query = \Drupal::entityQuery('taxonomy_term')->accessCheck(FALSE);
$query->addTag('term_access');
$result = $query->execute();
// All term IDs were retrieved.
$this->assertCount(5, $result);
// Custom entity query with taxonomy_term_access tag (preferred).
$this->assertQueryTagTestResult(1, 1);
}
/**
* Sets up the hooks in the test module.
*/
protected function setupQueryTagTestHooks(): void {
$this->container->get('entity_type.manager')->getStorage('taxonomy_term')->resetCache();
$state = $this->container->get('state');
$state->set('taxonomy_test_query_alter', 0);
$state->set('taxonomy_test_query_term_access_alter', 0);
$state->set('taxonomy_test_query_taxonomy_term_access_alter', 0);
}
/**
* Verifies invocation of the hooks in the test module.
*
* @param int $expected_generic_invocations
* The number of times the generic query_alter hook is expected to have
* been invoked.
* @param int $expected_specific_invocations
* The number of times the tag-specific query_alter hooks are expected to
* have been invoked.
*
* @internal
*/
protected function assertQueryTagTestResult(int $expected_generic_invocations, int $expected_specific_invocations): void {
$state = $this->container->get('state');
$this->assertEquals($expected_generic_invocations, $state->get('taxonomy_test_query_alter'));
$this->assertEquals($expected_specific_invocations, $state->get('taxonomy_test_query_term_access_alter'));
$this->assertEquals($expected_specific_invocations, $state->get('taxonomy_test_query_taxonomy_term_access_alter'));
}
}

View File

@ -0,0 +1,78 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\taxonomy\Kernel;
use Drupal\KernelTests\KernelTestBase;
use Drupal\Tests\taxonomy\Traits\TaxonomyTestTrait;
/**
* Verifies operation of a taxonomy-based Entity Query.
*
* @group taxonomy
*/
class TermEntityQueryTest extends KernelTestBase {
use TaxonomyTestTrait;
/**
* {@inheritdoc}
*/
protected static $modules = [
'field',
'filter',
'taxonomy',
'text',
'user',
];
/**
* Tests that a basic taxonomy entity query works.
*/
public function testTermEntityQuery(): void {
$this->installEntitySchema('taxonomy_term');
$vocabulary = $this->createVocabulary();
$terms = [];
for ($i = 0; $i < 5; $i++) {
$term = $this->createTerm($vocabulary);
$terms[$term->id()] = $term;
}
$result = \Drupal::entityQuery('taxonomy_term')->accessCheck(FALSE)->execute();
sort($result);
$this->assertEquals(array_keys($terms), $result);
$tid = reset($result);
$ids = (object) [
'entity_type' => 'taxonomy_term',
'entity_id' => $tid,
'bundle' => $vocabulary->id(),
];
$term = _field_create_entity_from_ids($ids);
$this->assertEquals($tid, $term->id());
// Create a second vocabulary and five more terms.
$vocabulary2 = $this->createVocabulary();
$terms2 = [];
for ($i = 0; $i < 5; $i++) {
$term = $this->createTerm($vocabulary2);
$terms2[$term->id()] = $term;
}
$result = \Drupal::entityQuery('taxonomy_term')
->accessCheck(FALSE)
->condition('vid', $vocabulary2->id())
->execute();
sort($result);
$this->assertEquals(array_keys($terms2), $result);
$tid = reset($result);
$ids = (object) [
'entity_type' => 'taxonomy_term',
'entity_id' => $tid,
'bundle' => $vocabulary2->id(),
];
$term = _field_create_entity_from_ids($ids);
$this->assertEquals($tid, $term->id());
}
}

View File

@ -0,0 +1,118 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\taxonomy\Kernel;
use Drupal\entity_test\EntityTestHelper;
use Drupal\field\Entity\FieldStorageConfig;
use Drupal\field\Entity\FieldConfig;
use Drupal\KernelTests\KernelTestBase;
use Drupal\taxonomy\Entity\Term;
use Drupal\taxonomy\Entity\Vocabulary;
/**
* Tests the settings of restricting term selection to a single vocabulary.
*
* @group taxonomy
*/
class TermEntityReferenceTest extends KernelTestBase {
/**
* {@inheritdoc}
*/
protected static $modules = [
'entity_test',
'field',
'system',
'taxonomy',
'text',
'user',
];
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
$this->installEntitySchema('entity_test');
$this->installEntitySchema('taxonomy_term');
$this->installEntitySchema('user');
}
/**
* Tests an entity reference field restricted to a single vocabulary.
*
* Creates two vocabularies with a term, then set up the entity reference
* field to limit the target vocabulary to one of them, ensuring that
* the restriction applies.
*/
public function testSelectionTestVocabularyRestriction(): void {
// Create two vocabularies.
$vocabulary = Vocabulary::create([
'name' => 'test1',
'vid' => 'test1',
]);
$vocabulary->save();
$vocabulary2 = Vocabulary::create([
'name' => 'test2',
'vid' => 'test2',
]);
$vocabulary2->save();
$term = Term::create([
'name' => 'term1',
'vid' => $vocabulary->id(),
]);
$term->save();
$term2 = Term::create([
'name' => 'term2',
'vid' => $vocabulary2->id(),
]);
$term2->save();
// Create an entity reference field.
$field_name = 'taxonomy_' . $vocabulary->id();
$field_storage = FieldStorageConfig::create([
'field_name' => $field_name,
'entity_type' => 'entity_test',
'translatable' => FALSE,
'settings' => [
'target_type' => 'taxonomy_term',
],
'type' => 'entity_reference',
'cardinality' => 1,
]);
$field_storage->save();
EntityTestHelper::createBundle('test_bundle');
$field = FieldConfig::create([
'field_storage' => $field_storage,
'entity_type' => 'entity_test',
'bundle' => 'test_bundle',
'settings' => [
'handler' => 'default',
'handler_settings' => [
// Restrict selection of terms to a single vocabulary.
'target_bundles' => [
$vocabulary->id() => $vocabulary->id(),
],
],
],
]);
$field->save();
$handler = $this->container->get('plugin.manager.entity_reference_selection')->getSelectionHandler($field);
$result = $handler->getReferenceableEntities();
$expected_result = [
$vocabulary->id() => [
$term->id() => $term->getName(),
],
];
$this->assertSame($expected_result, $result, 'Terms selection restricted to a single vocabulary.');
}
}

View File

@ -0,0 +1,196 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\taxonomy\Kernel;
use Drupal\KernelTests\Core\Entity\EntityKernelTestBase;
use Drupal\taxonomy\Entity\Vocabulary;
/**
* Tests handling of pending revisions.
*
* @coversDefaultClass \Drupal\taxonomy\Plugin\Validation\Constraint\TaxonomyTermHierarchyConstraintValidator
*
* @group taxonomy
*/
class TermHierarchyValidationTest extends EntityKernelTestBase {
/**
* {@inheritdoc}
*/
protected static $modules = ['taxonomy'];
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
$this->installEntitySchema('taxonomy_term');
}
/**
* Tests the term hierarchy validation with re-parenting in pending revisions.
*/
public function testTermHierarchyValidation(): void {
$vocabulary_id = $this->randomMachineName();
$vocabulary = Vocabulary::create([
'name' => $vocabulary_id,
'vid' => $vocabulary_id,
]);
$vocabulary->save();
// Create a simple hierarchy in the vocabulary, a root term and three parent
// terms.
/** @var \Drupal\Core\Entity\RevisionableStorageInterface $term_storage */
$term_storage = \Drupal::entityTypeManager()->getStorage('taxonomy_term');
$root = $term_storage->create([
'name' => $this->randomMachineName(),
'vid' => $vocabulary_id,
]);
$root->save();
$parent1 = $term_storage->create([
'name' => $this->randomMachineName(),
'vid' => $vocabulary_id,
'parent' => $root->id(),
]);
$parent1->save();
$parent2 = $term_storage->create([
'name' => $this->randomMachineName(),
'vid' => $vocabulary_id,
'parent' => $root->id(),
]);
$parent2->save();
$parent3 = $term_storage->create([
'name' => $this->randomMachineName(),
'vid' => $vocabulary_id,
'parent' => $root->id(),
]);
$parent3->save();
// Create a child term and assign one of the parents above.
$child1 = $term_storage->create([
'name' => $this->randomMachineName(),
'vid' => $vocabulary_id,
'parent' => $parent1->id(),
]);
$violations = $child1->validate();
$this->assertEmpty($violations);
$child1->save();
$validation_message = 'You can only change the hierarchy for the published version of this term.';
// Add a pending revision without changing the term parent.
$pending_name = $this->randomMachineName();
$child_pending = $term_storage->createRevision($child1, FALSE);
$child_pending->name = $pending_name;
$violations = $child_pending->validate();
$this->assertEmpty($violations);
// Add a pending revision and change the parent.
$child_pending = $term_storage->createRevision($child1, FALSE);
$child_pending->parent = $parent2;
$violations = $child_pending->validate();
$this->assertCount(1, $violations);
$this->assertEquals($validation_message, $violations[0]->getMessage());
$this->assertEquals('parent', $violations[0]->getPropertyPath());
// Add a pending revision and add a new parent.
$child_pending = $term_storage->createRevision($child1, FALSE);
$child_pending->parent[0] = $parent1;
$child_pending->parent[1] = $parent3;
$violations = $child_pending->validate();
$this->assertCount(1, $violations);
$this->assertEquals($validation_message, $violations[0]->getMessage());
$this->assertEquals('parent', $violations[0]->getPropertyPath());
// Add a pending revision and use the root term as a parent.
$child_pending = $term_storage->createRevision($child1, FALSE);
$child_pending->parent[0] = $root;
$violations = $child_pending->validate();
$this->assertCount(1, $violations);
$this->assertEquals($validation_message, $violations[0]->getMessage());
$this->assertEquals('parent', $violations[0]->getPropertyPath());
// Add a pending revision and remove the parent.
$child_pending = $term_storage->createRevision($child1, FALSE);
$child_pending->parent[0] = NULL;
$violations = $child_pending->validate();
$this->assertCount(1, $violations);
$this->assertEquals($validation_message, $violations[0]->getMessage());
$this->assertEquals('parent', $violations[0]->getPropertyPath());
// Add a pending revision and change the weight.
$child_pending = $term_storage->createRevision($child1, FALSE);
$child_pending->weight = 10;
$violations = $child_pending->validate();
$this->assertCount(1, $violations);
$this->assertEquals($validation_message, $violations[0]->getMessage());
$this->assertEquals('weight', $violations[0]->getPropertyPath());
// Add a pending revision and change both the parent and the weight.
$child_pending = $term_storage->createRevision($child1, FALSE);
$child_pending->parent = $parent2;
$child_pending->weight = 10;
$violations = $child_pending->validate();
$this->assertCount(2, $violations);
$this->assertEquals($validation_message, $violations[0]->getMessage());
$this->assertEquals($validation_message, $violations[1]->getMessage());
$this->assertEquals('parent', $violations[0]->getPropertyPath());
$this->assertEquals('weight', $violations[1]->getPropertyPath());
// Add a published revision and change the parent.
$child_pending = $term_storage->createRevision($child1, TRUE);
$child_pending->parent[0] = $parent2;
$violations = $child_pending->validate();
$this->assertEmpty($violations);
// Add a new term as a third-level child.
// The taxonomy tree structure ends up as follows:
// root
// - parent1
// - parent2
// -- child1 <- this will be a term with a pending revision
// --- child2
// - parent3
$child2 = $term_storage->create([
'name' => $this->randomMachineName(),
'vid' => $vocabulary_id,
'parent' => $child1->id(),
]);
$child2->save();
// Change 'child1' to be a pending revision.
$child1 = $term_storage->createRevision($child1, FALSE);
$child1->save();
// Check that a child of a pending term can not be re-parented.
$child2_pending = $term_storage->createRevision($child2, FALSE);
$child2_pending->parent = $parent3;
$violations = $child2_pending->validate();
$this->assertCount(1, $violations);
$this->assertEquals($validation_message, $violations[0]->getMessage());
$this->assertEquals('parent', $violations[0]->getPropertyPath());
// Check that another term which has a pending revision can not moved under
// another term which has pending revision.
$parent3_pending = $term_storage->createRevision($parent3, FALSE);
$parent3_pending->parent = $child1;
$violations = $parent3_pending->validate();
$this->assertCount(1, $violations);
$this->assertEquals($validation_message, $violations[0]->getMessage());
$this->assertEquals('parent', $violations[0]->getPropertyPath());
// Check that a new term can be created under a term that has a pending
// revision.
$child3 = $term_storage->create([
'name' => $this->randomMachineName(),
'vid' => $vocabulary_id,
'parent' => $child1->id(),
]);
$violations = $child3->validate();
$this->assertEmpty($violations);
}
}

View File

@ -0,0 +1,208 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\taxonomy\Kernel;
use Drupal\taxonomy\Entity\Term;
use Drupal\KernelTests\KernelTestBase;
use Drupal\Tests\taxonomy\Traits\TaxonomyTestTrait;
use Drupal\Tests\user\Traits\UserCreationTrait;
/**
* Kernel tests for taxonomy term functions.
*
* @group taxonomy
*/
class TermKernelTest extends KernelTestBase {
use TaxonomyTestTrait;
use UserCreationTrait;
/**
* {@inheritdoc}
*/
protected static $modules = ['filter', 'taxonomy', 'text', 'user', 'system'];
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
$this->installConfig(['filter']);
$this->installEntitySchema('taxonomy_term');
$this->installEntitySchema('user');
}
/**
* Tests that a deleted term is no longer in the vocabulary.
*/
public function testTermDelete(): void {
$vocabulary = $this->createVocabulary();
$valid_term = $this->createTerm($vocabulary);
// Delete a valid term.
$valid_term->delete();
$terms = \Drupal::entityTypeManager()->getStorage('taxonomy_term')->loadByProperties(['vid' => $vocabulary->id()]);
$this->assertEmpty($terms, 'Vocabulary is empty after deletion');
}
/**
* Deleting a parent of a term with multiple parents does not delete the term.
*/
public function testMultipleParentDelete(): void {
$vocabulary = $this->createVocabulary();
$parent_term1 = $this->createTerm($vocabulary);
$parent_term2 = $this->createTerm($vocabulary);
$child_term = $this->createTerm($vocabulary);
$child_term->parent = [$parent_term1->id(), $parent_term2->id()];
$child_term->save();
$child_term_id = $child_term->id();
$parent_term1->delete();
$term_storage = $this->container->get('entity_type.manager')->getStorage('taxonomy_term');
$term_storage->resetCache([$child_term_id]);
$child_term = Term::load($child_term_id);
$this->assertNotEmpty($child_term, 'Child term is not deleted if only one of its parents is removed.');
$parent_term2->delete();
$term_storage->resetCache([$child_term_id]);
$child_term = Term::load($child_term_id);
$this->assertEmpty($child_term, 'Child term is deleted if all of its parents are removed.');
}
/**
* Tests a taxonomy with terms that have multiple parents of different depths.
*/
public function testTaxonomyVocabularyTree(): void {
// Create a new vocabulary with 6 terms.
$vocabulary = $this->createVocabulary();
$term = [];
for ($i = 0; $i < 6; $i++) {
$term[$i] = $this->createTerm($vocabulary);
}
// Get the taxonomy storage.
$taxonomy_storage = $this->container->get('entity_type.manager')->getStorage('taxonomy_term');
// Set the weight on $term[1] so it appears before $term[5] when fetching
// the parents for $term[2], in order to test for a regression on
// \Drupal\taxonomy\TermStorageInterface::loadAllParents().
$term[1]->weight = -1;
$term[1]->save();
// $term[2] is a child of 1 and 5.
$term[2]->parent = [$term[1]->id(), $term[5]->id()];
$term[2]->save();
// $term[3] is a child of 2.
$term[3]->parent = [$term[2]->id()];
$term[3]->save();
// $term[5] is a child of 4.
$term[5]->parent = [$term[4]->id()];
$term[5]->save();
// Expected tree:
// term[0] | depth: 0
// term[1] | depth: 0
// -- term[2] | depth: 1
// ---- term[3] | depth: 2
// term[4] | depth: 0
// -- term[5] | depth: 1
// ---- term[2] | depth: 2
// ------ term[3] | depth: 3
// Count $term[1] parents with $max_depth = 1.
$tree = $taxonomy_storage->loadTree($vocabulary->id(), $term[1]->id(), 1);
$this->assertCount(1, $tree, 'We have one parent with depth 1.');
// Count all vocabulary tree elements.
$tree = $taxonomy_storage->loadTree($vocabulary->id());
$this->assertCount(8, $tree, 'We have all vocabulary tree elements.');
// Count elements in every tree depth.
foreach ($tree as $element) {
if (!isset($depth_count[$element->depth])) {
$depth_count[$element->depth] = 0;
}
$depth_count[$element->depth]++;
}
$this->assertEquals(3, $depth_count[0], 'Three elements in taxonomy tree depth 0.');
$this->assertEquals(2, $depth_count[1], 'Two elements in taxonomy tree depth 1.');
$this->assertEquals(2, $depth_count[2], 'Two elements in taxonomy tree depth 2.');
$this->assertEquals(1, $depth_count[3], 'One element in taxonomy tree depth 3.');
/** @var \Drupal\taxonomy\TermStorageInterface $storage */
$storage = \Drupal::entityTypeManager()->getStorage('taxonomy_term');
// Count parents of $term[2].
$parents = $storage->loadParents($term[2]->id());
$this->assertCount(2, $parents, 'The term has two parents.');
// Count parents of $term[3].
$parents = $storage->loadParents($term[3]->id());
$this->assertCount(1, $parents, 'The term has one parent.');
// Identify all ancestors of $term[2].
$ancestors = $storage->loadAllParents($term[2]->id());
$this->assertCount(4, $ancestors, 'The term has four ancestors including the term itself.');
// Identify all ancestors of $term[3].
$ancestors = $storage->loadAllParents($term[3]->id());
$this->assertCount(5, $ancestors, 'The term has five ancestors including the term itself.');
}
/**
* Tests that a Term is renderable when unsaved (preview).
*/
public function testTermPreview(): void {
$entity_manager = \Drupal::entityTypeManager();
$vocabulary = $this->createVocabulary();
// Create a unsaved term.
$term = $entity_manager->getStorage('taxonomy_term')->create([
'vid' => $vocabulary->id(),
'name' => 'Foo',
]);
// Confirm we can get the view of unsaved term.
$render_array = $entity_manager->getViewBuilder('taxonomy_term')
->view($term);
$this->assertNotEmpty($render_array, 'Term view builder is built.');
// Confirm we can render said view.
$rendered = (string) \Drupal::service('renderer')->renderInIsolation($render_array);
$this->assertNotEmpty(trim($rendered), 'Term is able to be rendered.');
}
/**
* Tests revision log access.
*/
public function testRevisionLogAccess(): void {
$vocabulary = $this->createVocabulary();
$entity = $this->createTerm($vocabulary, ['status' => TRUE]);
$admin = $this->createUser([
'administer taxonomy',
'access content',
]);
$editor = $this->createUser([
'edit terms in ' . $vocabulary->id(),
'access content',
]);
$viewer = $this->createUser([
'access content',
]);
$this->assertTrue($entity->get('revision_log_message')->access('view', $admin));
$this->assertTrue($entity->get('revision_log_message')->access('view', $editor));
$this->assertFalse($entity->get('revision_log_message')->access('view', $viewer));
}
/**
* The "parent" field must restrict references to the same vocabulary.
*/
public function testParentHandlerSettings(): void {
$vocabulary = $this->createVocabulary();
$vocabulary_fields = \Drupal::service('entity_field.manager')->getFieldDefinitions('taxonomy_term', $vocabulary->id());
$parent_target_bundles = $vocabulary_fields['parent']->getSetting('handler_settings')['target_bundles'];
$this->assertSame([$vocabulary->id() => $vocabulary->id()], $parent_target_bundles);
}
}

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