Code: ' . curl_getinfo($this->curlHandle, CURLINFO_HTTP_CODE) . + (isset($curl_options[CURLOPT_HTTPHEADER]) ? '
Request headers: ' . nl2br(print_r($curl_options[CURLOPT_HTTPHEADER], TRUE)) : '' ) . + (isset($curl_options[CURLOPT_POSTFIELDS]) ? '
Request body: ' . nl2br(print_r($curl_options[CURLOPT_POSTFIELDS], TRUE)) : '' ) . '
Response headers: ' . nl2br(print_r($headers, TRUE)) . '
Response body: ' . $this->responseBody); diff --git a/core/modules/rest/src/Tests/UpdateTest.php b/core/modules/rest/src/Tests/UpdateTest.php index cdef82c34..d3784ed66 100644 --- a/core/modules/rest/src/Tests/UpdateTest.php +++ b/core/modules/rest/src/Tests/UpdateTest.php @@ -381,9 +381,13 @@ class UpdateTest extends RESTTestBase { $serialized = $serializer->serialize($normalized, $format, $context); // Try first without CSRF token which should fail. - $this->httpRequest($url, 'PATCH', $serialized, $mime_type, TRUE); - $this->assertResponse(403, 'X-CSRF-Token request header is missing'); - + $this->httpRequest($url, 'PATCH', $serialized, $mime_type, FALSE); + $this->assertResponse(403); + $this->assertRaw('X-CSRF-Token request header is missing'); + // Then try with an invalid CSRF token. + $this->httpRequest($url, 'PATCH', $serialized, $mime_type, 'invalid-csrf-token'); + $this->assertResponse(403); + $this->assertRaw('X-CSRF-Token request header is invalid'); // Then try with CSRF token. $this->httpRequest($url, 'PATCH', $serialized, $mime_type); $this->assertResponse(200); diff --git a/core/modules/search/migration_templates/d6_search_settings.yml b/core/modules/search/migration_templates/d6_search_settings.yml index 8efb9fdb1..28f860060 100644 --- a/core/modules/search/migration_templates/d6_search_settings.yml +++ b/core/modules/search/migration_templates/d6_search_settings.yml @@ -10,8 +10,6 @@ source: - minimum_word_size - overlap_cjk - search_cron_limit - - search_tag_weights - - search_and_or_limit process: 'index/minimum_word_size': minimum_word_size 'index/overlap_cjk': overlap_cjk diff --git a/core/modules/serialization/src/Normalizer/ListNormalizer.php b/core/modules/serialization/src/Normalizer/ListNormalizer.php index 94aca1134..1729cfce8 100644 --- a/core/modules/serialization/src/Normalizer/ListNormalizer.php +++ b/core/modules/serialization/src/Normalizer/ListNormalizer.php @@ -26,7 +26,7 @@ class ListNormalizer extends NormalizerBase { public function normalize($object, $format = NULL, array $context = array()) { $attributes = array(); foreach ($object as $fieldItem) { - $attributes[] = $this->serializer->normalize($fieldItem, $format); + $attributes[] = $this->serializer->normalize($fieldItem, $format, $context); } return $attributes; } diff --git a/core/modules/serialization/tests/src/Unit/Normalizer/ListNormalizerTest.php b/core/modules/serialization/tests/src/Unit/Normalizer/ListNormalizerTest.php index 170fd3ecd..bbc7c1103 100644 --- a/core/modules/serialization/tests/src/Unit/Normalizer/ListNormalizerTest.php +++ b/core/modules/serialization/tests/src/Unit/Normalizer/ListNormalizerTest.php @@ -7,6 +7,7 @@ use Drupal\Core\TypedData\TypedDataManagerInterface; use Drupal\Tests\UnitTestCase; use Drupal\serialization\Normalizer\ListNormalizer; use Drupal\Core\TypedData\Plugin\DataType\ItemList; +use Symfony\Component\Serializer\Serializer; /** * @coversDefaultClass \Drupal\serialization\Normalizer\ListNormalizer @@ -35,13 +36,20 @@ class ListNormalizerTest extends UnitTestCase { */ protected $expectedListValues = array('test', 'test', 'test'); + /** + * The mocked typed data. + * + * @var \PHPUnit_Framework_MockObject_MockObject|\Drupal\Core\TypedData\TypedDataInterface + */ + protected $typedData; + protected function setUp() { // Mock the TypedDataManager to return a TypedDataInterface mock. - $typed_data = $this->getMock('Drupal\Core\TypedData\TypedDataInterface'); + $this->typedData = $this->getMock('Drupal\Core\TypedData\TypedDataInterface'); $typed_data_manager = $this->getMock(TypedDataManagerInterface::class); $typed_data_manager->expects($this->any()) ->method('getPropertyInstance') - ->will($this->returnValue($typed_data)); + ->will($this->returnValue($this->typedData)); // Set up a mock container as ItemList() will call for the 'typed_data_manager' // service. @@ -73,16 +81,14 @@ class ListNormalizerTest extends UnitTestCase { * Tests the normalize() method. */ public function testNormalize() { - $serializer = $this->getMockBuilder('Symfony\Component\Serializer\Serializer') - ->setMethods(array('normalize')) - ->getMock(); - $serializer->expects($this->exactly(3)) - ->method('normalize') - ->will($this->returnValue('test')); + $serializer = $this->prophesize(Serializer::class); + $serializer->normalize($this->typedData, 'json', ['mu' => 'nu']) + ->shouldBeCalledTimes(3) + ->willReturn('test'); - $this->normalizer->setSerializer($serializer); + $this->normalizer->setSerializer($serializer->reveal()); - $normalized = $this->normalizer->normalize($this->list); + $normalized = $this->normalizer->normalize($this->list, 'json', ['mu' => 'nu']); $this->assertEquals($this->expectedListValues, $normalized); } diff --git a/core/modules/shortcut/migration_templates/d7_shortcut.yml b/core/modules/shortcut/migration_templates/d7_shortcut.yml index ba986eb85..da3d89494 100644 --- a/core/modules/shortcut/migration_templates/d7_shortcut.yml +++ b/core/modules/shortcut/migration_templates/d7_shortcut.yml @@ -23,4 +23,4 @@ destination: migration_dependencies: required: - d7_shortcut_set - - menu_links + - d7_menu_links diff --git a/core/modules/shortcut/shortcut.module b/core/modules/shortcut/shortcut.module index 066afcc95..b7294ad65 100644 --- a/core/modules/shortcut/shortcut.module +++ b/core/modules/shortcut/shortcut.module @@ -311,7 +311,8 @@ function shortcut_preprocess_page_title(&$variables) { // Replicate template_preprocess_html()'s processing to get the title in // string form, so we can set the default name for the shortcut. - $name = render($variables['title']); + // Strip HTML tags from the title. + $name = trim(strip_tags(render($variables['title']))); $query = array( 'link' => $link, 'name' => $name, diff --git a/core/modules/shortcut/src/Tests/ShortcutLinksTest.php b/core/modules/shortcut/src/Tests/ShortcutLinksTest.php index c6efe4134..679f89bbe 100644 --- a/core/modules/shortcut/src/Tests/ShortcutLinksTest.php +++ b/core/modules/shortcut/src/Tests/ShortcutLinksTest.php @@ -2,7 +2,9 @@ namespace Drupal\shortcut\Tests; +use Drupal\block_content\Entity\BlockContentType; use Drupal\Component\Utility\SafeMarkup; +use Drupal\Component\Render\FormattableMarkup; use Drupal\Core\Url; use Drupal\shortcut\Entity\Shortcut; use Drupal\shortcut\Entity\ShortcutSet; @@ -161,18 +163,12 @@ class ShortcutLinksTest extends ShortcutTestBase { // Test the "Add to shortcuts" link for a page generated by views. $this->clickLink('Add to Default shortcuts'); $this->assertText('Added a shortcut for People.'); - // Due to the structure of the markup in the link ::assertLink() doesn't - // works here. - $link = $this->xpath('//a[normalize-space()=:label]', array(':label' => 'Remove from Default shortcuts')); - $this->assertTrue(!empty($link), 'Link Remove from Default shortcuts found.'); + $this->assertShortcutQuickLink('Remove from Default shortcuts'); // Test the "Remove from shortcuts" link for a page generated by views. $this->clickLink('Remove from Default shortcuts'); $this->assertText('The shortcut People has been deleted.'); - // Due to the structure of the markup in the link ::assertLink() doesn't - // works here. - $link = $this->xpath('//a[normalize-space()=:label]', array(':label' => 'Add to Default shortcuts')); - $this->assertTrue(!empty($link), 'Link Add to Default shortcuts found.'); + $this->assertShortcutQuickLink('Add to Default shortcuts'); // Test two pages which use same route name but different route parameters. $this->drupalGet('node/add/page'); @@ -186,6 +182,37 @@ class ShortcutLinksTest extends ShortcutTestBase { // Add Shortcut for Article. $this->clickLink('Add to Default shortcuts'); $this->assertText('Added a shortcut for Create Article.'); + + $this->config('system.theme')->set('default', 'seven')->save(); + $this->drupalGet('node/' . $this->node->id()); + $title = $this->node->getTitle(); + + // Test the "Add to shortcuts" link for node view route. + $this->clickLink('Add to Default shortcuts'); + $this->assertText(new FormattableMarkup('Added a shortcut for @title.', ['@title' => $title])); + $this->assertShortcutQuickLink('Remove from Default shortcuts'); + + // Test the "Remove from shortcuts" link for node view route. + $this->clickLink('Remove from Default shortcuts'); + $this->assertText(new FormattableMarkup('The shortcut @title has been deleted.', ['@title' => $title])); + $this->assertShortcutQuickLink('Add to Default shortcuts'); + + \Drupal::service('module_installer')->install(['block_content']); + BlockContentType::create(array( + 'id' => 'basic', + 'label' => 'Basic block', + 'revision' => FALSE, + ))->save(); + // Test page with HTML tags in title. + $this->drupalGet('admin/structure/block/block-content/manage/basic'); + $page_title = new FormattableMarkup('Edit %label custom block type', ['%label' => 'Basic block']); + $this->assertRaw($page_title); + // Add shortcut to this page. + $this->clickLink('Add to Default shortcuts'); + $this->assertRaw(new FormattableMarkup('Added a shortcut for %title.', [ + '%title' => trim(strip_tags($page_title)), + ])); + } /** @@ -404,4 +431,32 @@ class ShortcutLinksTest extends ShortcutTestBase { $this->assertNoBlockAppears($block); } + /** + * Passes if a shortcut quick link with the specified label is found. + * + * An optional link index may be passed. + * + * @param string $label + * Text between the anchor tags. + * @param int $index + * Link position counting from zero. + * @param string $message + * (optional) A message to display with the assertion. Do not translate + * messages: use format_string() to embed variables in the message text, not + * t(). If left blank, a default message will be displayed. + * @param string $group + * (optional) The group this message is in, which is displayed in a column + * in test output. Use 'Debug' to indicate this is debugging output. Do not + * translate this string. Defaults to 'Other'; most tests do not override + * this default. + * + * @return bool + * TRUE if the assertion succeeded, FALSE otherwise. + */ + protected function assertShortcutQuickLink($label, $index = 0, $message = '', $group = 'Other') { + $links = $this->xpath('//a[normalize-space()=:label]', array(':label' => $label)); + $message = ($message ? $message : SafeMarkup::format('Shortcut quick link with label %label found.', array('%label' => $label))); + return $this->assert(isset($links[$index]), $message, $group); + } + } diff --git a/core/modules/shortcut/tests/src/Kernel/Migrate/d7/MigrateShortcutSetTest.php b/core/modules/shortcut/tests/src/Kernel/Migrate/d7/MigrateShortcutSetTest.php index 316c53f24..865828641 100644 --- a/core/modules/shortcut/tests/src/Kernel/Migrate/d7/MigrateShortcutSetTest.php +++ b/core/modules/shortcut/tests/src/Kernel/Migrate/d7/MigrateShortcutSetTest.php @@ -34,8 +34,8 @@ class MigrateShortcutSetTest extends MigrateDrupal7TestBase { $this->installEntitySchema('menu_link_content'); \Drupal::service('router.builder')->rebuild(); $this->executeMigration('d7_shortcut_set'); - $this->executeMigration('menu'); - $this->executeMigration('menu_links'); + $this->executeMigration('d7_menu'); + $this->executeMigration('d7_menu_links'); $this->executeMigration('d7_shortcut'); } diff --git a/core/modules/shortcut/tests/src/Kernel/Migrate/d7/MigrateShortcutSetUsersTest.php b/core/modules/shortcut/tests/src/Kernel/Migrate/d7/MigrateShortcutSetUsersTest.php index 6a9ebe346..1b15dcdef 100644 --- a/core/modules/shortcut/tests/src/Kernel/Migrate/d7/MigrateShortcutSetUsersTest.php +++ b/core/modules/shortcut/tests/src/Kernel/Migrate/d7/MigrateShortcutSetUsersTest.php @@ -36,8 +36,8 @@ class MigrateShortcutSetUsersTest extends MigrateDrupal7TestBase { $this->executeMigration('d7_user_role'); $this->executeMigration('d7_user'); $this->executeMigration('d7_shortcut_set'); - $this->executeMigration('menu'); - $this->executeMigration('menu_links'); + $this->executeMigration('d7_menu'); + $this->executeMigration('d7_menu_links'); $this->executeMigration('d7_shortcut'); $this->executeMigration('d7_shortcut_set_users'); } diff --git a/core/modules/shortcut/tests/src/Kernel/Migrate/d7/MigrateShortcutTest.php b/core/modules/shortcut/tests/src/Kernel/Migrate/d7/MigrateShortcutTest.php index 791deb219..18f55170a 100644 --- a/core/modules/shortcut/tests/src/Kernel/Migrate/d7/MigrateShortcutTest.php +++ b/core/modules/shortcut/tests/src/Kernel/Migrate/d7/MigrateShortcutTest.php @@ -34,8 +34,8 @@ class MigrateShortcutTest extends MigrateDrupal7TestBase { $this->installEntitySchema('menu_link_content'); \Drupal::service('router.builder')->rebuild(); $this->executeMigration('d7_shortcut_set'); - $this->executeMigration('menu'); - $this->executeMigration('menu_links'); + $this->executeMigration('d7_menu'); + $this->executeMigration('d7_menu_links'); $this->executeMigration('d7_shortcut'); } diff --git a/core/modules/shortcut/tests/src/Kernel/Plugin/migrate/source/d7/ShortcutSetTest.php b/core/modules/shortcut/tests/src/Kernel/Plugin/migrate/source/d7/ShortcutSetTest.php new file mode 100644 index 000000000..e1982de64 --- /dev/null +++ b/core/modules/shortcut/tests/src/Kernel/Plugin/migrate/source/d7/ShortcutSetTest.php @@ -0,0 +1,41 @@ + 'shortcut-set-2', + 'title' => 'Alternative shortcut set', + ], + ]; + + // The expected results are identical to the source data. + $tests[0]['expected_data'] = $tests[0]['source_data']['shortcut_set']; + + return $tests; + } + +} diff --git a/core/modules/shortcut/tests/src/Kernel/Plugin/migrate/source/d7/ShortcutSetUsersTest.php b/core/modules/shortcut/tests/src/Kernel/Plugin/migrate/source/d7/ShortcutSetUsersTest.php new file mode 100644 index 000000000..035aec31c --- /dev/null +++ b/core/modules/shortcut/tests/src/Kernel/Plugin/migrate/source/d7/ShortcutSetUsersTest.php @@ -0,0 +1,41 @@ + '2', + 'set_name' => 'shortcut-set-2', + ], + ]; + + // The expected results are identical to the source data. + $tests[0]['expected_data'] = $tests[0]['source_data']['shortcut_set_users']; + + return $tests; + } + +} diff --git a/core/modules/shortcut/tests/src/Kernel/Plugin/migrate/source/d7/ShortcutTest.php b/core/modules/shortcut/tests/src/Kernel/Plugin/migrate/source/d7/ShortcutTest.php new file mode 100644 index 000000000..9f8fd3ae6 --- /dev/null +++ b/core/modules/shortcut/tests/src/Kernel/Plugin/migrate/source/d7/ShortcutTest.php @@ -0,0 +1,72 @@ + 'shortcut-set-2', + 'mlid' => '473', + 'plid' => '0', + 'link_path' => 'admin/people', + 'router_path' => 'admin/people', + 'link_title' => 'People', + 'options' => 'a:0:{}', + 'module' => 'menu', + 'hidden' => '0', + 'external' => '0', + 'has_children' => '0', + 'expanded' => '0', + 'weight' => '-50', + 'depth' => '1', + 'customized' => '0', + 'p1' => '473', + 'p2' => '0', + 'p3' => '0', + 'p4' => '0', + 'p5' => '0', + 'p6' => '0', + 'p7' => '0', + 'p8' => '0', + 'p9' => '0', + 'updated' => '0', + ] + ]; + + // The expected results. + $tests[0]['expected_data'] = [ + [ + 'mlid' => '473', + 'menu_name' => 'shortcut-set-2', + 'link_path' => 'admin/people', + 'link_title' => 'People', + 'weight' => '-50', + ], + ]; + + return $tests; + } + +} diff --git a/core/modules/shortcut/tests/src/Unit/Plugin/migrate/source/d7/ShortcutSetTest.php b/core/modules/shortcut/tests/src/Unit/Plugin/migrate/source/d7/ShortcutSetTest.php deleted file mode 100644 index f2f221f09..000000000 --- a/core/modules/shortcut/tests/src/Unit/Plugin/migrate/source/d7/ShortcutSetTest.php +++ /dev/null @@ -1,38 +0,0 @@ - 'test', - 'source' => [ - 'plugin' => 'd7_shortcut_set', - ], - ]; - - protected $expectedResults = [ - [ - 'set_name' => 'shortcut-set-2', - 'title' => 'Alternative shortcut set', - ], - ]; - - /** - * {@inheritdoc} - */ - protected function setUp() { - $this->databaseContents['shortcut_set'] = $this->expectedResults; - parent::setUp(); - } - -} diff --git a/core/modules/shortcut/tests/src/Unit/Plugin/migrate/source/d7/ShortcutSetUsersTest.php b/core/modules/shortcut/tests/src/Unit/Plugin/migrate/source/d7/ShortcutSetUsersTest.php deleted file mode 100644 index 5681f6dfc..000000000 --- a/core/modules/shortcut/tests/src/Unit/Plugin/migrate/source/d7/ShortcutSetUsersTest.php +++ /dev/null @@ -1,38 +0,0 @@ - 'test', - 'source' => [ - 'plugin' => 'd7_shortcut_set_users', - ], - ]; - - protected $expectedResults = [ - [ - 'uid' => '2', - 'set_name' => 'shortcut-set-2', - ], - ]; - - /** - * {@inheritdoc} - */ - protected function setUp() { - $this->databaseContents['shortcut_set_users'] = $this->expectedResults; - parent::setUp(); - } - -} diff --git a/core/modules/shortcut/tests/src/Unit/Plugin/migrate/source/d7/ShortcutTest.php b/core/modules/shortcut/tests/src/Unit/Plugin/migrate/source/d7/ShortcutTest.php deleted file mode 100644 index 9fde228bc..000000000 --- a/core/modules/shortcut/tests/src/Unit/Plugin/migrate/source/d7/ShortcutTest.php +++ /dev/null @@ -1,68 +0,0 @@ - 'test', - 'source' => [ - 'plugin' => 'd7_shortcut', - ], - ]; - - protected $expectedResults = [ - [ - 'mlid' => '473', - 'menu_name' => 'shortcut-set-2', - 'link_path' => 'admin/people', - 'link_title' => 'People', - 'weight' => '-50', - ], - ]; - - /** - * {@inheritdoc} - */ - protected function setUp() { - $this->databaseContents['menu_links'][] = [ - 'menu_name' => 'shortcut-set-2', - 'mlid' => '473', - 'plid' => '0', - 'link_path' => 'admin/people', - 'router_path' => 'admin/people', - 'link_title' => 'People', - 'options' => 'a:0:{}', - 'module' => 'menu', - 'hidden' => '0', - 'external' => '0', - 'has_children' => '0', - 'expanded' => '0', - 'weight' => '-50', - 'depth' => '1', - 'customized' => '0', - 'p1' => '473', - 'p2' => '0', - 'p3' => '0', - 'p4' => '0', - 'p5' => '0', - 'p6' => '0', - 'p7' => '0', - 'p8' => '0', - 'p9' => '0', - 'updated' => '0', - ]; - - parent::setUp(); - } - -} diff --git a/core/modules/simpletest/simpletest.module b/core/modules/simpletest/simpletest.module index 0752e4251..f72ca5c6a 100644 --- a/core/modules/simpletest/simpletest.module +++ b/core/modules/simpletest/simpletest.module @@ -13,6 +13,7 @@ use Drupal\simpletest\TestBase; use Drupal\Core\Test\TestDatabase; use Drupal\simpletest\TestDiscovery; use Symfony\Component\Process\PhpExecutableFinder; +use Drupal\Core\Test\TestStatus; /** * Implements hook_help(). @@ -183,16 +184,15 @@ function simpletest_run_phpunit_tests($test_id, array $unescaped_test_classnames $phpunit_file = simpletest_phpunit_xml_filepath($test_id); simpletest_phpunit_run_command($unescaped_test_classnames, $phpunit_file, $status, $output); - $rows = simpletest_phpunit_xml_to_rows($test_id, $phpunit_file); - // A $status of 0 = passed test, 1 = failed test, > 1 indicates segfault - // timeout, or other type of failure. - if ($status > 1) { - // Something broke during the execution of phpunit. - // Return an error record of all failed classes. + $rows = []; + if ($status == TestStatus::PASS) { + $rows = simpletest_phpunit_xml_to_rows($test_id, $phpunit_file); + } + else { $rows[] = [ 'test_id' => $test_id, 'test_class' => implode(",", $unescaped_test_classnames), - 'status' => 'fail', + 'status' => TestStatus::label($status), 'message' => 'PHPunit Test failed to complete; Error: ' . implode("\n", $output), 'message_group' => 'Other', 'function' => implode(",", $unescaped_test_classnames), @@ -200,20 +200,6 @@ function simpletest_run_phpunit_tests($test_id, array $unescaped_test_classnames 'file' => $phpunit_file, ]; } - - if ($status === 1) { - $rows[] = [ - 'test_id' => $test_id, - 'test_class' => implode(",", $unescaped_test_classnames), - 'status' => 'fail', - 'message' => 'PHPunit Test failed to complete; Error: ' . implode("\n", $output), - 'message_group' => 'Other', - 'function' => implode(",", $unescaped_test_classnames), - 'line' => '0', - 'file' => $phpunit_file, - ]; - } - return $rows; } diff --git a/core/modules/simpletest/src/TestBase.php b/core/modules/simpletest/src/TestBase.php index fe25f9510..f4a23e7c1 100644 --- a/core/modules/simpletest/src/TestBase.php +++ b/core/modules/simpletest/src/TestBase.php @@ -1532,7 +1532,11 @@ abstract class TestBase { * need to get deleted too. */ public static function filePreDeleteCallback($path) { - chmod($path, 0700); + // When the webserver runs with the same system user as the test runner, we + // can make read-only files writable again. If not, chmod will fail while + // the file deletion still works if file permissions have been configured + // correctly. Thus, we ignore any problems while running chmod. + @chmod($path, 0700); } /** diff --git a/core/modules/simpletest/src/Tests/SimpleTestBrowserTest.php b/core/modules/simpletest/src/Tests/SimpleTestBrowserTest.php index c4528a53a..3b2061ec4 100644 --- a/core/modules/simpletest/src/Tests/SimpleTestBrowserTest.php +++ b/core/modules/simpletest/src/Tests/SimpleTestBrowserTest.php @@ -4,6 +4,7 @@ namespace Drupal\simpletest\Tests; use Drupal\Core\Url; use Drupal\simpletest\WebTestBase; +use Drupal\Tests\simpletest\Functional\ThroughUITest; /** * Tests the Simpletest UI internal browser. @@ -131,7 +132,7 @@ class SimpleTestBrowserTest extends WebTestBase { // A PHPUnit unit test. 'Drupal\Tests\action\Unit\Menu\ActionLocalTasksTest', // A PHPUnit functional test. - 'Drupal\FunctionalTests\BrowserTestBaseTest', + ThroughUITest::class, ); foreach ($tests as $test) { diff --git a/core/modules/simpletest/tests/fixtures/simpletest_phpunit_run_command_test.php b/core/modules/simpletest/tests/fixtures/simpletest_phpunit_run_command_test.php index 6fb61d523..a4ffc7ae4 100644 --- a/core/modules/simpletest/tests/fixtures/simpletest_phpunit_run_command_test.php +++ b/core/modules/simpletest/tests/fixtures/simpletest_phpunit_run_command_test.php @@ -2,22 +2,27 @@ namespace Drupal\Tests\simpletest\Unit; -use Drupal\Tests\UnitTestCase; - /** * This test crashes PHP. * * To avoid accidentally running, it is not in a normal PSR-4 directory, the * file name does not adhere to PSR-4 and an environment variable also needs to * be set for the crash to happen. + * + * @see \Drupal\Tests\simpletest\Unit\SimpletestPhpunitRunCommandTest::testSimpletestPhpUnitRunCommand() */ -class SimpletestPhpunitRunCommandTestWillDie extends UnitTestCase { +class SimpletestPhpunitRunCommandTestWillDie extends \PHPUnit_Framework_TestCase { + /** + * Performs the status specified by SimpletestPhpunitRunCommandTestWillDie. + */ public function testWillDie() { - if (getenv('SimpletestPhpunitRunCommandTestWillDie') === 'fail') { - exit(2); + $status = (int) getenv('SimpletestPhpunitRunCommandTestWillDie'); + if ($status == 0) { + $this->assertTrue(TRUE, 'Assertion to ensure test pass'); + return; } - $this->assertTrue(TRUE, 'Assertion to ensure test pass'); + exit($status); } } diff --git a/core/modules/simpletest/tests/src/Functional/ThroughUITest.php b/core/modules/simpletest/tests/src/Functional/ThroughUITest.php new file mode 100644 index 000000000..48737433f --- /dev/null +++ b/core/modules/simpletest/tests/src/Functional/ThroughUITest.php @@ -0,0 +1,23 @@ +pass('Success!'); + } + +} diff --git a/core/modules/simpletest/tests/src/Unit/SimpletestPhpunitRunCommandTest.php b/core/modules/simpletest/tests/src/Unit/SimpletestPhpunitRunCommandTest.php index 2afea1c7f..a96db963c 100644 --- a/core/modules/simpletest/tests/src/Unit/SimpletestPhpunitRunCommandTest.php +++ b/core/modules/simpletest/tests/src/Unit/SimpletestPhpunitRunCommandTest.php @@ -3,7 +3,7 @@ namespace Drupal\Tests\simpletest\Unit; use Drupal\Core\DependencyInjection\ContainerBuilder; -use Drupal\Tests\UnitTestCase; +use Drupal\Core\File\FileSystemInterface; /** * Tests simpletest_run_phpunit_tests() handles PHPunit fatals correctly. @@ -11,27 +11,92 @@ use Drupal\Tests\UnitTestCase; * @group simpletest * * @runTestsInSeparateProcesses - * @preserveGlobalState disabled */ -class SimpletestPhpunitRunCommandTest extends UnitTestCase { +class SimpletestPhpunitRunCommandTest extends \PHPUnit_Framework_TestCase { - function testSimpletestPhpUnitRunCommand() { - include_once __DIR__ . '/../../fixtures/simpletest_phpunit_run_command_test.php'; - $app_root = __DIR__ . '/../../../../../..'; - include_once "$app_root/core/modules/simpletest/simpletest.module"; + /** + * Path to the app root. + * + * @var string + */ + protected static $root; + + /** + * {@inheritdoc} + */ + public static function setUpBeforeClass() { + parent::setUpBeforeClass(); + // Figure out our app root. + self::$root = dirname(dirname(dirname(dirname(dirname(dirname(__DIR__)))))); + // Include the files we need for tests. The stub test we will run is + // SimpletestPhpunitRunCommandTestWillDie which is located in + // simpletest_phpunit_run_command_test.php. + include_once self::$root . '/core/modules/simpletest/tests/fixtures/simpletest_phpunit_run_command_test.php'; + // Since we're testing simpletest_run_phpunit_tests(), we need to include + // simpletest.module. + include_once self::$root . '/core/modules/simpletest/simpletest.module'; + } + + /** + * {@inheritdoc} + */ + protected function setUp() { + parent::setUp(); + // Organize our mock container. $container = new ContainerBuilder(); - $container->set('app.root', $app_root); - $file_system = $this->prophesize('Drupal\Core\File\FileSystemInterface'); + $container->set('app.root', self::$root); + $file_system = $this->prophesize(FileSystemInterface::class); + // The simpletest directory wrapper will always point to /tmp. $file_system->realpath('public://simpletest')->willReturn(sys_get_temp_dir()); $container->set('file_system', $file_system->reveal()); \Drupal::setContainer($container); - $test_id = basename(tempnam(sys_get_temp_dir(), 'xxx')); - foreach (['pass', 'fail'] as $status) { - putenv('SimpletestPhpunitRunCommandTestWillDie=' . $status); - $ret = simpletest_run_phpunit_tests($test_id, ['Drupal\Tests\simpletest\Unit\SimpletestPhpunitRunCommandTestWillDie']); - $this->assertSame($ret[0]['status'], $status); + } + + /** + * Data provider for testSimpletestPhpUnitRunCommand(). + * + * @return array + * Arrays of status codes and the label they're expected to have. + */ + public function provideStatusCodes() { + $data = [ + [0, 'pass'], + [1, 'fail'], + [2, 'exception'], + ]; + // All status codes 3 and above should be labeled 'error'. + // @todo: The valid values here would be 3 to 127. But since the test + // touches the file system a lot, we only have 3, 4, and 127 for speed. + foreach ([3, 4, 127] as $status) { + $data[] = [$status, 'error']; } + return $data; + } + + /** + * Test the round trip for PHPUnit execution status codes. + * + * @covers ::simpletest_run_phpunit_tests + * + * @dataProvider provideStatusCodes + */ + public function testSimpletestPhpUnitRunCommand($status, $label) { + $test_id = basename(tempnam(sys_get_temp_dir(), 'xxx')); + putenv('SimpletestPhpunitRunCommandTestWillDie=' . $status); + $ret = simpletest_run_phpunit_tests($test_id, [SimpletestPhpunitRunCommandTestWillDie::class]); + $this->assertSame($ret[0]['status'], $label); + putenv('SimpletestPhpunitRunCommandTestWillDie'); unlink(simpletest_phpunit_xml_filepath($test_id)); } + /** + * {@inheritdoc} + */ + protected function tearDown() { + // We unset the $base_url global, since the test code sets it as a + // side-effect. + unset($GLOBALS['base_url']); + parent::tearDown(); + } + } diff --git a/core/modules/statistics/migration_templates/d6_statistics_settings.yml b/core/modules/statistics/migration_templates/statistics_settings.yml similarity index 92% rename from core/modules/statistics/migration_templates/d6_statistics_settings.yml rename to core/modules/statistics/migration_templates/statistics_settings.yml index 348ad38e4..62c2c0612 100644 --- a/core/modules/statistics/migration_templates/d6_statistics_settings.yml +++ b/core/modules/statistics/migration_templates/statistics_settings.yml @@ -1,7 +1,8 @@ -id: d6_statistics_settings +id: statistics_settings label: Statistics configuration migration_tags: - Drupal 6 + - Drupal 7 source: plugin: variable variables: diff --git a/core/modules/statistics/tests/src/Kernel/Migrate/d6/MigrateStatisticsConfigsTest.php b/core/modules/statistics/tests/src/Kernel/Migrate/d6/MigrateStatisticsConfigsTest.php index aa9760a52..401a5e82b 100644 --- a/core/modules/statistics/tests/src/Kernel/Migrate/d6/MigrateStatisticsConfigsTest.php +++ b/core/modules/statistics/tests/src/Kernel/Migrate/d6/MigrateStatisticsConfigsTest.php @@ -24,7 +24,7 @@ class MigrateStatisticsConfigsTest extends MigrateDrupal6TestBase { */ protected function setUp() { parent::setUp(); - $this->executeMigration('d6_statistics_settings'); + $this->executeMigration('statistics_settings'); } /** diff --git a/core/modules/statistics/tests/src/Kernel/Migrate/d7/MigrateStatisticsConfigsTest.php b/core/modules/statistics/tests/src/Kernel/Migrate/d7/MigrateStatisticsConfigsTest.php new file mode 100644 index 000000000..e5d3310ad --- /dev/null +++ b/core/modules/statistics/tests/src/Kernel/Migrate/d7/MigrateStatisticsConfigsTest.php @@ -0,0 +1,41 @@ +executeMigration('statistics_settings'); + } + + /** + * Tests migration of statistics variables to statistics.settings.yml. + */ + public function testStatisticsSettings() { + $config = $this->config('statistics.settings'); + $this->assertIdentical(TRUE, $config->get('access_log.enabled')); + $this->assertIdentical(3600, $config->get('access_log.max_lifetime')); + $this->assertIdentical(1, $config->get('count_content_views')); + $this->assertConfigSchema(\Drupal::service('config.typed'), 'statistics.settings', $config->get()); + } + +} diff --git a/core/modules/system/migration_templates/menu.yml b/core/modules/system/migration_templates/d6_menu.yml similarity index 91% rename from core/modules/system/migration_templates/menu.yml rename to core/modules/system/migration_templates/d6_menu.yml index 974a2d68a..25ad0af9b 100644 --- a/core/modules/system/migration_templates/menu.yml +++ b/core/modules/system/migration_templates/d6_menu.yml @@ -1,9 +1,8 @@ # The menu_settings migration is in the menu_ui module. -id: menu +id: d6_menu label: Menus migration_tags: - Drupal 6 - - Drupal 7 source: plugin: menu process: diff --git a/core/modules/system/migration_templates/d7_menu.yml b/core/modules/system/migration_templates/d7_menu.yml new file mode 100644 index 000000000..da47b3656 --- /dev/null +++ b/core/modules/system/migration_templates/d7_menu.yml @@ -0,0 +1,20 @@ +id: d7_menu +label: Menus +migration_tags: + - Drupal 7 +source: + plugin: menu +process: + id: + plugin: static_map + bypass: true + source: menu_name + map: + main-menu: main + management: admin + navigation: tools + user-menu: account + label: title + description: description +destination: + plugin: entity:menu diff --git a/core/modules/system/src/Tests/Ajax/DialogTest.php b/core/modules/system/src/Tests/Ajax/DialogTest.php index 80b465067..254f01d33 100644 --- a/core/modules/system/src/Tests/Ajax/DialogTest.php +++ b/core/modules/system/src/Tests/Ajax/DialogTest.php @@ -39,7 +39,7 @@ class DialogTest extends AjaxTestBase { 'data' => $dialog_contents, 'dialogOptions' => array( 'modal' => TRUE, - 'title' => 'AJAX Dialog contents', + 'title' => 'AJAX Dialog & contents', ), ); $form_expected_response = array( @@ -67,7 +67,7 @@ class DialogTest extends AjaxTestBase { 'data' => $dialog_contents, 'dialogOptions' => array( 'modal' => FALSE, - 'title' => 'AJAX Dialog contents', + 'title' => 'AJAX Dialog & contents', ), ); $no_target_expected_response = array( @@ -77,7 +77,7 @@ class DialogTest extends AjaxTestBase { 'data' => $dialog_contents, 'dialogOptions' => array( 'modal' => FALSE, - 'title' => 'AJAX Dialog contents', + 'title' => 'AJAX Dialog & contents', ), ); $close_expected_response = array( @@ -97,6 +97,9 @@ class DialogTest extends AjaxTestBase { // Emulate going to the JS version of the page and check the JSON response. $ajax_result = $this->drupalGetAjax('ajax-test/dialog-contents', array('query' => array(MainContentViewSubscriber::WRAPPER_FORMAT => 'drupal_modal'))); $this->assertEqual($modal_expected_response, $ajax_result[3], 'Modal dialog JSON response matches.'); + // Test the HTML escaping of & character. + $this->assertEqual($ajax_result[3]['dialogOptions']['title'], 'AJAX Dialog & contents'); + $this->assertNotEqual($ajax_result[3]['dialogOptions']['title'], 'AJAX Dialog & contents'); // Check that requesting a "normal" dialog without JS goes to a page. $this->drupalGet('ajax-test/dialog-contents'); @@ -152,6 +155,8 @@ class DialogTest extends AjaxTestBase { // Check that the response matches the expected value. $this->assertEqual($modal_expected_response, $ajax_result[4], 'POST request modal dialog JSON response matches.'); + // Test the HTML escaping of & character. + $this->assertNotEqual($ajax_result[4]['dialogOptions']['title'], 'AJAX Dialog & contents'); // Abbreviated test for "normal" dialogs, testing only the difference. $ajax_result = $this->drupalPostAjaxForm('ajax-test/dialog', array(), 'button2'); diff --git a/core/modules/system/src/Tests/Common/FormatDateTest.php b/core/modules/system/src/Tests/Common/FormatDateTest.php index 36589ef46..cddf0a3fa 100644 --- a/core/modules/system/src/Tests/Common/FormatDateTest.php +++ b/core/modules/system/src/Tests/Common/FormatDateTest.php @@ -77,46 +77,4 @@ class FormatDateTest extends WebTestBase { $this->assertIdentical(format_date($timestamp, 'undefined_style'), format_date($timestamp, 'fallback'), 'Test format_date() defaulting to `fallback` when $type not found.'); } - /** - * Tests the format_date() function. - */ - function testFormatDate() { - $timestamp = strtotime('2007-03-26T00:00:00+00:00'); - $this->assertIdentical(format_date($timestamp, 'custom', 'l, d-M-y H:i:s T', 'America/Los_Angeles', 'en'), 'Sunday, 25-Mar-07 17:00:00 PDT', 'Test all parameters.'); - $this->assertIdentical(format_date($timestamp, 'custom', 'l, d-M-y H:i:s T', 'America/Los_Angeles', self::LANGCODE), 'domingo, 25-Mar-07 17:00:00 PDT', 'Test translated format.'); - $this->assertIdentical(format_date($timestamp, 'custom', '\\l, d-M-y H:i:s T', 'America/Los_Angeles', self::LANGCODE), 'l, 25-Mar-07 17:00:00 PDT', 'Test an escaped format string.'); - $this->assertIdentical(format_date($timestamp, 'custom', '\\\\l, d-M-y H:i:s T', 'America/Los_Angeles', self::LANGCODE), '\\domingo, 25-Mar-07 17:00:00 PDT', 'Test format containing backslash character.'); - $this->assertIdentical(format_date($timestamp, 'custom', '\\\\\\l, d-M-y H:i:s T', 'America/Los_Angeles', self::LANGCODE), '\\l, 25-Mar-07 17:00:00 PDT', 'Test format containing backslash followed by escaped format string.'); - $this->assertIdentical(format_date($timestamp, 'custom', 'l, d-M-y H:i:s T', 'Europe/London', 'en'), 'Monday, 26-Mar-07 01:00:00 BST', 'Test a different time zone.'); - - // Change the default language and timezone. - $this->config('system.site')->set('default_langcode', static::LANGCODE)->save(); - date_default_timezone_set('America/Los_Angeles'); - - // Reset the language manager so new negotiations attempts will fall back on - // on the new language. - $this->container->get('language_manager')->reset(); - - $this->assertIdentical(format_date($timestamp, 'custom', 'l, d-M-y H:i:s T', 'America/Los_Angeles', 'en'), 'Sunday, 25-Mar-07 17:00:00 PDT', 'Test a different language.'); - $this->assertIdentical(format_date($timestamp, 'custom', 'l, d-M-y H:i:s T', 'Europe/London'), 'Monday, 26-Mar-07 01:00:00 BST', 'Test a different time zone.'); - $this->assertIdentical(format_date($timestamp, 'custom', 'l, d-M-y H:i:s T'), 'domingo, 25-Mar-07 17:00:00 PDT', 'Test custom date format.'); - $this->assertIdentical(format_date($timestamp, 'long'), 'domingo, 25. marzo 2007 - 17:00', 'Test long date format.'); - $this->assertIdentical(format_date($timestamp, 'medium'), '25. marzo 2007 - 17:00', 'Test medium date format.'); - $this->assertIdentical(format_date($timestamp, 'short'), '2007 Mar 25 - 5:00pm', 'Test short date format.'); - $this->assertIdentical(format_date($timestamp), '25. marzo 2007 - 17:00', 'Test default date format.'); - // Test HTML time element formats. - $this->assertIdentical(format_date($timestamp, 'html_datetime'), '2007-03-25T17:00:00-0700', 'Test html_datetime date format.'); - $this->assertIdentical(format_date($timestamp, 'html_date'), '2007-03-25', 'Test html_date date format.'); - $this->assertIdentical(format_date($timestamp, 'html_time'), '17:00:00', 'Test html_time date format.'); - $this->assertIdentical(format_date($timestamp, 'html_yearless_date'), '03-25', 'Test html_yearless_date date format.'); - $this->assertIdentical(format_date($timestamp, 'html_week'), '2007-W12', 'Test html_week date format.'); - $this->assertIdentical(format_date($timestamp, 'html_month'), '2007-03', 'Test html_month date format.'); - $this->assertIdentical(format_date($timestamp, 'html_year'), '2007', 'Test html_year date format.'); - - // HTML is not escaped by the date formatter, it must be escaped later. - $formatter = \Drupal::service('date.formatter'); - $this->assertIdentical($formatter->format($timestamp, 'custom', '\<\s\c\r\i\p\t\>\a\l\e\r\t\(\'Y\'\)\;\<\/\s\c\r\i\p\t\>'), "", 'Script tags not removed from dates.'); - $this->assertIdentical($formatter->format($timestamp, 'custom', '\<\e\m\>Y\<\/\e\m\>'), '2007', 'Em tags are not removed from dates.'); - } - } diff --git a/core/modules/system/src/Tests/Render/AjaxPageStateTest.php b/core/modules/system/src/Tests/Render/AjaxPageStateTest.php index 08cd2768e..19e623775 100644 --- a/core/modules/system/src/Tests/Render/AjaxPageStateTest.php +++ b/core/modules/system/src/Tests/Render/AjaxPageStateTest.php @@ -11,6 +11,13 @@ use Drupal\simpletest\WebTestBase; */ class AjaxPageStateTest extends WebTestBase { + /** + * Modules to install. + * + * @var array + */ + public static $modules = ['node', 'views']; + /** * User account with all available permissions * @@ -77,22 +84,16 @@ class AjaxPageStateTest extends WebTestBase { } /** - * Test if multiple libaries can be excluded. + * Test if multiple libraries can be excluded. * - * ajax_page_state[libraries] should be able to support multiple libraries + * The ajax_page_state[libraries] should be able to support multiple libraries * comma separated. */ public function testMultipleLibrariesAreNotLoaded() { $this->drupalGet('node', - array( - "query" => - array( - 'ajax_page_state' => array( - 'libraries' => 'core/html5shiv,core/drupalSettings' - ) - ) - ) + ['query' => ['ajax_page_state' => ['libraries' => 'core/html5shiv,core/drupalSettings']]] ); + $this->assertResponse(200); $this->assertNoRaw( '/core/assets/vendor/html5shiv/html5shiv.min.js', 'The html5shiv library from core should be excluded from loading.' @@ -102,6 +103,17 @@ class AjaxPageStateTest extends WebTestBase { '/core/misc/drupalSettingsLoader.js', 'The drupalSettings library from core should be excluded from loading.' ); + + $this->drupalGet('node'); + $this->assertRaw( + '/core/assets/vendor/html5shiv/html5shiv.min.js', + 'The html5shiv library from core should be included in loading.' + ); + + $this->assertRaw( + '/core/misc/drupalSettingsLoader.js', + 'The drupalSettings library from core should be included in loading.' + ); } } diff --git a/core/modules/system/src/Tests/Theme/EngineTwigTest.php b/core/modules/system/src/Tests/Theme/EngineTwigTest.php index ae08fc835..51d092d44 100644 --- a/core/modules/system/src/Tests/Theme/EngineTwigTest.php +++ b/core/modules/system/src/Tests/Theme/EngineTwigTest.php @@ -2,6 +2,7 @@ namespace Drupal\system\Tests\Theme; +use Drupal\Core\Render\Markup; use Drupal\Core\Url; use Drupal\simpletest\WebTestBase; @@ -75,12 +76,16 @@ class EngineTwigTest extends WebTestBase { /** @var \Drupal\Core\Utility\LinkGenerator $link_generator */ $link_generator = $this->container->get('link_generator'); + + $generated_url = Url::fromRoute('user.register', [], ['absolute' => TRUE])->toString(TRUE)->getGeneratedUrl(); $expected = [ 'link via the linkgenerator: ' . $link_generator->generate('register', new Url('user.register', [], ['absolute' => TRUE])), 'link via the linkgenerator: ' . $link_generator->generate('register', new Url('user.register', [], ['absolute' => TRUE, 'attributes' => ['foo' => 'bar']])), 'link via the linkgenerator: ' . $link_generator->generate('register', new Url('user.register', [], ['attributes' => ['foo' => 'bar', 'id' => 'kitten']])), 'link via the linkgenerator: ' . $link_generator->generate('register', new Url('user.register', [], ['attributes' => ['id' => 'kitten']])), 'link via the linkgenerator: ' . $link_generator->generate('register', new Url('user.register', [], ['attributes' => ['class' => ['llama', 'kitten', 'panda']]])), + 'link via the linkgenerator: ' . $link_generator->generate(Markup::create('register'), new Url('user.register', [], ['absolute' => TRUE])), + 'link via the linkgenerator: register', ]; // Verify that link() has the ability to bubble cacheability metadata: diff --git a/core/modules/system/system.install b/core/modules/system/system.install index 3ef24a5a1..cbdafbc9f 100644 --- a/core/modules/system/system.install +++ b/core/modules/system/system.install @@ -276,7 +276,7 @@ function system_requirements($phase) { if (empty($drivers)) { $database_ok = FALSE; $pdo_message = t('Your web server does not appear to support any common PDO database extensions. Check with your hosting provider to see if they support PDO (PHP Data Objects) and offer any databases that Drupal supports.', array( - ':drupal-databases' => 'https://www.drupal.org/node/270#database', + ':drupal-databases' => 'https://www.drupal.org/requirements/database', )); } // Make sure the native PDO extension is available, not the older PEAR diff --git a/core/modules/system/system.libraries.yml b/core/modules/system/system.libraries.yml index dd9d2ec43..206ae4fc8 100644 --- a/core/modules/system/system.libraries.yml +++ b/core/modules/system/system.libraries.yml @@ -59,6 +59,7 @@ drupal.system.modules: - core/drupal - core/drupal.debounce - core/jquery.once + - core/drupal.announce diff: version: VERSION diff --git a/core/modules/system/system.module b/core/modules/system/system.module index 2f627bef5..01c8c5e1e 100644 --- a/core/modules/system/system.module +++ b/core/modules/system/system.module @@ -623,7 +623,7 @@ function system_page_attachments(array &$page) { // Handle setting the "active" class on links by: // - loading the active-link library if the current user is authenticated; // - applying a response filter if the current user is anonymous. - // @see l() + // @see \Drupal\Core\Link // @see \Drupal\Core\Utility\LinkGenerator::generate() // @see template_preprocess_links() // @see \Drupal\Core\EventSubscriber\ActiveLinkResponseFilter @@ -655,15 +655,6 @@ function system_js_settings_build(&$settings, AttachedAssetsInterface $assets) { // @see \Drupal\Core\Theme\AjaxBasePageNegotiator $theme_key = \Drupal::theme()->getActiveTheme()->getName(); $settings['ajaxPageState']['theme'] = $theme_key; - - // Provide the page with information about the individual asset libraries - // used, information not otherwise available when aggregation is enabled. - $minimal_libraries = $library_dependency_resolver->getMinimalRepresentativeSubset(array_merge( - $assets->getLibraries(), - $assets->getAlreadyLoadedLibraries() - )); - sort($minimal_libraries); - $settings['ajaxPageState']['libraries'] = implode(',', $minimal_libraries); } } @@ -715,7 +706,7 @@ function system_js_settings_alter(&$settings, AttachedAssetsInterface $assets) { $settings['pluralDelimiter'] = LOCALE_PLURAL_DELIMITER; } // Add the theme token to ajaxPageState, ensuring the database is available - // before doing so. + // before doing so. Also add the loaded libraries to ajaxPageState. /** @var \Drupal\Core\Asset\LibraryDependencyResolver $library_dependency_resolver */ $library_dependency_resolver = \Drupal::service('library.dependency_resolver'); if (isset($settings['ajaxPageState']) || in_array('core/drupal.ajax', $library_dependency_resolver->getLibrariesWithDependencies($assets->getAlreadyLoadedLibraries()))) { @@ -729,6 +720,14 @@ function system_js_settings_alter(&$settings, AttachedAssetsInterface $assets) { ->get($active_theme_key); } } + // Provide the page with information about the individual asset libraries + // used, information not otherwise available when aggregation is enabled. + $minimal_libraries = $library_dependency_resolver->getMinimalRepresentativeSubset(array_merge( + $assets->getLibraries(), + $assets->getAlreadyLoadedLibraries() + )); + sort($minimal_libraries); + $settings['ajaxPageState']['libraries'] = implode(',', $minimal_libraries); } } diff --git a/core/modules/system/templates/html.html.twig b/core/modules/system/templates/html.html.twig index 195d82e77..107c56b0a 100644 --- a/core/modules/system/templates/html.html.twig +++ b/core/modules/system/templates/html.html.twig @@ -8,7 +8,7 @@ * - root_path: The root path of the current page (e.g., node, admin, user). * - node_type: The content type for the current node, if the page is a node. * - head_title: List of text elements that make up the head_title variable. - * May contain or more of the following: + * May contain one or more of the following: * - title: The title of the page. * - name: The name of the site. * - slogan: The slogan of the site. diff --git a/core/modules/system/tests/modules/ajax_test/src/Controller/AjaxTestController.php b/core/modules/system/tests/modules/ajax_test/src/Controller/AjaxTestController.php index b618f0af7..63cc0abf1 100644 --- a/core/modules/system/tests/modules/ajax_test/src/Controller/AjaxTestController.php +++ b/core/modules/system/tests/modules/ajax_test/src/Controller/AjaxTestController.php @@ -23,7 +23,7 @@ class AjaxTestController { public static function dialogContents() { // This is a regular render array; the keys do not have special meaning. $content = array( - '#title' => 'AJAX Dialog contents', + '#title' => 'AJAX Dialog & contents', 'content' => array( '#markup' => 'Example message', ), diff --git a/core/modules/system/tests/modules/ajax_test/src/Form/AjaxTestDialogForm.php b/core/modules/system/tests/modules/ajax_test/src/Form/AjaxTestDialogForm.php index 304e2bbaf..a97ed9525 100644 --- a/core/modules/system/tests/modules/ajax_test/src/Form/AjaxTestDialogForm.php +++ b/core/modules/system/tests/modules/ajax_test/src/Form/AjaxTestDialogForm.php @@ -93,7 +93,7 @@ class AjaxTestDialogForm extends FormBase { protected function dialog($is_modal = FALSE) { $content = AjaxTestController::dialogContents(); $response = new AjaxResponse(); - $title = $this->t('AJAX Dialog contents'); + $title = $this->t('AJAX Dialog & contents'); // Attach the library necessary for using the Open(Modal)DialogCommand and // set the attachments for this Ajax response. diff --git a/core/modules/system/tests/modules/condition_test/src/Plugin/Condition/ConditionTestNoExistingType.php b/core/modules/system/tests/modules/condition_test/src/Plugin/Condition/ConditionTestNoExistingType.php new file mode 100644 index 000000000..ef00053fe --- /dev/null +++ b/core/modules/system/tests/modules/condition_test/src/Plugin/Condition/ConditionTestNoExistingType.php @@ -0,0 +1,34 @@ +t('Condition that requires a non-existent context.'); + } + +} diff --git a/core/modules/system/tests/modules/entity_test/entity_test.module b/core/modules/system/tests/modules/entity_test/entity_test.module index 453bdf114..b0c8cbf4d 100644 --- a/core/modules/system/tests/modules/entity_test/entity_test.module +++ b/core/modules/system/tests/modules/entity_test/entity_test.module @@ -674,14 +674,25 @@ function _entity_test_record_hooks($hook, $data) { * Implements hook_entity_prepare_view(). */ function entity_test_entity_prepare_view($entity_type, array $entities, array $displays) { - // Add a dummy field item attribute on field_test_text if it exists. if ($entity_type == 'entity_test') { foreach ($entities as $entity) { + /** @var \Drupal\Core\Entity\ContentEntityInterface $entity */ + + // Add a dummy field item attribute on field_test_text if it exists. if ($entity->hasField('field_test_text') && $displays[$entity->bundle()]->getComponent('field_test_text')) { foreach ($entity->get('field_test_text') as $item) { $item->_attributes += array('data-field-item-attr' => 'foobar'); } } + + // Add a dummy field item attribute on daterange fields if they exist. + $fields = $entity->getFieldDefinitions(); + foreach ($fields as $field) { + if ($field->getType() === 'daterange') { + $item = $entity->get($field->getName()); + $item->_attributes += array('data-field-item-attr' => 'foobar'); + } + } } } } diff --git a/core/modules/system/tests/modules/form_test/form_test.routing.yml b/core/modules/system/tests/modules/form_test/form_test.routing.yml index 790b9ce21..2250a0bb2 100644 --- a/core/modules/system/tests/modules/form_test/form_test.routing.yml +++ b/core/modules/system/tests/modules/form_test/form_test.routing.yml @@ -358,6 +358,14 @@ form_test.label: requirements: _access: 'TRUE' +form_test.machine_name: + path: '/form-test/machine-name' + defaults: + _form: '\Drupal\form_test\Form\FormTestMachineNameForm' + _title: 'Machine name fields' + requirements: + _access: 'TRUE' + form_test.state_persistence: path: '/form-test/state-persist' defaults: diff --git a/core/modules/system/tests/modules/form_test/src/Form/FormTestMachineNameForm.php b/core/modules/system/tests/modules/form_test/src/Form/FormTestMachineNameForm.php new file mode 100644 index 000000000..542edc8a7 --- /dev/null +++ b/core/modules/system/tests/modules/form_test/src/Form/FormTestMachineNameForm.php @@ -0,0 +1,63 @@ + 'textfield', + '#title' => 'Machine name 1 label', + ]; + $form['machine_name_1'] = [ + '#type' => 'machine_name', + '#title' => 'Machine name 1', + '#description' => 'A machine name.', + '#machine_name' => [ + 'source' => ['machine_name_1_label'] + ], + ]; + $form['machine_name_2_label'] = [ + '#type' => 'textfield', + '#title' => 'Machine name 2 label', + ]; + $form['machine_name_2'] = [ + '#type' => 'machine_name', + '#title' => 'Machine name 2', + '#description' => 'Another machine name.', + '#machine_name' => [ + 'source' => ['machine_name_2_label'] + ], + ]; + $form['submit'] = [ + '#type' => 'submit', + '#value' => 'Submit', + ]; + return $form; + } + + /** + * {@inheritdoc} + */ + public function submitForm(array &$form, FormStateInterface $form_state) { + $form_state->setResponse(new JsonResponse($form_state->getValues())); + } + +} diff --git a/core/modules/system/tests/modules/twig_theme_test/templates/twig_theme_test.link_generator.html.twig b/core/modules/system/tests/modules/twig_theme_test/templates/twig_theme_test.link_generator.html.twig index 8925705be..a836aec93 100644 --- a/core/modules/system/tests/modules/twig_theme_test/templates/twig_theme_test.link_generator.html.twig +++ b/core/modules/system/tests/modules/twig_theme_test/templates/twig_theme_test.link_generator.html.twig @@ -3,3 +3,7 @@
' . t('You can reorganize the terms in %capital_name using their drag-and-drop handles, and group terms under a parent term by sliding them under and to the right of the parent.', array('%capital_name' => Unicode::ucfirst($vocabulary->label()), '%name' => $vocabulary->label())) . '
'; - case TAXONOMY_HIERARCHY_SINGLE: + case VocabularyInterface::HIERARCHY_SINGLE: return '' . t('%capital_name contains terms grouped under parent terms. You can reorganize the terms in %capital_name using their drag-and-drop handles.', array('%capital_name' => Unicode::ucfirst($vocabulary->label()), '%name' => $vocabulary->label())) . '
'; - case TAXONOMY_HIERARCHY_MULTIPLE: + case VocabularyInterface::HIERARCHY_MULTIPLE: return '' . t('%capital_name contains terms with multiple parents. Drag and drop of terms with multiple parents is not supported, but you can re-enable drag-and-drop support by editing each term to include only a single parent.', array('%capital_name' => Unicode::ucfirst($vocabulary->label()))) . '
'; } } @@ -134,10 +143,11 @@ function taxonomy_theme() { * Checks the current parents of all terms in a vocabulary and updates the * vocabulary's hierarchy setting to the lowest possible level. If no term * has parent terms then the vocabulary will be given a hierarchy of - * TAXONOMY_HIERARCHY_DISABLED. If any term has a single parent then the - * vocabulary will be given a hierarchy of TAXONOMY_HIERARCHY_SINGLE. If any - * term has multiple parents then the vocabulary will be given a hierarchy of - * TAXONOMY_HIERARCHY_MULTIPLE. + * VocabularyInterface::HIERARCHY_DISABLED. If any term has a single parent then + * the vocabulary will be given a hierarchy of + * VocabularyInterface::HIERARCHY_SINGLE. If any term has multiple parents then + * the vocabulary will be given a hierarchy of + * VocabularyInterface::HIERARCHY_MULTIPLE. * * @param \Drupal\taxonomy\VocabularyInterface $vocabulary * A taxonomy vocabulary entity. @@ -149,7 +159,7 @@ function taxonomy_theme() { */ function taxonomy_check_vocabulary_hierarchy(VocabularyInterface $vocabulary, $changed_term) { $tree = \Drupal::entityManager()->getStorage('taxonomy_term')->loadTree($vocabulary->id()); - $hierarchy = TAXONOMY_HIERARCHY_DISABLED; + $hierarchy = VocabularyInterface::HIERARCHY_DISABLED; foreach ($tree as $term) { // Update the changed term with the new parent value before comparison. if ($term->tid == $changed_term['tid']) { @@ -158,11 +168,11 @@ function taxonomy_check_vocabulary_hierarchy(VocabularyInterface $vocabulary, $c } // Check this term's parent count. if (count($term->parents) > 1) { - $hierarchy = TAXONOMY_HIERARCHY_MULTIPLE; + $hierarchy = VocabularyInterface::HIERARCHY_MULTIPLE; break; } elseif (count($term->parents) == 1 && !isset($term->parents[0])) { - $hierarchy = TAXONOMY_HIERARCHY_SINGLE; + $hierarchy = VocabularyInterface::HIERARCHY_SINGLE; } } if ($hierarchy != $vocabulary->getHierarchy()) { diff --git a/core/modules/taxonomy/tests/modules/vocabulary_serialization_test/src/VocabularyResponse.php b/core/modules/taxonomy/tests/modules/vocabulary_serialization_test/src/VocabularyResponse.php new file mode 100644 index 000000000..39941b4c6 --- /dev/null +++ b/core/modules/taxonomy/tests/modules/vocabulary_serialization_test/src/VocabularyResponse.php @@ -0,0 +1,19 @@ +vocabulary = $vocabulary; + } + +} diff --git a/core/modules/taxonomy/tests/modules/vocabulary_serialization_test/src/VocabularySerializationTestController.php b/core/modules/taxonomy/tests/modules/vocabulary_serialization_test/src/VocabularySerializationTestController.php new file mode 100644 index 000000000..829edfbe0 --- /dev/null +++ b/core/modules/taxonomy/tests/modules/vocabulary_serialization_test/src/VocabularySerializationTestController.php @@ -0,0 +1,15 @@ +setVocabulary($taxonomy_vocabulary); + return $response; + } + +} diff --git a/core/modules/taxonomy/tests/modules/vocabulary_serialization_test/vocabulary_serialization_test.info.yml b/core/modules/taxonomy/tests/modules/vocabulary_serialization_test/vocabulary_serialization_test.info.yml new file mode 100644 index 000000000..be28f20a2 --- /dev/null +++ b/core/modules/taxonomy/tests/modules/vocabulary_serialization_test/vocabulary_serialization_test.info.yml @@ -0,0 +1,7 @@ +name: 'Vocabulary serialization test' +type: module +package: Testing +version: VERSION +core: 8.x +dependencies: + - taxonomy diff --git a/core/modules/taxonomy/tests/modules/vocabulary_serialization_test/vocabulary_serialization_test.routing.yml b/core/modules/taxonomy/tests/modules/vocabulary_serialization_test/vocabulary_serialization_test.routing.yml new file mode 100644 index 000000000..82f2b2222 --- /dev/null +++ b/core/modules/taxonomy/tests/modules/vocabulary_serialization_test/vocabulary_serialization_test.routing.yml @@ -0,0 +1,6 @@ +vocabulary_serialization_test: + path: '/vocabulary_serialization_test/{taxonomy_vocabulary}' + defaults: + _controller: 'Drupal\vocabulary_serialization_test\VocabularySerializationTestController::vocabularyResponse' + requirements: + _access: 'TRUE' diff --git a/core/modules/taxonomy/tests/src/Functional/VocabularySerializationTest.php b/core/modules/taxonomy/tests/src/Functional/VocabularySerializationTest.php new file mode 100644 index 000000000..b9aba8a79 --- /dev/null +++ b/core/modules/taxonomy/tests/src/Functional/VocabularySerializationTest.php @@ -0,0 +1,47 @@ + 'test'])->save(); + } + + public function testSerialization() { + $this->drupalGet('/vocabulary_serialization_test/test'); + $this->assertSession()->statusCodeEquals(200); + $this->assertSame('this is the output', $this->getSession()->getPage()->getContent()); + $this->assertSession()->responseHeaderEquals('X-Drupal-Cache', 'MISS'); + + $this->drupalGet('/vocabulary_serialization_test/test'); + $this->assertSession()->statusCodeEquals(200); + $this->assertSame('this is the output', $this->getSession()->getPage()->getContent()); + $this->assertSession()->responseHeaderEquals('X-Drupal-Cache', 'HIT'); + } + +} diff --git a/core/modules/taxonomy/tests/src/Kernel/Migrate/d7/MigrateTaxonomyVocabularyTest.php b/core/modules/taxonomy/tests/src/Kernel/Migrate/d7/MigrateTaxonomyVocabularyTest.php index f94d79197..3f29fbf77 100644 --- a/core/modules/taxonomy/tests/src/Kernel/Migrate/d7/MigrateTaxonomyVocabularyTest.php +++ b/core/modules/taxonomy/tests/src/Kernel/Migrate/d7/MigrateTaxonomyVocabularyTest.php @@ -54,9 +54,9 @@ class MigrateTaxonomyVocabularyTest extends MigrateDrupal7TestBase { * Tests the Drupal 7 taxonomy vocabularies to Drupal 8 migration. */ public function testTaxonomyVocabulary() { - $this->assertEntity('tags', 'Tags', 'Use tags to group articles on similar topics into categories.', TAXONOMY_HIERARCHY_DISABLED, 0); - $this->assertEntity('forums', 'Forums', 'Forum navigation vocabulary', TAXONOMY_HIERARCHY_SINGLE, -10); - $this->assertEntity('test_vocabulary', 'Test Vocabulary', 'This is the vocabulary description', TAXONOMY_HIERARCHY_SINGLE, 0); + $this->assertEntity('tags', 'Tags', 'Use tags to group articles on similar topics into categories.', VocabularyInterface::HIERARCHY_DISABLED, 0); + $this->assertEntity('forums', 'Forums', 'Forum navigation vocabulary', VocabularyInterface::HIERARCHY_SINGLE, -10); + $this->assertEntity('test_vocabulary', 'Test Vocabulary', 'This is the vocabulary description', VocabularyInterface::HIERARCHY_SINGLE, 0); } } diff --git a/core/modules/taxonomy/tests/src/Kernel/Plugin/migrate/source/TermSourceWithVocabularyFilterTest.php b/core/modules/taxonomy/tests/src/Kernel/Plugin/migrate/source/TermSourceWithVocabularyFilterTest.php new file mode 100644 index 000000000..4ac2766d7 --- /dev/null +++ b/core/modules/taxonomy/tests/src/Kernel/Plugin/migrate/source/TermSourceWithVocabularyFilterTest.php @@ -0,0 +1,56 @@ + 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'] = [ + 'vocabulary' => [5], + ]; + + return $tests; + } + +} diff --git a/core/modules/taxonomy/tests/src/Kernel/Plugin/migrate/source/TermTest.php b/core/modules/taxonomy/tests/src/Kernel/Plugin/migrate/source/TermTest.php new file mode 100644 index 000000000..bdffcf81c --- /dev/null +++ b/core/modules/taxonomy/tests/src/Kernel/Plugin/migrate/source/TermTest.php @@ -0,0 +1,157 @@ + 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, + ], + ]; + $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, + ], + ]; + + // 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], + ], + ]; + + return $tests; + } + +} diff --git a/core/modules/taxonomy/tests/src/Unit/Migrate/d6/TermNodeTest.php b/core/modules/taxonomy/tests/src/Kernel/Plugin/migrate/source/d6/TermNodeTest.php similarity index 54% rename from core/modules/taxonomy/tests/src/Unit/Migrate/d6/TermNodeTest.php rename to core/modules/taxonomy/tests/src/Kernel/Plugin/migrate/source/d6/TermNodeTest.php index 6ef0bd79f..3084acffa 100644 --- a/core/modules/taxonomy/tests/src/Unit/Migrate/d6/TermNodeTest.php +++ b/core/modules/taxonomy/tests/src/Kernel/Plugin/migrate/source/d6/TermNodeTest.php @@ -1,59 +1,48 @@ 'test', - 'source' => array( - 'plugin' => 'd6_term_node', - 'vid' => 3, - ), - ); - - protected $expectedResults = array( - array( - 'nid' => 1, - 'vid' => 1, - 'type' => 'story', - 'tid' => array(1, 4, 5), - ), - ); +class TermNodeTest extends MigrateSqlSourceTestBase { /** * {@inheritdoc} */ - protected function setUp() { - $this->databaseContents['term_node'] = array( - array( + public static $modules = ['taxonomy', 'migrate_drupal']; + + /** + * {@inheritdoc} + */ + public function providerSource() { + $tests = []; + + // The source data. + $tests[0]['source_data']['term_node'] = [ + [ 'nid' => '1', 'vid' => '1', 'tid' => '1', - ), - array( + ], + [ 'nid' => '1', 'vid' => '1', 'tid' => '4', - ), - array( + ], + [ 'nid' => '1', 'vid' => '1', 'tid' => '5', - ), - ); - $this->databaseContents['node'] = array( - array( + ], + ]; + $tests[0]['source_data']['node'] = [ + [ 'nid' => '1', 'vid' => '1', 'type' => 'story', @@ -69,32 +58,51 @@ class TermNodeTest extends MigrateSqlSourceTestCase { 'sticky' => '0', 'tnid' => '0', 'translate' => '0', - ), - ); - $this->databaseContents['term_data'] = array( - array( + ], + ]; + $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', - ), - array( + ], + [ 'tid' => '4', 'vid' => '3', 'name' => 'term 4 of vocabulary 3', 'description' => 'description of term 4 of vocabulary 3', 'weight' => '6', - ), - array( + ], + [ 'tid' => '5', 'vid' => '3', 'name' => 'term 5 of vocabulary 3', 'description' => 'description of term 5 of vocabulary 3', 'weight' => '7', - ), - ); - parent::setUp(); + ], + ]; + + // 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; } } diff --git a/core/modules/taxonomy/tests/src/Kernel/Plugin/migrate/source/d6/VocabularyTest.php b/core/modules/taxonomy/tests/src/Kernel/Plugin/migrate/source/d6/VocabularyTest.php new file mode 100644 index 000000000..a98b2985e --- /dev/null +++ b/core/modules/taxonomy/tests/src/Kernel/Plugin/migrate/source/d6/VocabularyTest.php @@ -0,0 +1,108 @@ + 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; + } + +} diff --git a/core/modules/taxonomy/tests/src/Kernel/Plugin/migrate/source/d7/VocabularyTest.php b/core/modules/taxonomy/tests/src/Kernel/Plugin/migrate/source/d7/VocabularyTest.php new file mode 100644 index 000000000..4d1975ceb --- /dev/null +++ b/core/modules/taxonomy/tests/src/Kernel/Plugin/migrate/source/d7/VocabularyTest.php @@ -0,0 +1,54 @@ + 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; + } + +} diff --git a/core/modules/taxonomy/tests/src/Unit/Migrate/TermSourceWithVocabularyFilterTest.php b/core/modules/taxonomy/tests/src/Unit/Migrate/TermSourceWithVocabularyFilterTest.php deleted file mode 100644 index 67c46cb5f..000000000 --- a/core/modules/taxonomy/tests/src/Unit/Migrate/TermSourceWithVocabularyFilterTest.php +++ /dev/null @@ -1,25 +0,0 @@ -migrationConfiguration['source']['vocabulary'] = array(5); - parent::setUp(); - $this->expectedResults = array_values(array_filter($this->expectedResults, function($result) { - return $result['vid'] == 5; - })); - // We know there are two rows with vid == 5. - $this->expectedCount = 2; - } - -} diff --git a/core/modules/taxonomy/tests/src/Unit/Migrate/TermTest.php b/core/modules/taxonomy/tests/src/Unit/Migrate/TermTest.php deleted file mode 100644 index ad2706bb7..000000000 --- a/core/modules/taxonomy/tests/src/Unit/Migrate/TermTest.php +++ /dev/null @@ -1,12 +0,0 @@ - 'test', - 'source' => array( - 'plugin' => 'd6_taxonomy_term', - ), - ); - - protected $expectedResults = array( - array( - 'tid' => 1, - 'vid' => 5, - 'name' => 'name value 1', - 'description' => 'description value 1', - 'weight' => 0, - 'parent' => array(0), - ), - array( - 'tid' => 2, - 'vid' => 6, - 'name' => 'name value 2', - 'description' => 'description value 2', - 'weight' => 0, - 'parent' => array(0), - ), - array( - 'tid' => 3, - 'vid' => 6, - 'name' => 'name value 3', - 'description' => 'description value 3', - 'weight' => 0, - 'parent' => array(0), - ), - array( - 'tid' => 4, - 'vid' => 5, - 'name' => 'name value 4', - 'description' => 'description value 4', - 'weight' => 1, - 'parent' => array(1), - ), - array( - 'tid' => 5, - 'vid' => 6, - 'name' => 'name value 5', - 'description' => 'description value 5', - 'weight' => 1, - 'parent' => array(2), - ), - array( - 'tid' => 6, - 'vid' => 6, - 'name' => 'name value 6', - 'description' => 'description value 6', - 'weight' => 0, - 'parent' => array(3, 2), - ), - ); - - /** - * {@inheritdoc} - */ - protected function setUp() { - foreach ($this->expectedResults as $k => $row) { - foreach ($row['parent'] as $parent) { - $this->databaseContents['term_hierarchy'][] = array( - 'tid' => $row['tid'], - 'parent' => $parent, - ); - } - unset($row['parent']); - $this->databaseContents['term_data'][$k] = $row; - } - parent::setUp(); - } - -} diff --git a/core/modules/taxonomy/tests/src/Unit/Migrate/d6/VocabularyTest.php b/core/modules/taxonomy/tests/src/Unit/Migrate/d6/VocabularyTest.php deleted file mode 100644 index 9227b80cc..000000000 --- a/core/modules/taxonomy/tests/src/Unit/Migrate/d6/VocabularyTest.php +++ /dev/null @@ -1,74 +0,0 @@ - 'test', - 'source' => [ - 'plugin' => 'd6_vocabulary', - ], - ]; - - protected $expectedResults = [ - [ - '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, - ], - ]; - - /** - * {@inheritdoc} - */ - protected function setUp() { - foreach ($this->expectedResults as &$row) { - foreach ($row['node_types'] as $type) { - $this->databaseContents['vocabulary_node_types'][] = [ - 'type' => $type, - 'vid' => $row['vid'], - ]; - } - unset($row['node_types']); - } - $this->databaseContents['vocabulary'] = $this->expectedResults; - parent::setUp(); - } - -} diff --git a/core/modules/taxonomy/tests/src/Unit/Migrate/d7/VocabularyTest.php b/core/modules/taxonomy/tests/src/Unit/Migrate/d7/VocabularyTest.php deleted file mode 100644 index 88cd14467..000000000 --- a/core/modules/taxonomy/tests/src/Unit/Migrate/d7/VocabularyTest.php +++ /dev/null @@ -1,52 +0,0 @@ - 'test', - 'source' => [ - 'plugin' => 'd7_vocabulary', - ], - ]; - - protected $expectedResults = [ - [ - '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', - ], - ]; - - /** - * {@inheritdoc} - */ - protected function setUp() { - $this->databaseContents['taxonomy_vocabulary'] = $this->expectedResults; - parent::setUp(); - } - -} diff --git a/core/modules/tracker/tests/src/Kernel/Plugin/migrate/source/d7/TrackerNodeTest.php b/core/modules/tracker/tests/src/Kernel/Plugin/migrate/source/d7/TrackerNodeTest.php new file mode 100644 index 000000000..0318b8e7b --- /dev/null +++ b/core/modules/tracker/tests/src/Kernel/Plugin/migrate/source/d7/TrackerNodeTest.php @@ -0,0 +1,42 @@ + '2', + 'published' => '1', + 'changed' => '1421727536', + ] + ]; + + // The expected results are identical to the source data. + $tests[0]['expected_results'] = $tests[0]['database']['tracker_node']; + + return $tests; + } + +} diff --git a/core/modules/tracker/tests/src/Kernel/Plugin/migrate/source/d7/TrackerUserTest.php b/core/modules/tracker/tests/src/Kernel/Plugin/migrate/source/d7/TrackerUserTest.php new file mode 100644 index 000000000..ffcee93b5 --- /dev/null +++ b/core/modules/tracker/tests/src/Kernel/Plugin/migrate/source/d7/TrackerUserTest.php @@ -0,0 +1,43 @@ + '1', + 'uid' => '2', + 'published' => '1', + 'changed' => '1421727536', + ] + ]; + + // The expected results are identical to the source data. + $tests[0]['expected_results'] = $tests[0]['database']['tracker_user']; + + return $tests; + } + +} diff --git a/core/modules/tracker/tests/src/Unit/Plugin/migrate/source/d7/TrackerNodeTest.php b/core/modules/tracker/tests/src/Unit/Plugin/migrate/source/d7/TrackerNodeTest.php deleted file mode 100644 index 49f42400a..000000000 --- a/core/modules/tracker/tests/src/Unit/Plugin/migrate/source/d7/TrackerNodeTest.php +++ /dev/null @@ -1,39 +0,0 @@ - 'test', - 'source' => [ - 'plugin' => 'd7_tracker_node', - ], - ]; - - protected $expectedResults = [ - [ - 'nid' => '2', - 'published' => '1', - 'changed' => '1421727536', - ] - ]; - - /** - * {@inheritdoc} - */ - protected function setUp() { - $this->databaseContents['tracker_node'] = $this->expectedResults; - parent::setUp(); - } - -} diff --git a/core/modules/tracker/tests/src/Unit/Plugin/migrate/source/d7/TrackerUserTest.php b/core/modules/tracker/tests/src/Unit/Plugin/migrate/source/d7/TrackerUserTest.php deleted file mode 100644 index 847869e2f..000000000 --- a/core/modules/tracker/tests/src/Unit/Plugin/migrate/source/d7/TrackerUserTest.php +++ /dev/null @@ -1,40 +0,0 @@ - 'test', - 'source' => [ - 'plugin' => 'd7_tracker_user', - ], - ]; - - protected $expectedResults = [ - [ - 'nid' => '1', - 'uid' => '2', - 'published' => '1', - 'changed' => '1421727536', - ] - ]; - - /** - * {@inheritdoc} - */ - protected function setUp() { - $this->databaseContents['tracker_user'] = $this->expectedResults; - parent::setUp(); - } - -} diff --git a/core/modules/user/migration_templates/d6_user.yml b/core/modules/user/migration_templates/d6_user.yml index bf6ec2ade..fa6326e93 100644 --- a/core/modules/user/migration_templates/d6_user.yml +++ b/core/modules/user/migration_templates/d6_user.yml @@ -16,7 +16,18 @@ process: timezone: plugin: user_update_7002 source: timezone - preferred_langcode: language + langcode: + plugin: user_langcode + source: language + fallback_to_site_default: false + preferred_langcode: + plugin: user_langcode + source: language + fallback_to_site_default: true + preferred_admin_langcode: + plugin: user_langcode + source: language + fallback_to_site_default: true init: init roles: plugin: migration @@ -34,6 +45,8 @@ migration_dependencies: required: - d6_user_role optional: + - language + - default_language - d6_user_picture_file - user_picture_entity_display - user_picture_entity_form_display diff --git a/core/modules/user/migration_templates/d6_user_role.yml b/core/modules/user/migration_templates/d6_user_role.yml index a97d5194f..1d224f489 100644 --- a/core/modules/user/migration_templates/d6_user_role.yml +++ b/core/modules/user/migration_templates/d6_user_role.yml @@ -9,11 +9,6 @@ process: - plugin: machine_name source: name - - - plugin: dedupe_entity - entity_type: user_role - field: id - length: 32 - plugin: user_update_8002 label: name @@ -26,15 +21,15 @@ process: 'use PHP for block visibility': 'use PHP for settings' 'administer site-wide contact form': 'administer contact forms' 'post comments without approval': 'skip comment approval' - 'edit own blog entries' : 'edit own blog content' - 'edit any blog entry' : 'edit any blog content' - 'delete own blog entries' : 'delete own blog content' - 'delete any blog entry' : 'delete any blog content' - 'create forum topics' : 'create forum content' - 'delete any forum topic' : 'delete any forum content' - 'delete own forum topics' : 'delete own forum content' - 'edit any forum topic' : 'edit any forum content' - 'edit own forum topics' : 'edit own forum content' + 'edit own blog entries': 'edit own blog content' + 'edit any blog entry': 'edit any blog content' + 'delete own blog entries': 'delete own blog content' + 'delete any blog entry': 'delete any blog content' + 'create forum topics': 'create forum content' + 'delete any forum topic': 'delete any forum content' + 'delete own forum topics': 'delete own forum content' + 'edit any forum topic': 'edit any forum content' + 'edit own forum topics': 'edit own forum content' - plugin: system_update_7000 - plugin: node_update_7008 - plugin: flatten diff --git a/core/modules/user/migration_templates/d7_user.yml b/core/modules/user/migration_templates/d7_user.yml index d68fddff4..b72e8c728 100644 --- a/core/modules/user/migration_templates/d7_user.yml +++ b/core/modules/user/migration_templates/d7_user.yml @@ -15,9 +15,18 @@ process: login: login status: status timezone: timezone - langcode: language - preferred_langcode: language - preferred_admin_langcode: language + langcode: + plugin: user_langcode + source: language + fallback_to_site_default: false + preferred_langcode: + plugin: user_langcode + source: language + fallback_to_site_default: true + preferred_admin_langcode: + plugin: user_langcode + source: language + fallback_to_site_default: true init: init roles: plugin: migration @@ -38,6 +47,8 @@ migration_dependencies: - d7_user_role optional: - d7_file + - language + - default_language - user_picture_field_instance - user_picture_entity_display - user_picture_entity_form_display diff --git a/core/modules/user/migration_templates/d7_user_role.yml b/core/modules/user/migration_templates/d7_user_role.yml index 68c0d1168..5f887574e 100644 --- a/core/modules/user/migration_templates/d7_user_role.yml +++ b/core/modules/user/migration_templates/d7_user_role.yml @@ -9,11 +9,6 @@ process: - plugin: machine_name source: name - - - plugin: dedupe_entity - entity_type: user_role - field: id - length: 32 - plugin: user_update_8002 label: name @@ -26,15 +21,15 @@ process: 'use PHP for block visibility': 'use PHP for settings' 'administer site-wide contact form': 'administer contact forms' 'post comments without approval': 'skip comment approval' - 'edit own blog entries' : 'edit own blog content' - 'edit any blog entry' : 'edit any blog content' - 'delete own blog entries' : 'delete own blog content' - 'delete any blog entry' : 'delete any blog content' - 'create forum topics' : 'create forum content' - 'delete any forum topic' : 'delete any forum content' - 'delete own forum topics' : 'delete own forum content' - 'edit any forum topic' : 'edit any forum content' - 'edit own forum topics' : 'edit own forum content' + 'edit own blog entries': 'edit own blog content' + 'edit any blog entry': 'edit any blog content' + 'delete own blog entries': 'delete own blog content' + 'delete any blog entry': 'delete any blog content' + 'create forum topics': 'create forum content' + 'delete any forum topic': 'delete any forum content' + 'delete own forum topics': 'delete own forum content' + 'edit any forum topic': 'edit any forum content' + 'edit own forum topics': 'edit own forum content' - plugin: flatten weight: weight destination: diff --git a/core/modules/user/src/Entity/User.php b/core/modules/user/src/Entity/User.php index 030b9a933..6a3d6b161 100644 --- a/core/modules/user/src/Entity/User.php +++ b/core/modules/user/src/Entity/User.php @@ -416,6 +416,9 @@ class User extends ContentEntityBase implements UserInterface { static::$anonymousUser = new $class([ 'uid' => [LanguageInterface::LANGCODE_DEFAULT => 0], 'name' => [LanguageInterface::LANGCODE_DEFAULT => ''], + // Explicitly set the langcode to ensure that field definitions do not + // need to be fetched to figure out a default. + 'langcode' => [LanguageInterface::LANGCODE_DEFAULT => LanguageInterface::LANGCODE_NOT_SPECIFIED] ], $entity_type->id()); } return clone static::$anonymousUser; diff --git a/core/modules/user/src/Plugin/migrate/process/UserLangcode.php b/core/modules/user/src/Plugin/migrate/process/UserLangcode.php new file mode 100644 index 000000000..80f20783b --- /dev/null +++ b/core/modules/user/src/Plugin/migrate/process/UserLangcode.php @@ -0,0 +1,86 @@ +languageManager = $language_manager; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) { + return new static( + $configuration, + $plugin_id, + $plugin_definition, + $container->get('language_manager') + ); + } + + /** + * {@inheritdoc} + */ + public function transform($value, MigrateExecutableInterface $migrate_executable, Row $row, $destination_property) { + if (!isset($this->configuration['fallback_to_site_default'])) { + $this->configuration['fallback_to_site_default'] = TRUE; + } + + // If the user's language is empty, it means the locale module was not + // installed, so the user's langcode should be English and the user's + // preferred_langcode and preferred_admin_langcode should fallback to the + // default language. + if (empty($value)) { + if ($this->configuration['fallback_to_site_default']) { + return $this->languageManager->getDefaultLanguage()->getId(); + } + else { + return 'en'; + } + } + // If the user's language does not exists, use the default language. + elseif ($this->languageManager->getLanguage($value) === NULL) { + return $this->languageManager->getDefaultLanguage()->getId(); + } + + // If the langcode is a valid one, just return it. + return $value; + } + +} diff --git a/core/modules/user/src/Plugin/migrate/source/d6/ProfileFieldValues.php b/core/modules/user/src/Plugin/migrate/source/d6/ProfileFieldValues.php index 0970b8737..0ccbbeb51 100644 --- a/core/modules/user/src/Plugin/migrate/source/d6/ProfileFieldValues.php +++ b/core/modules/user/src/Plugin/migrate/source/d6/ProfileFieldValues.php @@ -21,7 +21,7 @@ class ProfileFieldValues extends DrupalSqlBase { public function query() { $query = $this->select('profile_values', 'pv') ->distinct() - ->fields('pv', array('fid', 'uid')); + ->fields('pv', array('uid')); return $query; } diff --git a/core/modules/user/src/Plugin/migrate/source/d6/Role.php b/core/modules/user/src/Plugin/migrate/source/d6/Role.php index f7bac99c2..2357c5c86 100644 --- a/core/modules/user/src/Plugin/migrate/source/d6/Role.php +++ b/core/modules/user/src/Plugin/migrate/source/d6/Role.php @@ -69,7 +69,15 @@ class Role extends DrupalSqlBase { ->condition('rid', $rid) ->execute() ->fetchField(); - $row->setSourceProperty('permissions', explode(', ', $permissions)); + + // If a role has no permissions then set to an empty array. The role will + // be migrated and given the default D8 permissions. + if ($permissions) { + $row->setSourceProperty('permissions', explode(', ', $permissions)); + } + else { + $row->setSourceProperty('permissions', []); + } if (isset($this->filterPermissions[$rid])) { $row->setSourceProperty("filter_permissions:$rid", $this->filterPermissions[$rid]); } diff --git a/core/modules/user/src/Plugin/migrate/source/d6/User.php b/core/modules/user/src/Plugin/migrate/source/d6/User.php index 1260ca75e..56f68bf8c 100644 --- a/core/modules/user/src/Plugin/migrate/source/d6/User.php +++ b/core/modules/user/src/Plugin/migrate/source/d6/User.php @@ -100,6 +100,7 @@ class User extends DrupalSqlBase { 'name' => $this->t('Username'), 'pass' => $this->t('Password'), 'mail' => $this->t('Email address'), + 'theme' => $this->t('Theme'), 'signature' => $this->t('Signature'), 'signature_format' => $this->t('Signature format'), 'created' => $this->t('Registered timestamp'), diff --git a/core/modules/user/tests/src/Kernel/Migrate/d6/MigrateUserProfileValuesTest.php b/core/modules/user/tests/src/Kernel/Migrate/d6/MigrateUserProfileValuesTest.php index 978c0bf45..e734517a5 100644 --- a/core/modules/user/tests/src/Kernel/Migrate/d6/MigrateUserProfileValuesTest.php +++ b/core/modules/user/tests/src/Kernel/Migrate/d6/MigrateUserProfileValuesTest.php @@ -12,6 +12,11 @@ use Drupal\user\Entity\User; */ class MigrateUserProfileValuesTest extends MigrateDrupal6TestBase { + /** + * {@inheritdoc} + */ + public static $modules = ['language']; + /** * {@inheritdoc} */ @@ -19,6 +24,7 @@ class MigrateUserProfileValuesTest extends MigrateDrupal6TestBase { parent::setUp(); $this->executeMigrations([ + 'language', 'user_profile_field', 'user_profile_field_instance', 'user_profile_entity_display', diff --git a/core/modules/user/tests/src/Kernel/Migrate/d6/MigrateUserRoleTest.php b/core/modules/user/tests/src/Kernel/Migrate/d6/MigrateUserRoleTest.php index d3f49ccfc..a52b6a771 100644 --- a/core/modules/user/tests/src/Kernel/Migrate/d6/MigrateUserRoleTest.php +++ b/core/modules/user/tests/src/Kernel/Migrate/d6/MigrateUserRoleTest.php @@ -3,7 +3,9 @@ namespace Drupal\Tests\user\Kernel\Migrate\d6; use Drupal\user\Entity\Role; +use Drupal\user\RoleInterface; use Drupal\Tests\migrate_drupal\Kernel\d6\MigrateDrupal6TestBase; +use Drupal\migrate\Plugin\MigrateIdMapInterface; /** * Upgrade user roles to user.role.*.yml. @@ -21,29 +23,69 @@ class MigrateUserRoleTest extends MigrateDrupal6TestBase { } /** - * Tests user role migration. + * Helper function to perform assertions on a user role. + * + * @param string $id + * The role ID. + * @param string[] $permissions + * An array of user permissions. + * @param int $lookupId + * The original numeric ID of the role in the source database. + * @param \Drupal\migrate\Plugin\MigrateIdMapInterface $id_map + * The map table plugin. */ - public function testUserRole() { - /** @var \Drupal\migrate\Plugin\MigrationInterface $migration */ - $id_map = $this->getMigration('d6_user_role')->getIdMap(); - $rid = 'anonymous'; - $anonymous = Role::load($rid); - $this->assertSame($rid, $anonymous->id()); - $this->assertSame(array('migrate test anonymous permission', 'use text format filtered_html'), $anonymous->getPermissions()); - $this->assertSame(array($rid), $id_map->lookupDestinationId(array(1))); - $rid = 'authenticated'; - $authenticated = Role::load($rid); - $this->assertSame($rid, $authenticated->id()); - $this->assertSame(array('migrate test authenticated permission', 'use text format filtered_html'), $authenticated->getPermissions()); - $this->assertSame(array($rid), $id_map->lookupDestinationId(array(2))); - $rid = 'migrate_test_role_1'; - $migrate_test_role_1 = Role::load($rid); - $this->assertSame($rid, $migrate_test_role_1->id()); - $this->assertSame(array('migrate test role 1 test permission', 'use text format full_html', 'use text format php_code'), $migrate_test_role_1->getPermissions()); - $this->assertSame(array($rid), $id_map->lookupDestinationId(array(3))); - $rid = 'migrate_test_role_2'; - $migrate_test_role_2 = Role::load($rid); - $this->assertSame(array( + protected function assertRole($id, array $permissions, $lookupId, MigrateIdMapInterface $id_map) { + /** @var \Drupal\user\RoleInterface $role */ + $role = Role::load($id); + $this->assertInstanceOf(RoleInterface::class, $role); + $this->assertSame($permissions, $role->getPermissions()); + $this->assertSame([[$id]], $id_map->lookupDestinationIds(['rid' => $lookupId])); + } + + /** + * Helper function to test the migration of the user roles. The user roles + * will be re-imported and the tests here will be repeated. + * + * @param \Drupal\migrate\Plugin\MigrateIdMapInterface $id_map + * The map table plugin. + */ + protected function assertRoles(MigrateIdMapInterface $id_map) { + + // The permissions for each role are found in the two tables in the Drupal 6 + // source database. One is the permission table and the other is the + // filter_format table. + $permissions = [ + // From permission table. + 'access content', + 'migrate test anonymous permission', + // From filter_format tables. + 'use text format filtered_html' + ]; + $this->assertRole('anonymous', $permissions, 1, $id_map); + + $permissions = [ + // From permission table. + 'access comments', + 'access content', + 'post comments', + 'skip comment approval', + 'migrate test authenticated permission', + // From filter_format. + 'use text format filtered_html', + ]; + $this->assertRole('authenticated', $permissions, 2, $id_map); + + $permissions = [ + // From permission table. + 'migrate test role 1 test permission', + // From filter format. + 'use text format full_html', + 'use text format php_code' + ]; + $this->assertRole('migrate_test_role_1', $permissions, 3, $id_map); + + $permissions = [ + // From permission table. 'migrate test role 2 test permission', 'use PHP for settings', 'administer contact forms', @@ -59,14 +101,66 @@ class MigrateUserRoleTest extends MigrateDrupal6TestBase { 'edit own forum content', 'administer nodes', 'access content overview', + // From filter format. 'use text format php_code', - ), $migrate_test_role_2->getPermissions()); - $this->assertSame($rid, $migrate_test_role_2->id()); - $this->assertSame(array($rid), $id_map->lookupDestinationId(array(4))); - $rid = 'migrate_test_role_3_that_is_long'; - $migrate_test_role_3 = Role::load($rid); - $this->assertSame($rid, $migrate_test_role_3->id()); - $this->assertSame(array($rid), $id_map->lookupDestinationId(array(5))); + ]; + $this->assertRole('migrate_test_role_2', $permissions, 4, $id_map); + + // The only permission for this role is a filter format. + $permissions = ['use text format php_code']; + $this->assertRole('migrate_test_role_3_that_is_longer_than_thirty_two_characters', $permissions, 5, $id_map); + } + + /** + * Tests user role migration. + */ + public function testUserRole() { + $id_map = $this->getMigration('d6_user_role')->getIdMap(); + $this->assertRoles($id_map); + + // Test there are no duplicated roles. + $roles = [ + 'anonymous1', + 'authenticated1', + 'administrator1', + 'migrate_test_role_11', + 'migrate_test_role_21', + 'migrate_test_role_3_that_is_longer_than_thirty_two_characters1' + ]; + $this->assertEmpty(Role::loadMultiple($roles)); + + // Remove the map row for the migrate_test_role_1 role and rerun the + // migration. This will re-import the migrate_test_role_1 role migration + // again. + $this->sourceDatabase->insert('role') + ->fields([ + 'rid' => 6, + 'name' => 'migrate test role 4', + ]) + ->execute(); + $this->sourceDatabase->insert('permission') + ->fields([ + 'pid' => 7, + 'rid' => 6, + 'perm' => 'access content', + 'tid' => 0, + ]) + ->execute(); + + $id_map->delete(['rid' => 3]); + + $this->executeMigration('d6_user_role'); + + // Test there are no duplicated roles. + $roles[] = 'migrate_test_role_41'; + $this->assertEmpty(Role::loadMultiple($roles)); + + // Test that the existing roles have not changed. + $this->assertRoles($id_map); + + // Test the migration of the new role, migrate_test_role_4. + $permissions = ['access content']; + $this->assertRole('migrate_test_role_4', $permissions, 6, $id_map); } } diff --git a/core/modules/user/tests/src/Kernel/Migrate/d6/MigrateUserTest.php b/core/modules/user/tests/src/Kernel/Migrate/d6/MigrateUserTest.php index 931783406..33952d177 100644 --- a/core/modules/user/tests/src/Kernel/Migrate/d6/MigrateUserTest.php +++ b/core/modules/user/tests/src/Kernel/Migrate/d6/MigrateUserTest.php @@ -19,6 +19,11 @@ class MigrateUserTest extends MigrateDrupal6TestBase { use FileMigrationTestTrait; + /** + * {@inheritdoc} + */ + public static $modules = ['language']; + /** * {@inheritdoc} */ @@ -60,6 +65,7 @@ class MigrateUserTest extends MigrateDrupal6TestBase { file_put_contents($file->getFileUri(), file_get_contents('core/modules/simpletest/files/image-2.jpg')); $file->save(); + $this->executeMigration('language'); $this->migrateUsers(); } @@ -91,28 +97,47 @@ class MigrateUserTest extends MigrateDrupal6TestBase { /** @var \Drupal\user\UserInterface $user */ $user = User::load($source->uid); - $this->assertIdentical($source->uid, $user->id()); - $this->assertIdentical($source->name, $user->label()); - $this->assertIdentical($source->mail, $user->getEmail()); - $this->assertIdentical($source->created, $user->getCreatedTime()); - $this->assertIdentical($source->access, $user->getLastAccessedTime()); - $this->assertIdentical($source->login, $user->getLastLoginTime()); + $this->assertSame($source->uid, $user->id()); + $this->assertSame($source->name, $user->label()); + $this->assertSame($source->mail, $user->getEmail()); + $this->assertSame($source->created, $user->getCreatedTime()); + $this->assertSame($source->access, $user->getLastAccessedTime()); + $this->assertSame($source->login, $user->getLastLoginTime()); $is_blocked = $source->status == 0; - $this->assertIdentical($is_blocked, $user->isBlocked()); + $this->assertSame($is_blocked, $user->isBlocked()); + $expected_timezone_name = $source->timezone_name ?: $this->config('system.date')->get('timezone.default'); + $this->assertSame($expected_timezone_name, $user->getTimeZone()); + $this->assertSame($source->init, $user->getInitialEmail()); + $this->assertSame($roles, $user->getRoles()); + + // Ensure the user's langcode, preferred_langcode and + // preferred_admin_langcode are valid. // $user->getPreferredLangcode() might fallback to default language if the // user preferred language is not configured on the site. We just want to // test if the value was imported correctly. - $this->assertIdentical($source->language, $user->preferred_langcode->value); - $expected_timezone_name = $source->timezone_name ?: $this->config('system.date')->get('timezone.default'); - $this->assertIdentical($expected_timezone_name, $user->getTimeZone()); - $this->assertIdentical($source->init, $user->getInitialEmail()); - $this->assertIdentical($roles, $user->getRoles()); + $language_manager = $this->container->get('language_manager'); + $default_langcode = $language_manager->getDefaultLanguage()->getId(); + if (empty($source->language)) { + $this->assertSame('en', $user->langcode->value); + $this->assertSame($default_langcode, $user->preferred_langcode->value); + $this->assertSame($default_langcode, $user->preferred_admin_langcode->value); + } + elseif ($language_manager->getLanguage($source->language) === NULL) { + $this->assertSame($default_langcode, $user->langcode->value); + $this->assertSame($default_langcode, $user->preferred_langcode->value); + $this->assertSame($default_langcode, $user->preferred_admin_langcode->value); + } + else { + $this->assertSame($source->language, $user->langcode->value); + $this->assertSame($source->language, $user->preferred_langcode->value); + $this->assertSame($source->language, $user->preferred_admin_langcode->value); + } // We have one empty picture in the data so don't try load that. if (!empty($source->picture)) { // Test the user picture. $file = File::load($user->user_picture->target_id); - $this->assertIdentical(basename($source->picture), $file->getFilename()); + $this->assertSame(basename($source->picture), $file->getFilename()); } else { // Ensure the user does not have a picture. diff --git a/core/modules/user/tests/src/Kernel/Migrate/d7/MigrateUserRoleTest.php b/core/modules/user/tests/src/Kernel/Migrate/d7/MigrateUserRoleTest.php index de0d0b96a..edd5e3c03 100644 --- a/core/modules/user/tests/src/Kernel/Migrate/d7/MigrateUserRoleTest.php +++ b/core/modules/user/tests/src/Kernel/Migrate/d7/MigrateUserRoleTest.php @@ -56,6 +56,50 @@ class MigrateUserRoleTest extends MigrateDrupal7TestBase { $this->assertEntity('anonymous', 'anonymous user', 1); $this->assertEntity('authenticated', 'authenticated user', 2); $this->assertEntity('administrator', 'administrator', 3); + // Test there are no duplicated roles. + $roles = [ + 'anonymous1', + 'authenticated1', + 'administrator1', + ]; + $this->assertEmpty(Role::loadMultiple($roles)); + + // Remove the map row for the administrator role and rerun the migration. + // This will re-import the administrator role again. + $id_map = $this->getMigration('d7_user_role')->getIdMap(); + $id_map->delete(['rid' => 3]); + + $this->sourceDatabase->insert('role') + ->fields([ + 'rid' => 4, + 'name' => 'test role', + 'weight' => 10, + ]) + ->execute(); + $this->sourceDatabase->insert('role_permission') + ->fields([ + 'rid' => 4, + 'permission' => 'access content', + 'module' => 'node', + ]) + ->execute(); + $this->executeMigration('d7_user_role'); + + // Test there are no duplicated roles. + $roles = [ + 'anonymous1', + 'authenticated1', + 'administrator1', + ]; + $this->assertEmpty(Role::loadMultiple($roles)); + + // Test that the existing roles have not changed. + $this->assertEntity('administrator', 'administrator', 3); + $this->assertEntity('anonymous', 'anonymous user', 1); + $this->assertEntity('authenticated', 'authenticated user', 2); + + // Test the migration of the new role, test role. + $this->assertEntity('test_role', 'test role', 4); } } diff --git a/core/modules/user/tests/src/Kernel/Migrate/d7/MigrateUserTest.php b/core/modules/user/tests/src/Kernel/Migrate/d7/MigrateUserTest.php index 33bc5fe8b..c21856e5b 100644 --- a/core/modules/user/tests/src/Kernel/Migrate/d7/MigrateUserTest.php +++ b/core/modules/user/tests/src/Kernel/Migrate/d7/MigrateUserTest.php @@ -3,6 +3,7 @@ namespace Drupal\Tests\user\Kernel\Migrate\d7; use Drupal\comment\Entity\CommentType; +use Drupal\Core\Database\Database; use Drupal\node\Entity\NodeType; use Drupal\taxonomy\Entity\Vocabulary; use Drupal\Tests\migrate_drupal\Kernel\d7\MigrateDrupal7TestBase; @@ -25,6 +26,7 @@ class MigrateUserTest extends MigrateDrupal7TestBase { 'datetime', 'file', 'image', + 'language', 'link', 'node', 'system', @@ -49,6 +51,7 @@ class MigrateUserTest extends MigrateDrupal7TestBase { $this->createType('test_content_type'); Vocabulary::create(['vid' => 'test_vocabulary'])->save(); $this->executeMigrations([ + 'language', 'user_picture_field', 'user_picture_field_instance', 'd7_user_role', @@ -88,6 +91,8 @@ class MigrateUserTest extends MigrateDrupal7TestBase { * The user's email address. * @param string $password * The password for this user. + * @param int $created + * The user's creation time. * @param int $access * The last access time. * @param int $login @@ -96,37 +101,59 @@ class MigrateUserTest extends MigrateDrupal7TestBase { * Whether or not the account is blocked. * @param string $langcode * The user account's language code. + * @param string $timezone + * The user account's timezone name. * @param string $init * The user's initial email address. * @param string[] $roles * Role IDs the user account is expected to have. - * @param bool $has_picture - * Whether the user is expected to have a picture attached. * @param int $field_integer * The value of the integer field. + * @param bool $has_picture + * Whether the user is expected to have a picture attached. */ - protected function assertEntity($id, $label, $mail, $password, $access, $login, $blocked, $langcode, $init, array $roles = [RoleInterface::AUTHENTICATED_ID], $has_picture = FALSE, $field_integer = NULL) { + protected function assertEntity($id, $label, $mail, $password, $created, $access, $login, $blocked, $langcode, $timezone, $init, $roles, $field_integer, $has_picture = FALSE) { /** @var \Drupal\user\UserInterface $user */ $user = User::load($id); $this->assertTrue($user instanceof UserInterface); - $this->assertIdentical($label, $user->label()); - $this->assertIdentical($mail, $user->getEmail()); - $this->assertIdentical($access, $user->getLastAccessedTime()); - $this->assertIdentical($login, $user->getLastLoginTime()); - $this->assertIdentical($blocked, $user->isBlocked()); + $this->assertSame($label, $user->label()); + $this->assertSame($mail, $user->getEmail()); + $this->assertSame($password, $user->getPassword()); + $this->assertSame($created, $user->getCreatedTime()); + $this->assertSame($access, $user->getLastAccessedTime()); + $this->assertSame($login, $user->getLastLoginTime()); + $this->assertNotSame($blocked, $user->isBlocked()); + + // Ensure the user's langcode, preferred_langcode and + // preferred_admin_langcode are valid. // $user->getPreferredLangcode() might fallback to default language if the // user preferred language is not configured on the site. We just want to // test if the value was imported correctly. - $this->assertIdentical($langcode, $user->langcode->value); - $this->assertIdentical($langcode, $user->preferred_langcode->value); - $this->assertIdentical($langcode, $user->preferred_admin_langcode->value); - $this->assertIdentical($init, $user->getInitialEmail()); - $this->assertIdentical($roles, $user->getRoles()); - $this->assertIdentical($has_picture, !$user->user_picture->isEmpty()); - $this->assertIdentical($password, $user->getPassword()); + $language_manager = $this->container->get('language_manager'); + $default_langcode = $language_manager->getDefaultLanguage()->getId(); + if ($langcode == '') { + $this->assertSame('en', $user->langcode->value); + $this->assertSame($default_langcode, $user->preferred_langcode->value); + $this->assertSame($default_langcode, $user->preferred_admin_langcode->value); + } + elseif ($language_manager->getLanguage($langcode) === NULL) { + $this->assertSame($default_langcode, $user->langcode->value); + $this->assertSame($default_langcode, $user->preferred_langcode->value); + $this->assertSame($default_langcode, $user->preferred_admin_langcode->value); + } + else { + $this->assertSame($langcode, $user->langcode->value); + $this->assertSame($langcode, $user->preferred_langcode->value); + $this->assertSame($langcode, $user->preferred_admin_langcode->value); + } + + $this->assertSame($timezone, $user->getTimeZone()); + $this->assertSame($init, $user->getInitialEmail()); + $this->assertSame($roles, $user->getRoles()); + $this->assertSame($has_picture, !$user->user_picture->isEmpty()); if (!is_null($field_integer)) { $this->assertTrue($user->hasField('field_integer')); - $this->assertEquals($field_integer, $user->field_integer->value); + $this->assertEquals($field_integer[0], $user->field_integer->value); } } @@ -134,22 +161,65 @@ class MigrateUserTest extends MigrateDrupal7TestBase { * Tests the Drupal 7 user to Drupal 8 migration. */ public function testUser() { - $password = '$S$DGFZUE.FhrXbe4y52eC7p0ZVRGD/gOPtVctDlmC89qkujnBokAlJ'; - $this->assertEntity(2, 'Odo', 'odo@local.host', $password, '0', '0', FALSE, 'en', 'odo@local.host', [RoleInterface::AUTHENTICATED_ID], FALSE, 99); + $users = Database::getConnection('default', 'migrate') + ->select('users', 'u') + ->fields('u') + ->condition('uid', 1, '>') + ->execute() + ->fetchAll(); - // Ensure that the user can authenticate. - $this->assertEquals(2, \Drupal::service('user.auth')->authenticate('Odo', 'a password')); - // After authenticating the password will be rehashed because the password - // stretching iteration count has changed from 15 in Drupal 7 to 16 in - // Drupal 8. - $user = User::load(2); - $rehash = $user->getPassword(); - $this->assertNotEquals($password, $rehash); + foreach ($users as $source) { + $rids = Database::getConnection('default', 'migrate') + ->select('users_roles', 'ur') + ->fields('ur', array('rid')) + ->condition('ur.uid', $source->uid) + ->execute() + ->fetchCol(); + $roles = array(RoleInterface::AUTHENTICATED_ID); + $id_map = $this->getMigration('d7_user_role')->getIdMap(); + foreach ($rids as $rid) { + $role = $id_map->lookupDestinationId(array($rid)); + $roles[] = reset($role); + } - // Authenticate again and there should be no re-hash. - $this->assertEquals(2, \Drupal::service('user.auth')->authenticate('Odo', 'a password')); - $user = User::load(2); - $this->assertEquals($rehash, $user->getPassword()); + $field_integer = Database::getConnection('default', 'migrate') + ->select('field_data_field_integer', 'fi') + ->fields('fi', array('field_integer_value')) + ->condition('fi.entity_id', $source->uid) + ->execute() + ->fetchCol(); + $field_integer = !empty($field_integer) ? $field_integer : NULL; + + $this->assertEntity( + $source->uid, + $source->name, + $source->mail, + $source->pass, + $source->created, + $source->access, + $source->login, + $source->status, + $source->language, + $source->timezone, + $source->init, + $roles, + $field_integer + ); + + // Ensure that the user can authenticate. + $this->assertEquals($source->uid, $this->container->get('user.auth')->authenticate($source->name, 'a password')); + // After authenticating the password will be rehashed because the password + // stretching iteration count has changed from 15 in Drupal 7 to 16 in + // Drupal 8. + $user = User::load($source->uid); + $rehash = $user->getPassword(); + $this->assertNotEquals($source->pass, $rehash); + + // Authenticate again and there should be no re-hash. + $this->assertEquals($source->uid, $this->container->get('user.auth')->authenticate($source->name, 'a password')); + $user = User::load($source->uid); + $this->assertEquals($rehash, $user->getPassword()); + } } } diff --git a/core/modules/user/tests/src/Kernel/Plugin/migrate/source/ProfileFieldTest.php b/core/modules/user/tests/src/Kernel/Plugin/migrate/source/ProfileFieldTest.php new file mode 100644 index 000000000..c32118571 --- /dev/null +++ b/core/modules/user/tests/src/Kernel/Plugin/migrate/source/ProfileFieldTest.php @@ -0,0 +1,129 @@ + [], + 'expected_data' => [], + ], + ]; + + $profile_fields = [ + [ + 'fid' => 1, + 'title' => 'First name', + 'name' => 'profile_first_name', + 'explanation' => 'First name user', + 'category' => 'profile', + 'page' => '', + 'type' => 'textfield', + 'weight' => 0, + 'required' => 1, + 'register' => 0, + 'visibility' => 2, + 'autocomplete' => 0, + 'options' => '', + ], + [ + 'fid' => 2, + 'title' => 'Last name', + 'name' => 'profile_last_name', + 'explanation' => 'Last name user', + 'category' => 'profile', + 'page' => '', + 'type' => 'textfield', + 'weight' => 0, + 'required' => 0, + 'register' => 0, + 'visibility' => 2, + 'autocomplete' => 0, + 'options' => '', + ], + [ + 'fid' => 3, + 'title' => 'Policy', + 'name' => 'profile_policy', + 'explanation' => 'A checkbox that say if you accept policy of website', + 'category' => 'profile', + 'page' => '', + 'type' => 'checkbox', + 'weight' => 0, + 'required' => 1, + 'register' => 1, + 'visibility' => 2, + 'autocomplete' => 0, + 'options' => '', + ], + [ + 'fid' => 4, + 'title' => 'Color', + 'name' => 'profile_color', + 'explanation' => 'A selection that allows user to select a color', + 'category' => 'profile', + 'page' => '', + 'type' => 'selection', + 'weight' => 0, + 'required' => 0, + 'register' => 0, + 'visibility' => 2, + 'autocomplete' => 0, + 'options' => "red\nblue\ngreen", + ], + ]; + + $tests[0]['source_data']['profile_fields'] = $profile_fields; + + // Profile values are merged with pre-set options of a "selection" field. + $tests[0]['source_data']['profile_values'] = [ + [ + 'fid' => 4, + 'uid' => 1, + 'value' => 'yellow', + ] + ]; + + // Expected options are: + // for "checkbox" fields - array with NULL options + // for "selection" fields - options in both keys and values + $expected_field_options = [ + '', + '', + [NULL, NULL], + [ + 'red' => 'red', + 'blue' => 'blue', + 'green' => 'green', + 'yellow' => 'yellow', + ] + ]; + + $tests[0]['expected_data'] = $profile_fields; + + foreach ($tests[0]['expected_data'] as $delta => $row) { + $tests[0]['expected_data'][$delta]['options'] = $expected_field_options[$delta]; + } + + return $tests; + } + +} diff --git a/core/modules/user/tests/src/Kernel/Plugin/migrate/source/UserPictureInstanceTest.php b/core/modules/user/tests/src/Kernel/Plugin/migrate/source/UserPictureInstanceTest.php new file mode 100644 index 000000000..d9e77edde --- /dev/null +++ b/core/modules/user/tests/src/Kernel/Plugin/migrate/source/UserPictureInstanceTest.php @@ -0,0 +1,55 @@ + 'file_directory', + 'value' => serialize(NULL), + ], + [ + 'name' => 'user_picture_file_size', + 'value' => serialize(128), + ], + [ + 'name' => 'user_picture_dimensions', + 'value' => serialize('128x128'), + ], + ]; + + // The expected results. + $tests[0]['expected_data'] = [ + [ + 'id' => '', + 'file_directory' => 'pictures', + 'max_filesize' => '128KB', + 'max_resolution' => '128x128', + ], + ]; + + return $tests; + } + +} diff --git a/core/modules/user/tests/src/Unit/Migrate/d6/ProfileFieldValuesTest.php b/core/modules/user/tests/src/Kernel/Plugin/migrate/source/d6/ProfileFieldValuesTest.php similarity index 55% rename from core/modules/user/tests/src/Unit/Migrate/d6/ProfileFieldValuesTest.php rename to core/modules/user/tests/src/Kernel/Plugin/migrate/source/d6/ProfileFieldValuesTest.php index ce20e669c..26c2ed55f 100644 --- a/core/modules/user/tests/src/Unit/Migrate/d6/ProfileFieldValuesTest.php +++ b/core/modules/user/tests/src/Kernel/Plugin/migrate/source/d6/ProfileFieldValuesTest.php @@ -1,57 +1,44 @@ 'test', - 'source' => array( - 'plugin' => 'd6_profile_field_values', - ), - ); - - protected $expectedResults = array( - array( - 'fid' => '8', - 'profile_color' => array('red'), - 'uid' => '2', - ), - array( - 'fid' => '9', - 'profile_biography' => array('Lorem ipsum dolor sit amet...'), - 'uid' => '2', - ), - ); +class ProfileFieldValuesTest extends MigrateSqlSourceTestBase { /** * {@inheritdoc} */ - protected function setUp() { - $this->databaseContents['profile_values'] = array( - array( + public static $modules = ['user', 'migrate_drupal']; + + /** + * {@inheritdoc} + */ + public function providerSource() { + $tests = []; + + // The source data. + $tests[0]['source_data']['profile_values'] = [ + [ 'fid' => '8', 'uid' => '2', 'value' => 'red', - ), - array( + ], + [ 'fid' => '9', 'uid' => '2', 'value' => 'Lorem ipsum dolor sit amet...', - ), - ); - $this->databaseContents['profile_fields'] = array( - array( + ], + ]; + + $tests[0]['source_data']['profile_fields'] = [ + [ 'fid' => '8', 'title' => 'Favorite color', 'name' => 'profile_color', @@ -65,8 +52,8 @@ class ProfileFieldValuesTest extends MigrateSqlSourceTestCase { 'visibility' => '2', 'autocomplete' => '1', 'options' => '', - ), - array( + ], + [ 'fid' => '9', 'title' => 'Biography', 'name' => 'profile_biography', @@ -80,9 +67,19 @@ class ProfileFieldValuesTest extends MigrateSqlSourceTestCase { 'visibility' => '2', 'autocomplete' => '0', 'options' => '', - ), - ); - parent::setUp(); + ], + ]; + + // The expected results. + $tests[0]['expected_data'] = [ + [ + 'profile_color' => ['red'], + 'profile_biography' => ['Lorem ipsum dolor sit amet...'], + 'uid' => '2', + ], + ]; + + return $tests; } } diff --git a/core/modules/user/tests/src/Kernel/Plugin/migrate/source/d6/RoleTest.php b/core/modules/user/tests/src/Kernel/Plugin/migrate/source/d6/RoleTest.php new file mode 100644 index 000000000..1a841d32d --- /dev/null +++ b/core/modules/user/tests/src/Kernel/Plugin/migrate/source/d6/RoleTest.php @@ -0,0 +1,87 @@ + [], + 'expected_data' => [], + ], + ]; + + $roles = [ + [ + 'rid' => 1, + 'name' => 'anonymous user', + 'permissions' => [ + 'access content', + ], + ], + [ + 'rid' => 2, + 'name' => 'authenticated user', + 'permissions' => [ + 'access comments', + 'access content', + 'post comments', + 'post comments without approval', + ], + ], + [ + 'rid' => 3, + 'name' => 'administrator', + 'permissions' => [ + 'access comments', + 'administer comments', + 'post comments', + 'post comments without approval', + 'access content', + 'administer content types', + 'administer nodes', + ], + ], + ]; + + // The source data. + foreach ($roles as $role) { + $tests[0]['source_data']['permission'][] = [ + 'rid' => $role['rid'], + 'perm' => implode(', ', $role['permissions']), + ]; + unset($role['permissions']); + $tests[0]['source_data']['role'][] = $role; + } + + $tests[0]['source_data']['filter_formats'] = [ + [ + 'format' => 1, + 'roles' => '', + ], + ]; + + // The expected results. + $tests[0]['expected_data'] = $roles; + + return $tests; + } + +} diff --git a/core/modules/user/tests/src/Kernel/Plugin/migrate/source/d6/UserPictureFileTest.php b/core/modules/user/tests/src/Kernel/Plugin/migrate/source/d6/UserPictureFileTest.php new file mode 100644 index 000000000..2724d35f5 --- /dev/null +++ b/core/modules/user/tests/src/Kernel/Plugin/migrate/source/d6/UserPictureFileTest.php @@ -0,0 +1,49 @@ + '2', + 'picture' => 'core/modules/simpletest/files/image-test.jpg', + ], + [ + 'uid' => '15', + 'picture' => '', + ], + ]; + + // The expected results. + $tests[0]['expected_data'] = [ + [ + 'uid' => '2', + 'picture' => 'core/modules/simpletest/files/image-test.jpg', + ], + ]; + + return $tests; + } + +} diff --git a/core/modules/user/tests/src/Kernel/Plugin/migrate/source/d6/UserPictureTest.php b/core/modules/user/tests/src/Kernel/Plugin/migrate/source/d6/UserPictureTest.php new file mode 100644 index 000000000..aea6afa7c --- /dev/null +++ b/core/modules/user/tests/src/Kernel/Plugin/migrate/source/d6/UserPictureTest.php @@ -0,0 +1,46 @@ + 1, + 'access' => 1382835435, + 'picture' => 'sites/default/files/pictures/picture-1.jpg', + ], + [ + 'uid' => 2, + 'access' => 1382835436, + 'picture' => 'sites/default/files/pictures/picture-2.jpg', + ], + ]; + + // User picture data model is identical in source input and output. + $tests[0]['expected_data'] = $tests[0]['source_data']['users']; + + return $tests; + } + +} diff --git a/core/modules/user/tests/src/Kernel/Plugin/migrate/source/d6/UserTest.php b/core/modules/user/tests/src/Kernel/Plugin/migrate/source/d6/UserTest.php new file mode 100644 index 000000000..1dc34a7cd --- /dev/null +++ b/core/modules/user/tests/src/Kernel/Plugin/migrate/source/d6/UserTest.php @@ -0,0 +1,83 @@ + 2, + 'name' => 'admin', + 'pass' => '1234', + 'mail' => 'admin@example.com', + 'theme' => '', + 'signature' => '', + 'signature_format' => 0, + 'created' => 1279402616, + 'access' => 1322981278, + 'login' => 1322699994, + 'status' => 0, + 'timezone' => 'America/Lima', + 'language' => 'en', + // @todo Add the file when needed. + 'picture' => 'sites/default/files/pictures/picture-1.jpg', + 'init' => 'admin@example.com', + 'data' => NULL, + ], + [ + 'uid' => 4, + 'name' => 'alice', + // @todo d6 hash? + 'pass' => '1234', + 'mail' => 'alice@example.com', + 'theme' => '', + 'signature' => '', + 'signature_format' => 0, + 'created' => 1322981368, + 'access' => 1322982419, + 'login' => 132298140, + 'status' => 0, + 'timezone' => 'America/Lima', + 'language' => 'en', + 'picture' => '', + 'init' => 'alice@example.com', + 'data' => NULL, + ], + ]; + + // getDatabase() will not create empty tables, so we need to insert data + // even if it's irrelevant to the test. + $tests[0]['source_data']['users_roles'] = [ + [ + 'uid' => 99, + 'rid' => 99, + ], + ]; + + // The expected results. + $tests[0]['expected_data'] = $tests[0]['source_data']['users']; + + return $tests; + } + +} diff --git a/core/modules/user/tests/src/Kernel/Plugin/migrate/source/d7/UserTest.php b/core/modules/user/tests/src/Kernel/Plugin/migrate/source/d7/UserTest.php new file mode 100644 index 000000000..8e14d63c9 --- /dev/null +++ b/core/modules/user/tests/src/Kernel/Plugin/migrate/source/d7/UserTest.php @@ -0,0 +1,120 @@ + '33', + 'field_id' => '11', + 'field_name' => 'field_file', + 'entity_type' => 'user', + 'bundle' => 'user', + 'data' => 'a:0:{}', + 'deleted' => '0', + ], + ]; + $tests[0]['source_data']['field_data_field_file'] = [ + [ + 'entity_type' => 'user', + 'bundle' => 'user', + 'deleted' => 0, + 'entity_id' => 2, + 'revision_id' => NULL, + 'language' => 'und', + 'delta' => 0, + 'field_file_fid' => 99, + 'field_file_display' => 1, + 'field_file_description' => 'None', + ], + ]; + $tests[0]['source_data']['role'] = [ + [ + 'rid' => 2, + 'name' => 'authenticated user', + 'weight' => 0, + ], + ]; + $tests[0]['source_data']['users'] = [ + [ + 'uid' => '2', + 'name' => 'Odo', + 'pass' => '$S$DVpvPItXvnsmF3giVEe7Jy2lG.SCoEs8uKwpHsyPvdeNAaNZYxZ8', + 'mail' => 'odo@local.host', + 'theme' => '', + 'signature' => '', + 'signature_format' => 'filtered_html', + 'created' => '1432750741', + 'access' => '0', + 'login' => '0', + 'status' => '1', + 'timezone' => 'America/Chicago', + 'language' => '', + 'picture' => '0', + 'init' => 'odo@local.host', + 'data' => 'a:1:{s:7:"contact";i:1;}', + ], + ]; + $tests[0]['source_data']['users_roles'] = [ + [ + 'uid' => 2, + 'rid' => 2, + ], + ]; + + // The expected results. + $tests[0]['expected_data'] = [ + [ + 'uid' => '2', + 'name' => 'Odo', + 'pass' => '$S$DVpvPItXvnsmF3giVEe7Jy2lG.SCoEs8uKwpHsyPvdeNAaNZYxZ8', + 'mail' => 'odo@local.host', + 'signature' => '', + 'signature_format' => 'filtered_html', + 'created' => '1432750741', + 'access' => '0', + 'login' => '0', + 'status' => '1', + 'timezone' => 'America/Chicago', + 'language' => '', + 'picture' => '0', + 'init' => 'odo@local.host', + 'roles' => [2], + 'data' => [ + 'contact' => 1, + ], + 'field_file' => [ + [ + 'fid' => 99, + 'display' => 1, + 'description' => 'None', + ], + ], + ], + ]; + + return $tests; + } + +} diff --git a/core/modules/user/tests/src/Unit/Migrate/ProfileFieldTest.php b/core/modules/user/tests/src/Unit/Migrate/ProfileFieldTest.php deleted file mode 100644 index a639bf418..000000000 --- a/core/modules/user/tests/src/Unit/Migrate/ProfileFieldTest.php +++ /dev/null @@ -1,82 +0,0 @@ - 'test_profile_fields', - 'source' => [ - 'plugin' => 'd6_profile_field', - ], - ]; - - protected $expectedResults = [ - [ - 'fid' => 1, - 'title' => 'First name', - 'name' => 'profile_first_name', - 'explanation' => 'First name user', - 'category' => 'profile', - 'page' => '', - 'type' => 'textfield', - 'weight' => 0, - 'required' => 1, - 'register' => 0, - 'visibility' => 2, - 'autocomplete' => 0, - 'options' => [], - ], - [ - 'fid' => 2, - 'title' => 'Last name', - 'name' => 'profile_last_name', - 'explanation' => 'Last name user', - 'category' => 'profile', - 'page' => '', - 'type' => 'textfield', - 'weight' => 0, - 'required' => 0, - 'register' => 0, - 'visibility' => 2, - 'autocomplete' => 0, - 'options' => [], - ], - [ - 'fid' => 3, - 'title' => 'Policy', - 'name' => 'profile_policy', - 'explanation' => 'A checkbox that say if you accept policy of website', - 'category' => 'profile', - 'page' => '', - 'type' => 'checkbox', - 'weight' => 0, - 'required' => 1, - 'register' => 1, - 'visibility' => 2, - 'autocomplete' => 0, - 'options' => [], - ], - ]; - - /** - * Prepopulate contents with results. - */ - protected function setUp() { - $this->databaseContents['profile_fields'] = $this->expectedResults; - foreach ($this->databaseContents['profile_fields'] as &$row) { - $row['options'] = serialize([]); - } - parent::setUp(); - } - -} diff --git a/core/modules/user/tests/src/Unit/Migrate/UserPictureInstanceTest.php b/core/modules/user/tests/src/Unit/Migrate/UserPictureInstanceTest.php deleted file mode 100644 index a650704d4..000000000 --- a/core/modules/user/tests/src/Unit/Migrate/UserPictureInstanceTest.php +++ /dev/null @@ -1,54 +0,0 @@ - 'test', - 'source' => [ - 'plugin' => 'user_picture_instance', - ], - ]; - - protected $expectedResults = array( - array( - 'id' => '', - 'file_directory' => 'pictures', - 'max_filesize' => '128KB', - 'max_resolution' => '128x128', - ), - ); - - /** - * {@inheritdoc} - */ - protected function setUp() { - $this->databaseContents['variable'] = array( - array( - 'name' => 'file_directory', - 'value' => serialize(NULL), - ), - array( - 'name' => 'user_picture_file_size', - 'value' => serialize(128), - ), - array( - 'name' => 'user_picture_dimensions', - 'value' => serialize('128x128'), - ), - ); - parent::setUp(); - } - -} diff --git a/core/modules/user/tests/src/Unit/Migrate/d6/RoleTest.php b/core/modules/user/tests/src/Unit/Migrate/d6/RoleTest.php deleted file mode 100644 index 9d21b56c1..000000000 --- a/core/modules/user/tests/src/Unit/Migrate/d6/RoleTest.php +++ /dev/null @@ -1,75 +0,0 @@ - 'test', - 'source' => array( - 'plugin' => 'd6_user_role', - ), - ); - - protected $expectedResults = array( - array( - 'rid' => 1, - 'name' => 'anonymous user', - 'permissions' => array( - 'access content', - ), - ), - array( - 'rid' => 2, - 'name' => 'authenticated user', - 'permissions' => array( - 'access comments', - 'access content', - 'post comments', - 'post comments without approval', - ), - ), - array( - 'rid' => 3, - 'name' => 'administrator', - 'permissions' => array( - 'access comments', - 'administer comments', - 'post comments', - 'post comments without approval', - 'access content', - 'administer content types', - 'administer nodes', - ), - ), - ); - - /** - * {@inheritdoc} - */ - protected function setUp() { - foreach ($this->expectedResults as $row) { - $this->databaseContents['permission'][] = array( - 'perm' => implode(', ', $row['permissions']), - 'rid' => $row['rid'], - ); - unset($row['permissions']); - $this->databaseContents['role'][] = $row; - } - $this->databaseContents['filter_formats'][] = array( - 'format' => 1, - 'roles' => '', - ); - parent::setUp(); - } - -} diff --git a/core/modules/user/tests/src/Unit/Migrate/d6/UserPictureFileTest.php b/core/modules/user/tests/src/Unit/Migrate/d6/UserPictureFileTest.php deleted file mode 100644 index 6c045eaf3..000000000 --- a/core/modules/user/tests/src/Unit/Migrate/d6/UserPictureFileTest.php +++ /dev/null @@ -1,48 +0,0 @@ - 'test', - 'source' => array( - 'plugin' => 'd6_user_picture_file', - ), - ); - - protected $expectedResults = array( - array( - 'uid' => '2', - 'picture' => 'core/modules/simpletest/files/image-test.jpg', - ), - ); - - /** - * {@inheritdoc} - */ - protected function setUp() { - $this->databaseContents['users'] = array( - array( - 'uid' => '2', - 'picture' => 'core/modules/simpletest/files/image-test.jpg', - ), - array( - 'uid' => '15', - 'picture' => '', - ), - ); - parent::setUp(); - } - -} diff --git a/core/modules/user/tests/src/Unit/Migrate/d6/UserPictureTest.php b/core/modules/user/tests/src/Unit/Migrate/d6/UserPictureTest.php deleted file mode 100644 index 1962b1732..000000000 --- a/core/modules/user/tests/src/Unit/Migrate/d6/UserPictureTest.php +++ /dev/null @@ -1,44 +0,0 @@ - 'test_user_picture', - 'source' => array( - 'plugin' => 'd6_user_picture', - ), - ); - - protected $expectedResults = array( - array( - 'uid' => 1, - 'access' => 1382835435, - 'picture' => 'sites/default/files/pictures/picture-1.jpg', - ), - array( - 'uid' => 2, - 'access' => 1382835436, - 'picture' => 'sites/default/files/pictures/picture-2.jpg', - ), - ); - - /** - * {@inheritdoc} - */ - protected function setUp() { - $this->databaseContents['users'] = $this->expectedResults; - parent::setUp(); - } - -} diff --git a/core/modules/user/tests/src/Unit/Migrate/d6/UserTest.php b/core/modules/user/tests/src/Unit/Migrate/d6/UserTest.php deleted file mode 100644 index d087451d0..000000000 --- a/core/modules/user/tests/src/Unit/Migrate/d6/UserTest.php +++ /dev/null @@ -1,83 +0,0 @@ - 'test', - 'source' => array( - 'plugin' => 'd6_user', - ), - ); - - protected $expectedResults = array( - array( - 'uid' => 2, - 'name' => 'admin', - // @todo d6 hash? - 'pass' => '1234', - 'mail' => 'admin@example.com', - 'theme' => '', - 'signature' => '', - 'signature_format' => 0, - 'created' => 1279402616, - 'access' => 1322981278, - 'login' => 1322699994, - 'status' => 0, - 'timezone' => 'America/Lima', - 'language' => 'en', - // @todo Add the file when needed. - 'picture' => 'sites/default/files/pictures/picture-1.jpg', - 'init' => 'admin@example.com', - 'data' => NULL, - ), - array( - 'uid' => 4, - 'name' => 'alice', - // @todo d6 hash? - 'pass' => '1234', - 'mail' => 'alice@example.com', - 'theme' => '', - 'signature' => '', - 'signature_format' => 0, - 'created' => 1322981368, - 'access' => 1322982419, - 'login' => 132298140, - 'status' => 0, - 'timezone' => 'America/Lima', - 'language' => 'en', - 'picture' => '', - 'init' => 'alice@example.com', - 'data' => NULL, - ), - ); - - /** - * {@inheritdoc} - */ - protected function setUp() { - foreach ($this->expectedResults as $k => $row) { - $this->databaseContents['users'][$k] = $row; - } - // getDatabase() will not create empty tables, so we need to insert data - // even if it's irrelevant to the test. - $this->databaseContents['users_roles'] = array( - array( - 'uid' => 99, - 'rid' => 99, - ), - ); - parent::setUp(); - } - -} diff --git a/core/modules/user/tests/src/Unit/Plugin/migrate/source/d7/UserTest.php b/core/modules/user/tests/src/Unit/Plugin/migrate/source/d7/UserTest.php deleted file mode 100644 index f2c862bc6..000000000 --- a/core/modules/user/tests/src/Unit/Plugin/migrate/source/d7/UserTest.php +++ /dev/null @@ -1,112 +0,0 @@ - 'test', - 'source' => [ - 'plugin' => 'd7_user', - ], - ]; - - protected $expectedResults = [ - [ - 'uid' => '2', - 'name' => 'Odo', - 'pass' => '$S$DVpvPItXvnsmF3giVEe7Jy2lG.SCoEs8uKwpHsyPvdeNAaNZYxZ8', - 'mail' => 'odo@local.host', - 'signature' => '', - 'signature_format' => 'filtered_html', - 'created' => '1432750741', - 'access' => '0', - 'login' => '0', - 'status' => '1', - 'timezone' => 'America/Chicago', - 'language' => '', - 'picture' => '0', - 'init' => 'odo@local.host', - 'roles' => [2], - 'data' => [ - 'contact' => 1, - ], - 'field_file' => [ - [ - 'fid' => 99, - 'display' => 1, - 'description' => 'None', - ], - ], - ], - ]; - - /** - * {@inheritdoc} - */ - protected function setUp() { - $this->databaseContents['users'][] = [ - 'uid' => '2', - 'name' => 'Odo', - 'pass' => '$S$DVpvPItXvnsmF3giVEe7Jy2lG.SCoEs8uKwpHsyPvdeNAaNZYxZ8', - 'mail' => 'odo@local.host', - 'theme' => '', - 'signature' => '', - 'signature_format' => 'filtered_html', - 'created' => '1432750741', - 'access' => '0', - 'login' => '0', - 'status' => '1', - 'timezone' => 'America/Chicago', - 'language' => '', - 'picture' => '0', - 'init' => 'odo@local.host', - 'data' => 'a:1:{s:7:"contact";i:1;}', - ]; - $this->databaseContents['users_roles'][] = [ - 'uid' => 2, - 'rid' => 2, - ]; - $this->databaseContents['role'][] = [ - 'rid' => 2, - 'name' => 'authenticated user', - 'weight' => 0, - ]; - $this->databaseContents['field_config_instance'] = [ - [ - 'id' => '33', - 'field_id' => '11', - 'field_name' => 'field_file', - 'entity_type' => 'user', - 'bundle' => 'user', - 'data' => 'a:0:{}', - 'deleted' => '0', - ], - ]; - $this->databaseContents['field_data_field_file'] = [ - [ - 'entity_type' => 'user', - 'bundle' => 'user', - 'deleted' => 0, - 'entity_id' => 2, - 'revision_id' => NULL, - 'language' => 'und', - 'delta' => 0, - 'field_file_fid' => 99, - 'field_file_display' => 1, - 'field_file_description' => 'None', - ], - ]; - parent::setUp(); - } - -} diff --git a/core/modules/views/js/base.js b/core/modules/views/js/base.js index 7c7622db4..cca3be4dd 100644 --- a/core/modules/views/js/base.js +++ b/core/modules/views/js/base.js @@ -53,9 +53,11 @@ Drupal.Views.parseViewArgs = function (href, viewPath) { var returnObj = {}; var path = Drupal.Views.getPath(href); + // Get viewPath url without baseUrl portion. + var viewHref = Drupal.url(viewPath).substring(drupalSettings.path.baseUrl.length); // Ensure we have a correct path. - if (viewPath && path.substring(0, viewPath.length + 1) === viewPath + '/') { - returnObj.view_args = decodeURIComponent(path.substring(viewPath.length + 1, path.length)); + if (viewHref && path.substring(0, viewHref.length + 1) === viewHref + '/') { + returnObj.view_args = decodeURIComponent(path.substring(viewHref.length + 1, path.length)); returnObj.view_path = path; } return returnObj; diff --git a/core/modules/views/src/ManyToOneHelper.php b/core/modules/views/src/ManyToOneHelper.php index 09accee5a..a3c4834ac 100644 --- a/core/modules/views/src/ManyToOneHelper.php +++ b/core/modules/views/src/ManyToOneHelper.php @@ -298,7 +298,7 @@ class ManyToOneHelper { } $placeholders = array( $placeholder => $value, - ) + $this->placeholders; + ); $this->handler->query->addWhereExpression($options['group'], "$field $operator", $placeholders); } else { diff --git a/core/modules/views/src/Plugin/views/field/FieldPluginBase.php b/core/modules/views/src/Plugin/views/field/FieldPluginBase.php index cde5ecb35..103dd9dee 100644 --- a/core/modules/views/src/Plugin/views/field/FieldPluginBase.php +++ b/core/modules/views/src/Plugin/views/field/FieldPluginBase.php @@ -1271,7 +1271,20 @@ abstract class FieldPluginBase extends HandlerBase implements FieldHandlerInterf // @todo Views should expect and store a leading /. See // https://www.drupal.org/node/2423913. - $more_link = ' ' . $this->linkGenerator()->generate($more_link_text, CoreUrl::fromUserInput('/' . $more_link_path, array('attributes' => array('class' => array('views-more-link'))))); + $options = array( + 'attributes' => array( + 'class' => array( + 'views-more-link', + ), + ), + ); + if (UrlHelper::isExternal($more_link_path)) { + $more_link_url = CoreUrl::fromUri($more_link_path, $options); + } + else { + $more_link_url = CoreUrl::fromUserInput('/' . $more_link_path, $options); + } + $more_link = ' ' . $this->linkGenerator()->generate($more_link_text, $more_link_url); } } diff --git a/core/modules/views/src/Plugin/views/filter/Combine.php b/core/modules/views/src/Plugin/views/filter/Combine.php index e13df51ec..1b4ab9c1c 100644 --- a/core/modules/views/src/Plugin/views/filter/Combine.php +++ b/core/modules/views/src/Plugin/views/filter/Combine.php @@ -3,6 +3,7 @@ namespace Drupal\views\Plugin\views\filter; use Drupal\Core\Form\FormStateInterface; +use Drupal\Core\Database\Database; /** * Filter handler which allows to search on multiple fields. @@ -138,6 +139,40 @@ class Combine extends StringFilter { $this->query->addWhereExpression($this->options['group'], "$expression LIKE $placeholder", array($placeholder => '%' . db_like($this->value) . '%')); } + /** + * Filters by one or more words. + * + * By default opContainsWord uses add_where, that doesn't support complex + * expressions. + * + * @param string $expression + */ + protected function opContainsWord($expression) { + $placeholder = $this->placeholder(); + + // Don't filter on empty strings. + if (empty($this->value)) { + return; + } + + // Match all words separated by spaces or sentences encapsulated by double + // quotes. + preg_match_all(static::WORDS_PATTERN, ' ' . $this->value, $matches, PREG_SET_ORDER); + + // Switch between the 'word' and 'allwords' operator. + $type = $this->operator == 'word' ? 'OR' : 'AND'; + $group = $this->query->setWhereGroup($type); + $operator = Database::getConnection()->mapConditionOperator('LIKE'); + $operator = isset($operator['operator']) ? $operator['operator'] : 'LIKE'; + + foreach ($matches as $match_key => $match) { + $temp_placeholder = $placeholder . '_' . $match_key; + // Clean up the user input and remove the sentence delimiters. + $word = trim($match[2], ',?!();:-"'); + $this->query->addWhereExpression($group, "$expression $operator $temp_placeholder", array($temp_placeholder => '%' . Database::getConnection()->escapeLike($word) . '%')); + } + } + protected function opStartsWith($expression) { $placeholder = $this->placeholder(); $this->query->addWhereExpression($this->options['group'], "$expression LIKE $placeholder", array($placeholder => db_like($this->value) . '%')); diff --git a/core/modules/views/src/Plugin/views/filter/StringFilter.php b/core/modules/views/src/Plugin/views/filter/StringFilter.php index 10c407017..618b58458 100644 --- a/core/modules/views/src/Plugin/views/filter/StringFilter.php +++ b/core/modules/views/src/Plugin/views/filter/StringFilter.php @@ -14,6 +14,11 @@ use Drupal\Core\Form\FormStateInterface; */ class StringFilter extends FilterPluginBase { + /** + * All words separated by spaces or sentences encapsulated by double quotes. + */ + const WORDS_PATTERN = '/ (-?)("[^"]+"|[^" ]+)/i'; + // exposed filter options protected $alwaysMultiple = TRUE; @@ -267,7 +272,7 @@ class StringFilter extends FilterPluginBase { return; } - preg_match_all('/ (-?)("[^"]+"|[^" ]+)/i', ' ' . $this->value, $matches, PREG_SET_ORDER); + preg_match_all(static::WORDS_PATTERN, ' ' . $this->value, $matches, PREG_SET_ORDER); foreach ($matches as $match) { $phrase = FALSE; // Strip off phrase quotes diff --git a/core/modules/views/src/Plugin/views/pager/Mini.php b/core/modules/views/src/Plugin/views/pager/Mini.php index 5f1750ea3..c6520f4fb 100644 --- a/core/modules/views/src/Plugin/views/pager/Mini.php +++ b/core/modules/views/src/Plugin/views/pager/Mini.php @@ -47,14 +47,17 @@ class Mini extends SqlBase { public function query() { parent::query(); - // Don't query for the next page if we have a pager that has a limited - // amount of pages. - if ($this->getItemsPerPage() > 0 && (empty($this->options['total_pages']) || ($this->getCurrentPage() < $this->options['total_pages']))) { - // Increase the items in the query in order to be able to find out whether - // there is another page. - $limit = $this->view->query->getLimit(); - $limit += 1; - $this->view->query->setLimit($limit); + // Only modify the query if we don't want to do a total row count + if (!$this->view->get_total_rows) { + // Don't query for the next page if we have a pager that has a limited + // amount of pages. + if ($this->getItemsPerPage() > 0 && (empty($this->options['total_pages']) || ($this->getCurrentPage() < $this->options['total_pages']))) { + // Increase the items in the query in order to be able to find out + // whether there is another page. + $limit = $this->view->query->getLimit(); + $limit += 1; + $this->view->query->setLimit($limit); + } } } @@ -69,19 +72,16 @@ class Mini extends SqlBase { * {@inheritdoc} */ public function postExecute(&$result) { - // In query() one more item might have been retrieved than necessary. If so, - // the next link needs to be displayed and the item removed. - if ($this->getItemsPerPage() > 0 && count($result) > $this->getItemsPerPage()) { - array_pop($result); - // Make sure the pager shows the next link by setting the total items to - // the biggest possible number but prevent failing calculations like - // ceil(PHP_INT_MAX) we take PHP_INT_MAX / 2. - $total = PHP_INT_MAX / 2; + // Only modify the result if we didn't do a total row count + if (!$this->view->get_total_rows) { + $this->total_items = $this->getCurrentPage() * $this->getItemsPerPage() + count($result); + // query() checks if we need a next link by setting limit 1 record past + // this page If we got the extra record we need to remove it before we + // render the result. + if ($this->getItemsPerPage() > 0 && count($result) > $this->getItemsPerPage()) { + array_pop($result); + } } - else { - $total = $this->getCurrentPage() * $this->getItemsPerPage() + count($result); - } - $this->total_items = $total; } /** diff --git a/core/modules/views/src/Tests/Plugin/MiniPagerTest.php b/core/modules/views/src/Tests/Plugin/MiniPagerTest.php index f39a9d65f..1a73b6980 100644 --- a/core/modules/views/src/Tests/Plugin/MiniPagerTest.php +++ b/core/modules/views/src/Tests/Plugin/MiniPagerTest.php @@ -70,6 +70,18 @@ class MiniPagerTest extends PluginTestBase { $this->assertText($this->nodes[18]->label()); $this->assertText($this->nodes[19]->label()); + // Test @total value in result summary + $view = Views::getView('test_mini_pager'); + $view->setDisplay('page_4'); + $this->executeView($view); + $this->assertIdentical($view->get_total_rows, TRUE, 'The query was set to calculate the total number of rows.'); + $this->assertEqual(count($this->nodes), $view->total_rows, 'The total row count is equal to the number of nodes.'); + + $this->drupalGet('test_mini_pager_total', array('query' => array('page' => 1))); + $this->assertText('of ' . count($this->nodes), 'The first page shows the total row count.'); + $this->drupalGet('test_mini_pager_total', array('query' => array('page' => 6))); + $this->assertText('of ' . count($this->nodes), 'The last page shows the total row count.'); + // Test a mini pager with just one item per page. $this->drupalGet('test_mini_pager_one'); $this->assertText('››'); diff --git a/core/modules/views/src/Tests/TaxonomyGlossaryTest.php b/core/modules/views/src/Tests/TaxonomyGlossaryTest.php new file mode 100644 index 000000000..ccda8877d --- /dev/null +++ b/core/modules/views/src/Tests/TaxonomyGlossaryTest.php @@ -0,0 +1,58 @@ +enableViewsTestModule(); + + /** @var \Drupal\taxonomy\Entity\Vocabulary $vocabulary */ + $vocabulary = $this->createVocabulary(); + for ($i = 0; $i < 10; $i++) { + $this->taxonomyTerms[] = $this->createTerm($vocabulary); + } + } + + /** + * Tests a taxonomy glossary view. + */ + public function testTaxonomyGlossaryView() { + // Go the taxonomy glossary page for the first term. + $this->drupalGet('test_taxonomy_glossary/' . substr($this->taxonomyTerms[0]->getName(), 0, 1)); + $this->assertText($this->taxonomyTerms[0]->getName()); + } + +} diff --git a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_glossary.yml b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_glossary.yml index bf44f36e6..e2e143eaf 100644 --- a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_glossary.yml +++ b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_glossary.yml @@ -5,56 +5,450 @@ dependencies: - node - user id: test_glossary -label: test_glossary -module: views -description: '' +label: Test glossary +module: node +description: 'All content, by letter.' tag: default base_table: node_field_data base_field: nid core: '8' display: default: + id: default + display_title: Master + display_plugin: default + position: 0 display_options: - access: - type: perm - arguments: - title: - default_argument_type: fixed - field: title - glossary: true - id: title - limit: 1 - summary: - format: default_summary - number_of_records: 0 - summary_options: - items_per_page: 25 - table: node_field_data - plugin_id: string - entity_type: node - entity_field: title - cache: - type: tag - exposed_form: - type: basic - fields: - title: - field: title - id: title - label: '' - table: node_field_data - plugin_id: field - entity_type: node - entity_field: title - pager: - type: full query: type: views_query + options: + query_comment: '' + disable_sql_rewrite: false + distinct: false + replica: false + query_tags: { } + use_ajax: true + access: + type: perm + options: + perm: 'access content' + cache: + type: tag + options: { } + exposed_form: + type: basic + options: + submit_button: Apply + reset_button: false + reset_button_label: Reset + exposed_sorts_label: 'Sort by' + expose_sort_order: true + sort_asc_label: Asc + sort_desc_label: Desc + pager: + type: mini + options: + items_per_page: 36 + offset: 0 + id: 0 + total_pages: 0 + expose: + items_per_page: false + items_per_page_label: 'Items per page' + items_per_page_options: '5, 10, 25, 50' + items_per_page_options_all: false + items_per_page_options_all_label: '- All -' + offset: false + offset_label: Offset + tags: + previous: ‹‹ + next: ›› + fields: + title: + id: title + table: node_field_data + field: title + plugin_id: field + relationship: none + group_type: group + admin_label: '' + label: Title + exclude: false + alter: + alter_text: false + text: '' + make_link: false + path: '' + absolute: false + external: false + replace_spaces: false + path_case: none + trim_whitespace: false + alt: '' + rel: '' + link_class: '' + prefix: '' + suffix: '' + target: '' + nl2br: false + max_length: 0 + word_boundary: true + ellipsis: true + more_link: false + more_link_text: '' + more_link_path: '' + strip_tags: false + trim: false + preserve_tags: '' + html: false + element_type: '' + element_class: '' + element_label_type: '' + element_label_class: '' + element_label_colon: true + element_wrapper_type: '' + element_wrapper_class: '' + element_default_classes: true + empty: '' + hide_empty: false + empty_zero: false + hide_alter_empty: true + entity_type: node + entity_field: title + name: + id: name + table: users_field_data + field: name + label: Author + relationship: uid + plugin_id: field + type: user_name + group_type: group + admin_label: '' + exclude: false + alter: + alter_text: false + text: '' + make_link: false + path: '' + absolute: false + external: false + replace_spaces: false + path_case: none + trim_whitespace: false + alt: '' + rel: '' + link_class: '' + prefix: '' + suffix: '' + target: '' + nl2br: false + max_length: 0 + word_boundary: true + ellipsis: true + more_link: false + more_link_text: '' + more_link_path: '' + strip_tags: false + trim: false + preserve_tags: '' + html: false + element_type: '' + element_class: '' + element_label_type: '' + element_label_class: '' + element_label_colon: true + element_wrapper_type: '' + element_wrapper_class: '' + element_default_classes: true + empty: '' + hide_empty: false + empty_zero: false + hide_alter_empty: true + entity_type: user + entity_field: name + changed: + id: changed + table: node_field_data + field: changed + label: 'Last update' + type: timestamp + settings: + date_format: long + custom_date_format: '' + timezone: '' + plugin_id: field + relationship: none + group_type: group + admin_label: '' + exclude: false + alter: + alter_text: false + text: '' + make_link: false + path: '' + absolute: false + external: false + replace_spaces: false + path_case: none + trim_whitespace: false + alt: '' + rel: '' + link_class: '' + prefix: '' + suffix: '' + target: '' + nl2br: false + max_length: 0 + word_boundary: true + ellipsis: true + more_link: false + more_link_text: '' + more_link_path: '' + strip_tags: false + trim: false + preserve_tags: '' + html: false + element_type: '' + element_class: '' + element_label_type: '' + element_label_class: '' + element_label_colon: true + element_wrapper_type: '' + element_wrapper_class: '' + element_default_classes: true + empty: '' + hide_empty: false + empty_zero: false + hide_alter_empty: true + entity_type: node + entity_field: changed + arguments: + title: + id: title + table: node_field_data + field: title + default_action: default + exception: + title_enable: true + default_argument_type: fixed + default_argument_options: + argument: a + summary: + format: default_summary + specify_validation: true + glossary: true + limit: 1 + case: upper + path_case: lower + transform_dash: false + plugin_id: string + relationship: none + group_type: group + admin_label: '' + title_enable: false + title: '' + default_argument_skip_url: false + summary_options: { } + validate: + type: none + fail: 'not found' + validate_options: { } + break_phrase: false + entity_type: node + entity_field: title + relationships: + uid: + id: uid + table: node_field_data + field: uid + plugin_id: standard + relationship: none + group_type: group + admin_label: author + required: false style: - type: default + type: table + options: + columns: + title: title + name: name + changed: changed + default: title + info: + title: + sortable: true + separator: '' + name: + sortable: true + separator: '' + changed: + sortable: true + separator: '' + override: true + sticky: false + grouping: { } + row_class: '' + default_row_class: true + uses_fields: false + order: asc + summary: '' + empty_table: false row: type: fields - display_plugin: default - display_title: Master - id: default - position: 0 + options: + inline: { } + separator: '' + hide_empty: false + default_field_elements: true + header: { } + footer: { } + empty: { } + sorts: { } + filters: + langcode: + id: langcode + table: node_field_data + field: langcode + relationship: none + group_type: group + admin_label: '' + operator: in + value: + '***LANGUAGE_language_content***': '***LANGUAGE_language_content***' + group: 1 + exposed: false + expose: + operator_id: '' + label: '' + description: '' + use_operator: false + operator: '' + identifier: '' + required: false + remember: false + multiple: false + remember_roles: + authenticated: authenticated + reduce: false + is_grouped: false + group_info: + label: '' + description: '' + identifier: '' + optional: true + widget: select + multiple: false + remember: false + default_group: All + default_group_multiple: { } + group_items: { } + plugin_id: language + entity_type: node + entity_field: langcode + display_extenders: { } + cache_metadata: + contexts: + - 'languages:language_content' + - 'languages:language_interface' + - url + - url.query_args + - 'user.node_grants:view' + - user.permissions + max-age: 0 + tags: { } + attachment_1: + id: attachment_1 + display_title: Attachment + display_plugin: attachment + position: 2 + display_options: + query: + type: views_query + options: { } + pager: + type: none + options: + offset: 0 + items_per_page: 0 + defaults: + arguments: false + arguments: + title: + id: title + table: node_field_data + field: title + default_action: summary + exception: + title_enable: true + default_argument_type: fixed + default_argument_options: + argument: a + summary: + format: unformatted_summary + summary_options: + items_per_page: 25 + inline: true + separator: ' | ' + specify_validation: true + glossary: true + limit: 1 + case: upper + path_case: lower + transform_dash: false + plugin_id: string + relationship: none + group_type: group + admin_label: '' + title_enable: false + title: '' + default_argument_skip_url: false + validate: + type: none + fail: 'not found' + validate_options: { } + break_phrase: false + entity_type: node + entity_field: title + displays: + default: default + page_1: page_1 + inherit_arguments: false + display_extenders: { } + cache_metadata: + contexts: + - 'languages:language_content' + - 'languages:language_interface' + - url + - url.query_args + - 'user.node_grants:view' + - user.permissions + max-age: 0 + tags: { } + page_1: + id: page_1 + display_title: Page + display_plugin: page + position: 1 + display_options: + query: + type: views_query + options: { } + path: test-glossary + menu: + type: normal + title: Glossary + weight: 0 + menu_name: main + parent: '' + display_extenders: { } + cache_metadata: + contexts: + - 'languages:language_content' + - 'languages:language_interface' + - url + - url.query_args + - 'user.node_grants:view' + - user.permissions + max-age: 0 + tags: { } diff --git a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_invalid_tokens.yml b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_invalid_tokens.yml new file mode 100644 index 000000000..b57593a80 --- /dev/null +++ b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_invalid_tokens.yml @@ -0,0 +1,53 @@ +langcode: en +status: true +dependencies: { } +id: test_invalid_tokens +label: 'Test invalid tokens' +module: views +description: 'Test view to invalid token replacement tests.' +tag: '' +base_table: views_test_data +base_field: id +core: 8.x +display: + default: + display_options: + title: 'Test token default' + defaults: + fields: false + pager: false + sorts: false + fields: + age: + field: age + id: age + relationship: none + table: views_test_data + plugin_id: numeric + id: + field: id + id: id + relationship: none + table: views_test_data + plugin_id: numeric + name: + field: name + id: name + relationship: none + table: views_test_data + plugin_id: string + pager: + type: full + options: + items_per_page: 10 + display_plugin: default + display_title: Master + id: default + position: 0 + block_1: + display_plugin: block + id: block_1 + display_title: Block + position: 1 + display_options: + display_extenders: { } diff --git a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_mini_pager.yml b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_mini_pager.yml index e65ea4a4e..289f83db9 100644 --- a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_mini_pager.yml +++ b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_mini_pager.yml @@ -118,3 +118,26 @@ display: type: mini options: items_per_page: 0 + page_4: + display_plugin: page + id: page_4 + display_title: 'Page 4' + position: 4 + display_options: + display_extenders: { } + path: test_mini_pager_total + empty: { } + defaults: + empty: false + header: false + header: + result: + id: result + table: views + field: result + relationship: none + group_type: group + admin_label: '' + empty: false + content: 'Displaying @start - @end of @total' + plugin_id: result diff --git a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_taxonomy_glossary.yml b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_taxonomy_glossary.yml new file mode 100644 index 000000000..8e4182dc1 --- /dev/null +++ b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_taxonomy_glossary.yml @@ -0,0 +1,191 @@ +langcode: en +status: true +dependencies: + module: + - taxonomy +id: test_taxonomy_glossary +label: test_taxonomy_glossary +module: views +description: '' +tag: '' +base_table: taxonomy_term_field_data +base_field: tid +core: 8.x +display: + default: + display_plugin: default + id: default + display_title: Master + position: 0 + display_options: + access: + type: none + options: { } + cache: + type: tag + options: { } + query: + type: views_query + options: + disable_sql_rewrite: false + distinct: false + replica: false + query_comment: '' + query_tags: { } + exposed_form: + type: basic + options: + submit_button: Apply + reset_button: false + reset_button_label: Reset + exposed_sorts_label: 'Sort by' + expose_sort_order: true + sort_asc_label: Asc + sort_desc_label: Desc + pager: + type: mini + options: + items_per_page: 10 + offset: 0 + id: 0 + total_pages: null + expose: + items_per_page: false + items_per_page_label: 'Items per page' + items_per_page_options: '5, 10, 25, 50' + items_per_page_options_all: false + items_per_page_options_all_label: '- All -' + offset: false + offset_label: Offset + tags: + previous: ‹‹ + next: ›› + style: + type: default + row: + type: fields + fields: + name: + id: name + table: taxonomy_term_field_data + field: name + entity_type: taxonomy_term + entity_field: name + label: '' + alter: + alter_text: false + make_link: false + absolute: false + trim: false + word_boundary: false + ellipsis: false + strip_tags: false + html: false + hide_empty: false + empty_zero: false + type: string + settings: + link_to_entity: true + plugin_id: term_name + relationship: none + group_type: group + admin_label: '' + exclude: false + element_type: '' + element_class: '' + element_label_type: '' + element_label_class: '' + element_label_colon: true + element_wrapper_type: '' + element_wrapper_class: '' + element_default_classes: true + empty: '' + hide_alter_empty: true + click_sort_column: value + group_column: value + group_columns: { } + group_rows: true + delta_limit: 0 + delta_offset: 0 + delta_reversed: false + delta_first_last: false + multi_type: separator + separator: ', ' + field_api_classes: false + convert_spaces: false + filters: { } + sorts: { } + title: test_taxonomy_glossary + header: { } + footer: { } + empty: { } + relationships: { } + arguments: + name: + id: name + table: taxonomy_term_field_data + field: name + relationship: none + group_type: group + admin_label: '' + default_action: ignore + exception: + value: all + title_enable: false + title: All + title_enable: false + title: '' + default_argument_type: fixed + default_argument_options: + argument: '' + default_argument_skip_url: false + summary_options: + base_path: '' + count: true + items_per_page: 25 + override: false + summary: + sort_order: asc + number_of_records: 0 + format: default_summary + specify_validation: false + validate: + type: none + fail: 'not found' + validate_options: { } + glossary: true + limit: 1 + case: none + path_case: none + transform_dash: false + break_phrase: false + add_table: false + require_value: false + entity_type: taxonomy_term + entity_field: name + plugin_id: string + display_extenders: { } + cache_metadata: + max-age: -1 + contexts: + - 'languages:language_content' + - 'languages:language_interface' + - url + - url.query_args + tags: { } + page_1: + display_plugin: page + id: page_1 + display_title: Page + position: 1 + display_options: + display_extenders: { } + path: test_taxonomy_glossary + cache_metadata: + max-age: -1 + contexts: + - 'languages:language_content' + - 'languages:language_interface' + - url + - url.query_args + tags: { } diff --git a/core/modules/views/tests/src/FunctionalJavascript/GlossaryViewTest.php b/core/modules/views/tests/src/FunctionalJavascript/GlossaryViewTest.php new file mode 100644 index 000000000..42da7b659 --- /dev/null +++ b/core/modules/views/tests/src/FunctionalJavascript/GlossaryViewTest.php @@ -0,0 +1,129 @@ +createContentType(['type' => 'page']); + + $titles = [ + 'Page One', + 'Page Two', + 'Another page', + ]; + foreach ($titles as $title) { + $this->createNode([ + 'title' => $title, + 'language' => 'en', + ]); + $this->createNode([ + 'title' => $title, + 'language' => 'nl', + ]); + } + + // Create a user privileged enough to use exposed filters and view content. + $user = $this->drupalCreateUser([ + 'administer site configuration', + 'access content', + 'access content overview', + ]); + $this->drupalLogin($user); + } + + /** + * Tests the AJAX callbacks for the glossary view. + */ + public function testGlossaryDefault() { + // Visit the default Glossary page. + $url = Url::fromRoute('view.test_glossary.page_1'); + $this->drupalGet($url); + + $session = $this->getSession(); + $web_assert = $this->assertSession(); + + $page = $session->getPage(); + $rows = $page->findAll('css', '.view-test-glossary tr'); + // We expect 2 rows plus the header row. + $this->assertCount(3, $rows); + // Click on the P link, this should show 4 rows plus the header row. + $page->clickLink('P'); + $web_assert->assertWaitOnAjaxRequest(); + $rows = $page->findAll('css', '.view-test-glossary tr'); + $this->assertCount(5, $rows); + } + + /** + * Test that the glossary also works on a language prefixed URL. + */ + public function testGlossaryLanguagePrefix() { + $this->language = ConfigurableLanguage::createFromLangcode('nl')->save(); + + $config = $this->config('language.negotiation'); + $config->set('url.prefixes', ['en' => 'en', 'nl' => 'nl']) + ->save(); + + \Drupal::service('kernel')->rebuildContainer(); + + $url = Url::fromRoute('view.test_glossary.page_1'); + $this->drupalGet($url); + + $session = $this->getSession(); + $web_assert = $this->assertSession(); + + $page = $session->getPage(); + + $rows = $page->findAll('css', '.view-test-glossary tr'); + // We expect 2 rows plus the header row. + $this->assertCount(3, $rows); + // Click on the P link, this should show 4 rows plus the header row. + $page->clickLink('P'); + $web_assert->assertWaitOnAjaxRequest(); + + $rows = $page->findAll('css', '.view-test-glossary tr'); + $this->assertCount(5, $rows); + } + +} diff --git a/core/modules/views/tests/src/FunctionalJavascript/Plugin/views/Handler/ContextualFilterTest.php b/core/modules/views/tests/src/FunctionalJavascript/Plugin/views/Handler/ContextualFilterTest.php new file mode 100644 index 000000000..8cca9c360 --- /dev/null +++ b/core/modules/views/tests/src/FunctionalJavascript/Plugin/views/Handler/ContextualFilterTest.php @@ -0,0 +1,75 @@ +getEditable('views.settings')->set('ui.show.advanced_column', TRUE)->save(); + + $account = $this->drupalCreateUser(['administer views']); + $this->drupalLogin($account); + } + + /** + * Test adding a contextual filter handler through the UI. + */ + public function testAddContextualFilterUI() { + $web_assert = $this->assertSession(); + + $this->drupalGet('/admin/structure/views/view/test_field_body'); + $web_assert->assertWaitOnAjaxRequest(); + + $page = $this->getSession()->getPage(); + + $page->clickLink('views-add-argument'); + $web_assert->assertWaitOnAjaxRequest(); + + $page->checkField('name[node_field_data.nid]'); + $add_button = $page->find('css', '.ui-dialog-buttonset .button--primary'); + $add_button->click(); + $web_assert->assertWaitOnAjaxRequest(); + + $page->fillField('options[default_action]', 'default'); + $page->selectFieldOption('options[default_argument_type]', 'node'); + $add_button = $page->find('css', '.ui-dialog-buttonset .button--primary'); + $add_button->click(); + $web_assert->assertWaitOnAjaxRequest(); + $page->pressButton('edit-actions-submit'); + $web_assert->assertWaitOnAjaxRequest(); + $page->clickLink('Content: ID'); + // Check that the dialog opens. + $web_assert->assertWaitOnAjaxRequest(); + $page->pressButton('Close'); + } + +} diff --git a/core/modules/views/tests/src/Kernel/Handler/FilterCombineTest.php b/core/modules/views/tests/src/Kernel/Handler/FilterCombineTest.php index 775015bbe..042426383 100644 --- a/core/modules/views/tests/src/Kernel/Handler/FilterCombineTest.php +++ b/core/modules/views/tests/src/Kernel/Handler/FilterCombineTest.php @@ -95,6 +95,101 @@ class FilterCombineTest extends ViewsKernelTestBase { $this->assertIdenticalResultset($view, $resultset, $this->columnMap); } + /** + * Tests the Combine field filter with the 'word' operator. + */ + public function testFilterCombineWord() { + $view = Views::getView('test_view'); + $view->setDisplay(); + + $fields = $view->displayHandlers->get('default')->getOption('fields'); + $view->displayHandlers->get('default')->overrideOption('fields', $fields + array( + 'job' => array( + 'id' => 'job', + 'table' => 'views_test_data', + 'field' => 'job', + 'relationship' => 'none', + ), + )); + + // Change the filtering. + $view->displayHandlers->get('default')->overrideOption('filters', array( + 'age' => array( + 'id' => 'combine', + 'table' => 'views', + 'field' => 'combine', + 'relationship' => 'none', + 'operator' => 'word', + 'fields' => array( + 'name', + 'job', + ), + 'value' => 'singer ringo', + ), + )); + + $this->executeView($view); + $resultset = array( + array( + 'name' => 'John', + 'job' => 'Singer', + ), + array( + 'name' => 'George', + 'job' => 'Singer', + ), + array( + 'name' => 'Ringo', + 'job' => 'Drummer', + ), + ); + $this->assertIdenticalResultset($view, $resultset, $this->columnMap); + } + + /** + * Tests the Combine field filter with the 'allwords' operator. + */ + public function testFilterCombineAllWords() { + $view = Views::getView('test_view'); + $view->setDisplay(); + + $fields = $view->displayHandlers->get('default')->getOption('fields'); + $view->displayHandlers->get('default')->overrideOption('fields', $fields + array( + 'job' => array( + 'id' => 'job', + 'table' => 'views_test_data', + 'field' => 'job', + 'relationship' => 'none', + ), + )); + + // Set the filtering to allwords and simulate searching for a phrase. + $view->displayHandlers->get('default')->overrideOption('filters', array( + 'age' => array( + 'id' => 'combine', + 'table' => 'views', + 'field' => 'combine', + 'relationship' => 'none', + 'operator' => 'allwords', + 'fields' => array( + 'name', + 'job', + 'age', + ), + 'value' => '25 "john singer"', + ), + )); + + $this->executeView($view); + $resultset = array( + array( + 'name' => 'John', + 'job' => 'Singer', + ), + ); + $this->assertIdenticalResultset($view, $resultset, $this->columnMap); + } + /** * Tests if the filter can handle removed fields. * diff --git a/core/modules/views/tests/src/Kernel/TokenReplaceTest.php b/core/modules/views/tests/src/Kernel/TokenReplaceTest.php index 9377ff409..5cf40ad58 100644 --- a/core/modules/views/tests/src/Kernel/TokenReplaceTest.php +++ b/core/modules/views/tests/src/Kernel/TokenReplaceTest.php @@ -19,7 +19,7 @@ class TokenReplaceTest extends ViewsKernelTestBase { * * @var array */ - public static $testViews = array('test_tokens'); + public static $testViews = array('test_tokens', 'test_invalid_tokens'); protected function setUp($import_test_views = TRUE) { parent::setUp(); @@ -90,4 +90,23 @@ class TokenReplaceTest extends ViewsKernelTestBase { } } + /** + * Tests path token replacements generated from a view without a path. + */ + function testTokenReplacementNoPath() { + $token_handler = \Drupal::token(); + $view = Views::getView('test_invalid_tokens'); + $view->setDisplay('block_1'); + $this->executeView($view); + + $expected = array( + '[view:url]' => '', + ); + + foreach ($expected as $token => $expected_output) { + $output = $token_handler->replace($token, array('view' => $view)); + $this->assertIdentical($output, $expected_output, format_string('Token %token replaced correctly.', array('%token' => $token))); + } + } + } diff --git a/core/modules/views/tests/src/Unit/Plugin/field/FieldPluginBaseTest.php b/core/modules/views/tests/src/Unit/Plugin/field/FieldPluginBaseTest.php index 5135c4f00..0b33a5891 100644 --- a/core/modules/views/tests/src/Unit/Plugin/field/FieldPluginBaseTest.php +++ b/core/modules/views/tests/src/Unit/Plugin/field/FieldPluginBaseTest.php @@ -227,11 +227,17 @@ class FieldPluginBaseTest extends UnitTestCase { } /** - * Test rendering as a link without a path. + * Test rendering with a more link. * + * @param string $path + * An internal or external path. + * @param string $url + * The final url used by the more link. + * + * @dataProvider providerTestRenderTrimmedWithMoreLinkAndPath * @covers ::renderText */ - public function testRenderTrimmedWithMoreLink() { + public function testRenderTrimmedWithMoreLinkAndPath($path, $url) { $alter = [ 'trim' => TRUE, 'max_length' => 7, @@ -239,6 +245,7 @@ class FieldPluginBaseTest extends UnitTestCase { // Don't invoke translation. 'ellipsis' => FALSE, 'more_link_text' => 'more link', + 'more_link_path' => $path, ]; $this->display->expects($this->any()) @@ -253,11 +260,38 @@ class FieldPluginBaseTest extends UnitTestCase { $field->field_alias = 'key'; $row = new ResultRow(['key' => 'a long value']); - $expected_result = 'a long more link'; + $expected_result = 'a long more link'; $result = $field->advancedRender($row); $this->assertEquals($expected_result, $result); } + /** + * Data provider for ::testRenderTrimmedWithMoreLinkAndPath(). + * + * @return array + * Test data. + */ + public function providerTestRenderTrimmedWithMoreLinkAndPath() { + $data = []; + // Simple path with default options. + $data[] = ['test-path', '/test-path']; + // Add a fragment. + $data[] = ['test-path#test', '/test-path#test']; + // Query specified as part of the path. + $data[] = ['test-path?foo=bar', '/test-path?foo=bar']; + // Empty path. + $data[] = ['', '/%3Cfront%3E']; + // Front page path. + $data[] = ['