getEntity(Entity::class, []); $this->setExpectedException(EntityMalformedException::class, 'The "' . $this->entityTypeId . '" entity cannot have a URI as it does not have an ID'); $entity->toUrl(); } /** * Tests the toUrl() method with simple link templates. * * @param string $link_template * The link template to test. * @param string $expected_route_name * The expected route name of the generated URL. * * @dataProvider providerTestToUrlLinkTemplates * * @covers ::toUrl * @covers ::linkTemplates * @covers ::urlRouteParameters */ public function testToUrlLinkTemplates($link_template, $expected_route_name) { $values = ['id' => $this->entityId, 'langcode' => $this->langcode]; $entity = $this->getEntity(Entity::class, $values); $this->registerLinkTemplate($link_template); /** @var \Drupal\Core\Url $url */ $url = $entity->toUrl($link_template); // The entity ID is the sole route parameter for the link templates tested // here. $this->assertUrl($expected_route_name, ['test_entity' => $this->entityId], $entity, TRUE, $url); } /** * Provides data for testToUrlLinkTemplates(). * * @return array * An array of test cases for testToUrlLinkTemplates(). */ public function providerTestToUrlLinkTemplates() { $test_cases = []; $test_cases['canonical'] = ['canonical', 'entity.test_entity.canonical']; $test_cases['version-history'] = ['version-history', 'entity.test_entity.version_history']; $test_cases['edit-form'] = ['edit-form', 'entity.test_entity.edit_form']; $test_cases['delete-form'] = ['delete-form', 'entity.test_entity.delete_form']; $test_cases['revision'] = ['revision', 'entity.test_entity.revision']; return $test_cases; } /** * Tests the toUrl() method with the 'revision' link template. * * @param bool $is_default_revision * Whether or not the mock entity should be the default revision. * @param string $link_template * The link template to test. * @param string $expected_route_name * The expected route name of the generated URL. * @param array $expected_route_parameters * The expected route parameters of the generated URL. * * @dataProvider providerTestToUrlLinkTemplateRevision * * @covers ::toUrl * @covers ::linkTemplates * @covers ::urlRouteParameters */ public function testToUrlLinkTemplateRevision($is_default_revision, $link_template, $expected_route_name, array $expected_route_parameters) { $values = ['id' => $this->entityId, 'langcode' => $this->langcode]; $entity = $this->getEntity(RevisionableEntity::class, $values); $entity->method('getRevisionId')->willReturn($this->revisionId); $entity->method('isDefaultRevision')->willReturn($is_default_revision); $this->registerLinkTemplate($link_template); // Even though this is tested with both the 'canonical' and the 'revision' // template registered with the entity, we always ask for the 'revision' // link template, to test that it falls back to the 'canonical' link // template in case of the default revision. /** @var \Drupal\Core\Url $url */ $url = $entity->toUrl('revision'); $this->assertUrl($expected_route_name, $expected_route_parameters, $entity, TRUE, $url); } /** * Provides data for testToUrlLinkTemplateRevision(). * * @return array * An array of test cases for testToUrlLinkTemplateRevision(). */ public function providerTestToUrlLinkTemplateRevision() { $test_cases = []; $route_parameters = ['test_entity' => $this->entityId]; $test_cases['default_revision'] = [static::DEFAULT_REVISION, 'canonical', 'entity.test_entity.canonical', $route_parameters]; // Add the revision ID to the expected route parameters. $route_parameters['test_entity_revision'] = $this->revisionId; $test_cases['non_default_revision'] = [static::NON_DEFAULT_REVISION, 'revision', 'entity.test_entity.revision', $route_parameters]; return $test_cases; } /** * Tests the toUrl() method with link templates without an entity ID. * * @param string $link_template * The link template to test. * @param string $expected_route_name * The expected route name of the generated URL. * * @dataProvider providerTestToUrlLinkTemplateNoId * * @covers ::toUrl * @covers ::linkTemplates * @covers ::urlRouteParameters */ public function testToUrlLinkTemplateNoId($link_template, $expected_route_name) { $entity = $this->getEntity(Entity::class, ['id' => $this->entityId]); $this->registerLinkTemplate($link_template); /** @var \Drupal\Core\Url $url */ $url = $entity->toUrl($link_template); $this->assertUrl($expected_route_name, [], $entity, FALSE, $url); } /** * Provides data for testToUrlLinkTemplateNoId(). * * @return array * An array of test cases for testToUrlLinkTemplateNoId(). */ public function providerTestToUrlLinkTemplateNoId() { $test_cases = []; $test_cases['collection'] = ['collection', 'entity.test_entity.collection']; $test_cases['add-page'] = ['add-page', 'entity.test_entity.add_page']; return $test_cases; } /** * Tests the toUrl() method with the 'revision' link template. * * @param bool $has_bundle_key * Whether or not the mock entity type should have a bundle key. * @param string|null $bundle_entity_type * The ID of the bundle entity type of the mock entity type, or NULL if the * mock entity type should not have a bundle entity type. * @param string $bundle_key * The bundle key of the mock entity type or FALSE if the entity type should * not have a bundle key. * @param array $expected_route_parameters * The expected route parameters of the generated URL. * * @dataProvider providerTestToUrlLinkTemplateAddForm * * @covers ::toUrl * @covers ::linkTemplates * @covers ::urlRouteParameters */ public function testToUrlLinkTemplateAddForm($has_bundle_key, $bundle_entity_type, $bundle_key, $expected_route_parameters) { $values = ['id' => $this->entityId, 'langcode' => $this->langcode]; $entity = $this->getEntity(Entity::class, $values); $this->entityType->hasKey('bundle')->willReturn($has_bundle_key); $this->entityType->getBundleEntityType()->willReturn($bundle_entity_type); $this->entityType->getKey('bundle')->willReturn($bundle_key); $link_template = 'add-form'; $this->registerLinkTemplate($link_template); /** @var \Drupal\Core\Url $url */ $url = $entity->toUrl($link_template); $this->assertUrl('entity.test_entity.add_form', $expected_route_parameters, $entity, FALSE, $url); } /** * Provides data for testToUrlLinkTemplateAddForm(). * * @return array * An array of test cases for testToUrlLinkTemplateAddForm(). */ public function providerTestToUrlLinkTemplateAddForm() { $test_cases = []; $route_parameters = []; $test_cases['no_bundle_key'] = [static::HAS_NO_BUNDLE_KEY, NULL, FALSE, $route_parameters]; $route_parameters = ['type' => $this->entityTypeId]; $test_cases['bundle_entity_type'] = [static::HAS_BUNDLE_KEY, 'type', FALSE, $route_parameters]; $test_cases['bundle_key'] = [static::HAS_BUNDLE_KEY, NULL, 'type', $route_parameters]; return $test_cases; } /** * Tests the toUrl() method with neither link templates nor a URI callback. * * @param array $bundle_info * An array of bundle info to register. * @param string $uri_callback * The entity type URI callback to register. * * @dataProvider providerTestToUrlUriCallbackUndefined * * @covers ::toUrl * @covers ::linkTemplates */ public function testToUrlUriCallbackUndefined(array $bundle_info, $uri_callback) { $entity = $this->getEntity(Entity::class, ['id' => $this->entityId]); $this->registerBundleInfo($bundle_info); $this->entityType->getUriCallback()->willReturn($uri_callback); $link_template = 'canonical'; $this->setExpectedException(UndefinedLinkTemplateException::class, "No link template '$link_template' found for the '$this->entityTypeId' entity type"); $entity->toUrl($link_template); } /** * Provides data for testToUrlUriCallbackUndefined(). * * @return array * An array of test cases for testToUrlUriCallbackUndefined(). */ public function providerTestToUrlUriCallbackUndefined() { $test_cases = []; $test_cases['no_callback'] = [[], NULL]; $test_cases['uri_callback'] = [[], 'not_a_callable']; $test_cases['bundle_uri_callback'] = [['uri_callback' => 'not_a_callable'], NULL]; return $test_cases; } /** * Tests the toUrl() method with a URI callback. * * @param array $bundle_info * An array of bundle info to register. * @param string $uri_callback * The entity type URI callback to register. * * @covers ::toUrl * @covers ::linkTemplates * * @dataProvider providerTestToUrlUriCallback */ public function testToUrlUriCallback(array $bundle_info, $uri_callback) { $entity = $this->getEntity(Entity::class, ['id' => $this->entityId, 'langcode' => $this->langcode]); $this->registerBundleInfo($bundle_info); $this->entityType->getUriCallback()->willReturn($uri_callback); /** @var \Drupal\Core\Url $url */ $url = $entity->toUrl('canonical'); $this->assertUrl('', [], $entity, TRUE, $url); } /** * Provides data for testToUrlUriCallback(). * * @return array * An array of test cases for testToUrlUriCallback(). */ public function providerTestToUrlUriCallback() { $test_cases = []; $uri_callback = function () { return Url::fromRoute(''); }; $test_cases['uri_callback'] = [[], $uri_callback]; $test_cases['bundle_uri_callback'] = [['uri_callback' => $uri_callback], NULL]; return $test_cases; } /** * Tests the urlInfo() method. * * @param string $rel * The link relation to test. * @param array $options * An array of URL options to test with. * * @covers ::urlInfo * * @dataProvider providerTestUrlInfo */ public function testUrlInfo($rel, $options) { $entity = $this->getEntity(Entity::class, [], ['toUrl']); $entity->expects($this->once()) ->method('toUrl') ->with($rel, $options); $entity->urlInfo($rel, $options); } /** * Provides data for testUrlInfo(). * * @return array * An array of test cases for testUrlInfo(). */ public function providerTestUrlInfo() { $test_cases = []; $test_cases['default'] = ['canonical', []]; $test_cases['with_option'] = ['canonical', ['absolute' => TRUE]]; $test_cases['revision'] = ['revision', []]; return $test_cases; } /** * Tests the url() method without an entity ID. * * @param string $rel * The link relation to test. * * @covers ::url * @covers ::hasLinkTemplate * @covers ::linkTemplates * * @dataProvider providerTestUrl */ public function testUrlEmpty($rel) { $entity = $this->getEntity(Entity::class, []); $this->assertEquals('', $entity->url($rel)); } /** * Provides data for testUrlEmpty(). * * @return array * An array of test cases for testUrlEmpty(). */ public function providerTestUrlEmpty() { $test_cases = []; $test_cases['default'] = ['canonical', []]; $test_cases['revision'] = ['revision', []]; return $test_cases; } /** * Tests the url() method. * * @param string $rel * The link relation to test. * @param array $options * An array of URL options to call url() with. * @param array $default_options * An array of URL options that toUrl() should generate. * @param array $expected_options * An array of combined URL options that should be set on the final URL. * * @covers ::url * @covers ::hasLinkTemplate * @covers ::linkTemplates * * @dataProvider providerTestUrl */ public function testUrl($rel, $options, $default_options, $expected_options) { $entity = $this->getEntity(Entity::class, ['id' => $this->entityId], ['toUrl']); $this->registerLinkTemplate($rel); $uri = $this->prophesize(Url::class); $uri->getOptions()->willReturn($default_options); $uri->setOptions($expected_options)->shouldBeCalled(); $url_string = "/test-entity/{$this->entityId}/$rel"; $uri->toString()->willReturn($url_string); $entity->expects($this->once()) ->method('toUrl') ->with($rel) ->willReturn($uri->reveal()); $this->assertEquals($url_string, $entity->url($rel, $options)); } /** * Provides data for testUrl(). * * @return array * An array of test cases for testUrl(). */ public function providerTestUrl() { $test_cases = []; $test_cases['default'] = ['canonical', [], [], []]; $test_cases['revision'] = ['revision', [], [], []]; $test_cases['option'] = ['canonical', ['absolute' => TRUE], [], ['absolute' => TRUE]]; $test_cases['default_option'] = ['canonical', [], ['absolute' => TRUE], ['absolute' => TRUE]]; $test_cases['option_merge'] = ['canonical', ['absolute' => TRUE], ['entity_type' => $this->entityTypeId], ['absolute' => TRUE, 'entity_type' => $this->entityTypeId]]; $test_cases['option_override'] = ['canonical', ['absolute' => TRUE], ['absolute' => FALSE], ['absolute' => TRUE]]; return $test_cases; } /** * Tests the uriRelationships() method. * * @covers ::uriRelationships */ public function testUriRelationships() { $entity = $this->getEntity(Entity::class, ['id' => $this->entityId]); $container_builder = new ContainerBuilder(); $url_generator = $this->createMock(UrlGeneratorInterface::class); $container_builder->set('url_generator', $url_generator); \Drupal::setContainer($container_builder); // Test route with no mandatory parameters. $this->registerLinkTemplate('canonical'); $route_name_0 = 'entity.' . $this->entityTypeId . '.canonical'; $url_generator->expects($this->at(0)) ->method('generateFromRoute') ->with($route_name_0) ->willReturn((new GeneratedUrl())->setGeneratedUrl('/entity_test')); $this->assertEquals(['canonical'], $entity->uriRelationships()); // Test route with non-default mandatory parameters. $this->registerLinkTemplate('{non_default_parameter}'); $route_name_1 = 'entity.' . $this->entityTypeId . '.{non_default_parameter}'; $url_generator->expects($this->at(0)) ->method('generateFromRoute') ->with($route_name_1) ->willThrowException(new MissingMandatoryParametersException()); $this->assertEquals([], $entity->uriRelationships()); } /** * Returns a mock entity for testing. * * @param string $class * The class name to mock. Should be \Drupal\Core\Entity\Entity or a * subclass. * @param array $values * An array of entity values to construct the mock entity with. * @param array $methods * (optional) An array of additional methods to mock on the entity object. * The getEntityType() and entityManager() methods are always mocked. * * @return \Drupal\Core\Entity\Entity|\PHPUnit_Framework_MockObject_MockObject */ protected function getEntity($class, array $values, array $methods = []) { $methods = array_merge($methods, ['getEntityType', 'entityManager', 'entityTypeBundleInfo']); // Prophecy does not allow prophesizing abstract classes while actually // calling their code. We use Prophecy below because that allows us to // add method prophecies later while still revealing the prophecy now. $entity = $this->getMockBuilder($class) ->setConstructorArgs([$values, $this->entityTypeId]) ->setMethods($methods) ->getMockForAbstractClass(); $this->entityType = $this->prophesize(EntityTypeInterface::class); $this->entityType->getLinkTemplates()->willReturn([]); $this->entityType->getKey('langcode')->willReturn(FALSE); $entity->method('getEntityType')->willReturn($this->entityType->reveal()); $this->entityTypeBundleInfo = $this->prophesize(EntityTypeBundleInfoInterface::class); $entity->method('entityTypeBundleInfo')->willReturn($this->entityTypeBundleInfo->reveal()); return $entity; } /** * Asserts that a given URL object matches the expectations. * * @param string $expected_route_name * The expected route name of the generated URL. * @param array $expected_route_parameters * The expected route parameters of the generated URL. * @param \Drupal\Core\Entity\Entity|\PHPUnit_Framework_MockObject_MockObject $entity * The entity that is expected to be set as a URL option. * @param bool $has_language * Whether or not the URL is expected to have a language option. * @param \Drupal\Core\Url $url * The URL option to make the assertions on. */ protected function assertUrl($expected_route_name, array $expected_route_parameters, $entity, $has_language, Url $url) { $this->assertEquals($expected_route_name, $url->getRouteName()); $this->assertEquals($expected_route_parameters, $url->getRouteParameters()); $this->assertEquals($this->entityTypeId, $url->getOption('entity_type')); $this->assertEquals($entity, $url->getOption('entity')); if ($has_language) { $this->assertEquals($this->langcode, $url->getOption('language')->getId()); } else { $this->assertNull($url->getOption('language')); } } /** * Registers a link template for the mock entity. * * @param string $link_template * The link template to register. */ protected function registerLinkTemplate($link_template) { $link_templates = [ // The path is actually never used because we never invoke the URL // generator but perform assertions on the URL object directly. $link_template => "/test-entity/{test_entity}/$link_template", ]; $this->entityType->getLinkTemplates()->willReturn($link_templates); } /** * Registers bundle information for the mock entity type. * * @param array $bundle_info * The bundle information to register. */ protected function registerBundleInfo($bundle_info) { $this->entityTypeBundleInfo ->getBundleInfo($this->entityTypeId) ->willReturn([$this->entityTypeId => $bundle_info]); } } abstract class RevisionableEntity extends Entity implements RevisionableInterface {}