You are here

EntityContext.behat.inc in Lightning Core 8.3

File

tests/contexts/EntityContext.behat.inc
View source
<?php

namespace Acquia\LightningExtension\Context;

use Behat\Gherkin\Node\TableNode;
use Behat\Mink\Exception\ExpectationException;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\StringTranslation\PluralTranslatableMarkup as Plural;
use Drupal\DrupalExtension\Context\DrupalSubContextBase;
use Drupal\DrupalExtension\Hook\Scope\BeforeNodeCreateScope;
use Drupal\user\EntityOwnerInterface;

/**
 * Contains miscellaneous step definitions for working with Drupal entities.
 *
 * @internal
 *   This is an internal part of Lightning Core's testing system and may be
 *   changed or removed at any time without warning. It should not be extended,
 *   instantiated, or used in any way by external code! If you need to use this
 *   functionality, you should copy the relevant code into your own project.
 */
final class EntityContext extends DrupalSubContextBase {

  /**
   * IDs of entities created during the scenario.
   *
   * The IDs are divided into groups, keyed by entity type ID.
   *
   * @var array
   */
  private $trash = [];

  /**
   * Saves an entity, ensuring automatic clean-up.
   *
   * @param \Drupal\Core\Entity\EntityInterface $entity
   *   The entity to save.
   */
  public function save(EntityInterface $entity) {
    $uid = $this
      ->getCurrentUserUid();
    if ($uid && $entity instanceof EntityOwnerInterface && $entity
      ->getOwnerId() == 0) {
      $entity
        ->setOwnerId($uid)
        ->save();
    }
    else {
      $entity
        ->save();
      $entity_type = $entity
        ->getEntityTypeId();
      $this->trash[$entity_type][] = $entity
        ->id();
    }
  }

  /**
   * Creates a set of entities from tabular data.
   *
   * @param string $entity_type
   *   The entity type ID.
   * @param \Behat\Gherkin\Node\TableNode $table
   *   The table of entity data.
   *
   * @Given :entity_type entities:
   */
  public function createMultiple($entity_type, TableNode $table) {
    $storage = \Drupal::entityTypeManager()
      ->getStorage($entity_type);
    foreach ($table as $values) {
      $this
        ->save($storage
        ->create($values));
    }
  }

  /**
   * Deletes entities created during the scenario.
   *
   * @afterScenario
   */
  public function tearDown() {
    foreach ($this->trash as $entity_type => $ids) {

      /** @var \Drupal\Core\Entity\EntityInterface[] $entities */
      $entities = \Drupal::entityTypeManager()
        ->getStorage($entity_type)
        ->loadMultiple($ids);
      foreach ($entities as $entity) {
        $entity
          ->delete();
      }
    }
  }

  /**
   * Visits a randomly chosen entity of a specific type and (optional) bundle.
   *
   * @param string $entity_type
   *   The entity type ID.
   * @param string $bundle
   *   (optional) The bundle.
   *
   * @throws \Exception
   *   If there are no entities to visit.
   *
   * @When I visit (?:a|an) :entity_type
   * @When I visit (?:a|an) :bundle :entity_type
   */
  public function visitEntity($entity_type, $bundle = NULL) {
    $storage = \Drupal::entityTypeManager()
      ->getStorage($entity_type);
    $query = $storage
      ->getQuery();
    if ($bundle) {
      $key = $storage
        ->getEntityType()
        ->getKey('bundle');
      if ($key) {
        $query
          ->condition($key, $bundle);
      }
    }
    $items = $query
      ->execute();
    if ($items) {
      $id = reset($items);
      $url = $storage
        ->load($id)
        ->toUrl()
        ->getInternalPath();
      $this
        ->visitPath($url);
    }
    else {
      $label = $storage
        ->getEntityType()
        ->getPluralLabel();
      throw new \Exception("There are no {$bundle} {$label} available.");
    }
  }

  /**
   * Marks recently created entities to be deleted after the test scenario.
   *
   * @param string $entity_type
   *   The entity type ID.
   * @param int $n
   *   (optional) How many entities to delete.
   *
   * @When I delete the latest :entity_type
   * @When I delete the latest :entity_type entity
   * @When I delete the latest :n :entity_type entities
   */
  public function trashNewest($entity_type, $n = 1) {
    $items = \Drupal::entityTypeManager()
      ->getStorage($entity_type)
      ->getQuery()
      ->sort('created', 'DESC')
      ->range(0, $n)
      ->execute();
    foreach ($items as $id) {
      $this->trash[$entity_type][] = $id;
    }
  }

