3 namespace Drupal\Tests\Core\Entity;
5 use Drupal\Core\Entity\Entity;
6 use Drupal\Core\Entity\EntityMalformedException;
7 use Drupal\Core\Entity\EntityManagerInterface;
8 use Drupal\Core\Entity\EntityTypeInterface;
9 use Drupal\Core\Entity\Exception\UndefinedLinkTemplateException;
10 use Drupal\Core\Entity\RevisionableInterface;
12 use Drupal\Tests\UnitTestCase;
15 * Tests URL handling of the \Drupal\Core\Entity\Entity class.
17 * @coversDefaultClass \Drupal\Core\Entity\Entity
21 class EntityUrlTest extends UnitTestCase {
24 * The entity manager mock used in this test.
26 * @var \Prophecy\Prophecy\ProphecyInterface|\Drupal\Core\Entity\EntityManagerInterface
28 protected $entityManager;
31 * The ID of the entity type used in this test.
35 protected $entityTypeId = 'test_entity';
38 * The entity type mock used in this test.
40 * @var \Prophecy\Prophecy\ProphecyInterface|\Drupal\Core\Entity\EntityTypeInterface
42 protected $entityType;
45 * The ID of the entity used in this test.
49 protected $entityId = 1;
52 * The revision ID of the entity used in this test.
56 protected $revisionId = 2;
59 * The language code of the entity used in this test.
63 protected $langcode = 'en';
66 * Indicator for default revisions.
70 const DEFAULT_REVISION = TRUE;
73 * Indicator for non-default revisions.
77 const NON_DEFAULT_REVISION = FALSE;
80 * Indicator that the test entity type has no bundle key.
84 const HAS_NO_BUNDLE_KEY = FALSE;
87 * Indicator that the test entity type has a bundle key.
91 const HAS_BUNDLE_KEY = TRUE;
94 * Tests the toUrl() method without an entity ID.
98 public function testToUrlNoId() {
99 $entity = $this->getEntity(Entity::class, []);
101 $this->setExpectedException(EntityMalformedException::class, 'The "' . $this->entityTypeId . '" entity cannot have a URI as it does not have an ID');
106 * Tests the toUrl() method with simple link templates.
108 * @param string $link_template
109 * The link template to test.
110 * @param string $expected_route_name
111 * The expected route name of the generated URL.
113 * @dataProvider providerTestToUrlLinkTemplates
116 * @covers ::linkTemplates
117 * @covers ::urlRouteParameters
119 public function testToUrlLinkTemplates($link_template, $expected_route_name) {
120 $values = ['id' => $this->entityId, 'langcode' => $this->langcode];
121 $entity = $this->getEntity(Entity::class, $values);
122 $this->registerLinkTemplate($link_template);
124 /** @var \Drupal\Core\Url $url */
125 $url = $entity->toUrl($link_template);
126 // The entity ID is the sole route parameter for the link templates tested
128 $this->assertUrl($expected_route_name, ['test_entity' => $this->entityId], $entity, TRUE, $url);
132 * Provides data for testToUrlLinkTemplates().
135 * An array of test cases for testToUrlLinkTemplates().
137 public function providerTestToUrlLinkTemplates() {
140 $test_cases['canonical'] = ['canonical', 'entity.test_entity.canonical'];
141 $test_cases['version-history'] = ['version-history', 'entity.test_entity.version_history'];
142 $test_cases['edit-form'] = ['edit-form', 'entity.test_entity.edit_form'];
143 $test_cases['delete-form'] = ['delete-form', 'entity.test_entity.delete_form'];
144 $test_cases['revision'] = ['revision', 'entity.test_entity.revision'];
150 * Tests the toUrl() method with the 'revision' link template.
152 * @param bool $is_default_revision
153 * Whether or not the mock entity should be the default revision.
154 * @param string $link_template
155 * The link template to test.
156 * @param string $expected_route_name
157 * The expected route name of the generated URL.
158 * @param array $expected_route_parameters
159 * The expected route parameters of the generated URL.
161 * @dataProvider providerTestToUrlLinkTemplateRevision
164 * @covers ::linkTemplates
165 * @covers ::urlRouteParameters
167 public function testToUrlLinkTemplateRevision($is_default_revision, $link_template, $expected_route_name, array $expected_route_parameters) {
168 $values = ['id' => $this->entityId, 'langcode' => $this->langcode];
169 $entity = $this->getEntity(RevisionableEntity::class, $values);
170 $entity->method('getRevisionId')->willReturn($this->revisionId);
171 $entity->method('isDefaultRevision')->willReturn($is_default_revision);
172 $this->registerLinkTemplate($link_template);
173 // Even though this is tested with both the 'canonical' and the 'revision'
174 // template registered with the entity, we always ask for the 'revision'
175 // link template, to test that it falls back to the 'canonical' link
176 // template in case of the default revision.
177 /** @var \Drupal\Core\Url $url */
178 $url = $entity->toUrl('revision');
179 $this->assertUrl($expected_route_name, $expected_route_parameters, $entity, TRUE, $url);
184 * Provides data for testToUrlLinkTemplateRevision().
187 * An array of test cases for testToUrlLinkTemplateRevision().
189 public function providerTestToUrlLinkTemplateRevision() {
192 $route_parameters = ['test_entity' => $this->entityId];
193 $test_cases['default_revision'] = [static::DEFAULT_REVISION, 'canonical', 'entity.test_entity.canonical', $route_parameters];
194 // Add the revision ID to the expected route parameters.
195 $route_parameters['test_entity_revision'] = $this->revisionId;
196 $test_cases['non_default_revision'] = [static::NON_DEFAULT_REVISION, 'revision', 'entity.test_entity.revision', $route_parameters];
202 * Tests the toUrl() method with link templates without an entity ID.
204 * @param string $link_template
205 * The link template to test.
206 * @param string $expected_route_name
207 * The expected route name of the generated URL.
209 * @dataProvider providerTestToUrlLinkTemplateNoId
212 * @covers ::linkTemplates
213 * @covers ::urlRouteParameters
215 public function testToUrlLinkTemplateNoId($link_template, $expected_route_name) {
216 $entity = $this->getEntity(Entity::class, ['id' => $this->entityId]);
217 $this->registerLinkTemplate($link_template);
219 /** @var \Drupal\Core\Url $url */
220 $url = $entity->toUrl($link_template);
221 $this->assertUrl($expected_route_name, [], $entity, FALSE, $url);
225 * Provides data for testToUrlLinkTemplateNoId().
228 * An array of test cases for testToUrlLinkTemplateNoId().
230 public function providerTestToUrlLinkTemplateNoId() {
233 $test_cases['collection'] = ['collection', 'entity.test_entity.collection'];
234 $test_cases['add-page'] = ['add-page', 'entity.test_entity.add_page'];
240 * Tests the toUrl() method with the 'revision' link template.
242 * @param bool $has_bundle_key
243 * Whether or not the mock entity type should have a bundle key.
244 * @param string|null $bundle_entity_type
245 * The ID of the bundle entity type of the mock entity type, or NULL if the
246 * mock entity type should not have a bundle entity type.
247 * @param string $bundle_key
248 * The bundle key of the mock entity type or FALSE if the entity type should
249 * not have a bundle key.
250 * @param array $expected_route_parameters
251 * The expected route parameters of the generated URL.
253 * @dataProvider providerTestToUrlLinkTemplateAddForm
256 * @covers ::linkTemplates
257 * @covers ::urlRouteParameters
259 public function testToUrlLinkTemplateAddForm($has_bundle_key, $bundle_entity_type, $bundle_key, $expected_route_parameters) {
260 $values = ['id' => $this->entityId, 'langcode' => $this->langcode];
261 $entity = $this->getEntity(Entity::class, $values);
262 $this->entityType->hasKey('bundle')->willReturn($has_bundle_key);
263 $this->entityType->getBundleEntityType()->willReturn($bundle_entity_type);
264 $this->entityType->getKey('bundle')->willReturn($bundle_key);
265 $link_template = 'add-form';
266 $this->registerLinkTemplate($link_template);
268 /** @var \Drupal\Core\Url $url */
269 $url = $entity->toUrl($link_template);
270 $this->assertUrl('entity.test_entity.add_form', $expected_route_parameters, $entity, FALSE, $url);
274 * Provides data for testToUrlLinkTemplateAddForm().
277 * An array of test cases for testToUrlLinkTemplateAddForm().
279 public function providerTestToUrlLinkTemplateAddForm() {
282 $route_parameters = [];
283 $test_cases['no_bundle_key'] = [static::HAS_NO_BUNDLE_KEY, NULL, FALSE, $route_parameters];
285 $route_parameters = ['type' => $this->entityTypeId];
286 $test_cases['bundle_entity_type'] = [static::HAS_BUNDLE_KEY, 'type', FALSE, $route_parameters];
287 $test_cases['bundle_key'] = [static::HAS_BUNDLE_KEY, NULL, 'type', $route_parameters];
293 * Tests the toUrl() method with neither link templates nor a URI callback.
295 * @param array $bundle_info
296 * An array of bundle info to register.
297 * @param string $uri_callback
298 * The entity type URI callback to register.
300 * @dataProvider providerTestToUrlUriCallbackUndefined
303 * @covers ::linkTemplates
305 public function testToUrlUriCallbackUndefined(array $bundle_info, $uri_callback) {
306 $entity = $this->getEntity(Entity::class, ['id' => $this->entityId]);
308 $this->registerBundleInfo($bundle_info);
309 $this->entityType->getUriCallback()->willReturn($uri_callback);
311 $link_template = 'canonical';
312 $this->setExpectedException(UndefinedLinkTemplateException::class, "No link template '$link_template' found for the '$this->entityTypeId' entity type");
313 $entity->toUrl($link_template);
317 * Provides data for testToUrlUriCallbackUndefined().
320 * An array of test cases for testToUrlUriCallbackUndefined().
322 public function providerTestToUrlUriCallbackUndefined() {
325 $test_cases['no_callback'] = [[], NULL];
326 $test_cases['uri_callback'] = [[], 'not_a_callable'];
327 $test_cases['bundle_uri_callback'] = [['uri_callback' => 'not_a_callable'], NULL];
333 * Tests the toUrl() method with a URI callback.
335 * @param array $bundle_info
336 * An array of bundle info to register.
337 * @param string $uri_callback
338 * The entity type URI callback to register.
341 * @covers ::linkTemplates
343 * @dataProvider providerTestToUrlUriCallback
345 public function testToUrlUriCallback(array $bundle_info, $uri_callback) {
346 $entity = $this->getEntity(Entity::class, ['id' => $this->entityId, 'langcode' => $this->langcode]);
348 $this->registerBundleInfo($bundle_info);
349 $this->entityType->getUriCallback()->willReturn($uri_callback);
351 /** @var \Drupal\Core\Url $url */
352 $url = $entity->toUrl('canonical');
353 $this->assertUrl('<none>', [], $entity, TRUE, $url);
357 * Provides data for testToUrlUriCallback().
360 * An array of test cases for testToUrlUriCallback().
362 public function providerTestToUrlUriCallback() {
365 $uri_callback = function () { return Url::fromRoute('<none>'); };
366 $test_cases['uri_callback'] = [[], $uri_callback];
367 $test_cases['bundle_uri_callback'] = [['uri_callback' => $uri_callback], NULL];
373 * Tests the urlInfo() method.
376 * The link relation to test.
377 * @param array $options
378 * An array of URL options to test with.
382 * @dataProvider providerTestUrlInfo
384 public function testUrlInfo($rel, $options) {
385 $entity = $this->getEntity(Entity::class, [], ['toUrl']);
386 $entity->expects($this->once())
388 ->with($rel, $options);
390 $entity->urlInfo($rel, $options);
394 * Provides data for testUrlInfo().
397 * An array of test cases for testUrlInfo().
399 public function providerTestUrlInfo() {
402 $test_cases['default'] = ['canonical', []];
403 $test_cases['with_option'] = ['canonical', ['absolute' => TRUE]];
404 $test_cases['revision'] = ['revision', []];
410 * Tests the url() method without an entity ID.
413 * The link relation to test.
416 * @covers ::hasLinkTemplate
417 * @covers ::linkTemplates
419 * @dataProvider providerTestUrl
421 public function testUrlEmpty($rel) {
422 $entity = $this->getEntity(Entity::class, []);
423 $this->assertEquals('', $entity->url($rel));
427 * Provides data for testUrlEmpty().
430 * An array of test cases for testUrlEmpty().
432 public function providerTestUrlEmpty() {
435 $test_cases['default'] = ['canonical', []];
436 $test_cases['revision'] = ['revision', []];
442 * Tests the url() method.
445 * The link relation to test.
446 * @param array $options
447 * An array of URL options to call url() with.
448 * @param array $default_options
449 * An array of URL options that toUrl() should generate.
450 * @param array $expected_options
451 * An array of combined URL options that should be set on the final URL.
454 * @covers ::hasLinkTemplate
455 * @covers ::linkTemplates
457 * @dataProvider providerTestUrl
459 public function testUrl($rel, $options, $default_options, $expected_options) {
460 $entity = $this->getEntity(Entity::class, ['id' => $this->entityId], ['toUrl']);
461 $this->registerLinkTemplate($rel);
463 $uri = $this->prophesize(Url::class);
464 $uri->getOptions()->willReturn($default_options);
465 $uri->setOptions($expected_options)->shouldBeCalled();
467 $url_string = "/test-entity/{$this->entityId}/$rel";
468 $uri->toString()->willReturn($url_string);
470 $entity->expects($this->once())
473 ->willReturn($uri->reveal());
475 $this->assertEquals($url_string, $entity->url($rel, $options));
479 * Provides data for testUrl().
482 * An array of test cases for testUrl().
484 public function providerTestUrl() {
487 $test_cases['default'] = ['canonical', [], [], []];
488 $test_cases['revision'] = ['revision', [], [], []];
489 $test_cases['option'] = ['canonical', ['absolute' => TRUE], [], ['absolute' => TRUE]];
490 $test_cases['default_option'] = ['canonical', [], ['absolute' => TRUE], ['absolute' => TRUE]];
491 $test_cases['option_merge'] = ['canonical', ['absolute' => TRUE], ['entity_type' => $this->entityTypeId], ['absolute' => TRUE, 'entity_type' => $this->entityTypeId]];
492 $test_cases['option_override'] = ['canonical', ['absolute' => TRUE], ['absolute' => FALSE], ['absolute' => TRUE]];
498 * Returns a mock entity for testing.
500 * @param string $class
501 * The class name to mock. Should be \Drupal\Core\Entity\Entity or a
503 * @param array $values
504 * An array of entity values to construct the mock entity with.
505 * @param array $methods
506 * (optional) An array of additional methods to mock on the entity object.
507 * The getEntityType() and entityManager() methods are always mocked.
509 * @return \Drupal\Core\Entity\Entity|\PHPUnit_Framework_MockObject_MockObject
511 protected function getEntity($class, array $values, array $methods = []) {
512 $methods = array_merge($methods, ['getEntityType', 'entityManager']);
514 // Prophecy does not allow prophesizing abstract classes while actually
515 // calling their code. We use Prophecy below because that allows us to
516 // add method prophecies later while still revealing the prophecy now.
517 $entity = $this->getMockBuilder($class)
518 ->setConstructorArgs([$values, $this->entityTypeId])
519 ->setMethods($methods)
520 ->getMockForAbstractClass();
522 $this->entityType = $this->prophesize(EntityTypeInterface::class);
523 $this->entityType->getLinkTemplates()->willReturn([]);
524 $this->entityType->getKey('langcode')->willReturn(FALSE);
525 $entity->method('getEntityType')->willReturn($this->entityType->reveal());
527 $this->entityManager = $this->prophesize(EntityManagerInterface::class);
528 $entity->method('entityManager')->willReturn($this->entityManager->reveal());
534 * Asserts that a given URL object matches the expectations.
536 * @param string $expected_route_name
537 * The expected route name of the generated URL.
538 * @param array $expected_route_parameters
539 * The expected route parameters of the generated URL.
540 * @param \Drupal\Core\Entity\Entity|\PHPUnit_Framework_MockObject_MockObject $entity
541 * The entity that is expected to be set as a URL option.
542 * @param bool $has_language
543 * Whether or not the URL is expected to have a language option.
544 * @param \Drupal\Core\Url $url
545 * The URL option to make the assertions on.
547 protected function assertUrl($expected_route_name, array $expected_route_parameters, $entity, $has_language, Url $url) {
548 $this->assertEquals($expected_route_name, $url->getRouteName());
549 $this->assertEquals($expected_route_parameters, $url->getRouteParameters());
550 $this->assertEquals($this->entityTypeId, $url->getOption('entity_type'));
551 $this->assertEquals($entity, $url->getOption('entity'));
553 $this->assertEquals($this->langcode, $url->getOption('language')->getId());
556 $this->assertNull($url->getOption('language'));
561 * Registers a link template for the mock entity.
563 * @param string $link_template
564 * The link template to register.
566 protected function registerLinkTemplate($link_template) {
568 // The path is actually never used because we never invoke the URL
569 // generator but perform assertions on the URL object directly.
570 $link_template => "/test-entity/{test_entity}/$link_template",
572 $this->entityType->getLinkTemplates()->willReturn($link_templates);
576 * Registers bundle information for the mock entity type.
578 * @param array $bundle_info
579 * The bundle information to register.
581 protected function registerBundleInfo($bundle_info) {
583 ->getBundleInfo($this->entityTypeId)
584 ->willReturn([$this->entityTypeId => $bundle_info])
590 abstract class RevisionableEntity extends Entity implements RevisionableInterface {}