  /**
   * Counts the number of entities of a specific type and optional bundle.
   *
   * @param string $entity_type
   *   The entity type ID.
   * @param string $bundle
   *   (optional) The bundle.
   *
   * @return int
   *   How many entities exist of the given type and bundle.
   */
  private function count($entity_type, $bundle = NULL) {
    $storage = \Drupal::entityTypeManager()
      ->getStorage($entity_type);
    $query = $storage
      ->getQuery()
      ->count();
    if ($bundle) {
      $query
        ->condition($storage
        ->getEntityType()
        ->getKey('bundle'), $bundle);
    }
    return (int) $query
      ->execute();
  }

  /**
   * Asserts that entities of a specific type and optional bundle exist.
   *
   * @param string $entity_type
   *   The entity type ID.
   * @param int $n
   *   (optional) How many entities are expected to exist.
   * @param string $bundle
   *   (optional) The bundle.
   *
   * @throws \Behat\Mink\Exception\ExpectationException
   *   If the number of existing entities does not match the expected number.
   *
   * @Then 1 :entity_type entity should exist
   * @Then 1 :bundle :entity_type entity should exist
   * @Then :n :entity_type entities should exist
   * @Then :n :bundle :entity_type entities should exist
   */
  public function assertCount($entity_type, $n = 1, $bundle = NULL) {
    $count = $this
      ->count($entity_type, $bundle);
    if ($count !== (int) $n) {
      $message = sprintf('Expected %s to exist, but there are only %s.', $this
        ->formatCount($entity_type, $n), $this
        ->formatCount($entity_type, $count));
      throw new ExpectationException($message, $this
        ->getSession()
        ->getDriver());
    }
  }

  /**
   * Generates a formatted entity count string.
   *
   * @param string $entity_type
   *   The entity type ID.
   * @param int $n
   *   The number of entities.
   *
   * @see ::assertCount()
   *
   * @return string
   *   A formatted entity count string, e.g. '33 content items'.
   */
  private function formatCount($entity_type, $n) {
    $entity_type = \Drupal::entityTypeManager()
      ->getDefinition($entity_type);
    return (string) new Plural((int) $n, '@count ' . $entity_type
      ->getSingularLabel(), '@count' . $entity_type
      ->getPluralLabel());
  }

  /**
   * Visits the edit form for an entity.
   *
   * @see lightning_dev_local_tasks_alter()
   *
   * @When I visit the edit form
   */
  public function edit() {
    $this
      ->assertSession()
      ->elementExists('named', [
      'link',
      'edit-form',
    ])
      ->click();
  }

  /**
   * Reacts when a node is created by Drupal Extension's step definition.
   *
   * @BeforeNodeCreate
   */
  public function onNodeCreate(BeforeNodeCreateScope $scope) {
    $node = $scope
      ->getEntity();
    if (!isset($node->uid)) {
      $node->uid = $this
        ->getCurrentUserUid();
    }
  }

  /**
   * Gets the current user's UID.
   *
   * @return int
   *   Returns the current user's UID if there is one or 0 if not.
   */
  protected function getCurrentUserUid() {
    $user = $this
      ->getUserManager()
      ->getCurrentUser();
    return $user ? $user->uid : 0;
  }

  /**
   * Visits a specific revision of a node.
   *
   * @param int $n
   *   The one-based index of the revision.
   *
   * @When /^I visit the (\d+)(?:st|nd|rd|th) revision$/
   */
  public function visitRevision($n) {
    $this
      ->assertSession()
      ->elementExists('named', [
      'link',
      'Revisions',
    ])
      ->click();
    $this
      ->getContext(ElementContext::class)
      ->click("main table tr:nth-child({$n}) td:first-child a");
  }

  /**
   * Visits the current revision of a node.
   *
   * @When I visit the current revision
   */
  public function visitCurrentRevision() {
    $this
      ->assertSession()
      ->elementExists('named', [
      'link',
      'Revisions',
    ])
      ->click();
    $session = $this
      ->getSession();

    // The revision table's selector may vary depending on whether or not Diff
    // is installed, so we need to use a pretty general selector.
    $rows = $session
      ->getPage()
      ->findAll('css', 'main table > tbody tr');

    /** @var \Behat\Mink\Element\NodeElement $row */
    foreach ($rows as $row) {
      if ($row
        ->find('css', 'td:last-child')
        ->getText() == 'Current revision') {
        return $row
          ->find('css', 'td:first-child a')
          ->click();
      }
    }

    // WTF? None of the rows were the current revision.
    throw new ExpectationException('Current revision not found.', $session
      ->getDriver());
  }

}

Classes

Namesort descending Description
EntityContext Contains miscellaneous step definitions for working with Drupal entities.