Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Extending createNodes/assertViewingNode for files #323

Closed
Trogie opened this issue Oct 28, 2016 · 3 comments
Closed

Extending createNodes/assertViewingNode for files #323

Trogie opened this issue Oct 28, 2016 · 3 comments

Comments

@Trogie
Copy link

Trogie commented Oct 28, 2016

Hello,

I'm searching how to best extend the createNodes/assertViewingNode for file upload fields. Probably expandEntityFields.

I would like functionality like:
Given contenttype node:
| title | Test Node |
| file_field | logo.jpg |
So that behat/drupalextension inserts a new file from files_path into drupal(8) and attaches it to the node.

Even thinking about writing extra context for paragraphs(d7)/components(d8) too.

And ideas how to do this best?

Trogie

@pfrenssen
Copy link
Collaborator

The simplest way to do this without any custom code is to use this step from MinkContext:

@When I attach the file "myfile.png" to "My field"

This is however not great from a BDD standpoint. It would be preferable to be able to provide a table of field values, as you suggest.

Here is an example how we solved this in a project. Note that we are following strict BDD principles, so we are not using the default steps. We are using our own project's Domain Specific Language, which does not use Drupalisms such as "content type" and "node". We also never use machine names since these are not known by the end user. So we use for example logo instead of field_article_logo and for setting checkboxes we use on and off instead of 1 and 0.

In our user scenario we would have a step such as the following. This creates a "collection" (our DSL term for a group from the Organic Groups module), which includes two file fields: logo and banner:

Given the following collection:
  | title             | Open Data Initiative |
  | logo              | logo.png             |
  | banner            | banner.jpg           |
  | moderation        | no                   |
  | closed            | no                   |
  | elibrary creation | facilitators         |
  | policy domain     | Health               |
  | state             | validated            |

Here is the step definition:

  /**
   * Creates a collection with data provided in a table.
   *
   * Table format:
   * | title           | Open Data Initiative                  |
   * | author          | Mightily Oats                         |
   * | logo            | logo.png                              |
   * | moderation      | yes|no                                |
   * | closed          | yes|no                                |
   * | create elibrary | facilitators|members|registered users |
   * | metadata url    | https://ec.europa.eu/my/url           |
   *
   * Only the title field is required.
   *
   * @param TableNode $collection_table
   *   The collection data.
   *
   * @throws \Exception
   *   Thrown when a column name is incorrect.
   *
   * @Given (the following )collection:
   */
  public function givenCollection(TableNode $collection_table) {
    $aliases = self::collectionFieldAliases();

    $values = [];
    // Replace the column aliases with the actual field names.
    foreach ($collection_table->getRowsHash() as $key => $value) {
      if (array_key_exists($key, $aliases)) {
        $values[$aliases[$key]] = $value;
      }
      else {
        throw new \Exception("Unknown column '$key' in collection table.");
      }
    };

    // Convert user friendly values to machine values.
    $values = $this->convertValueAliases($values);

    $this->createCollection($values);
  }

ref. givenCollection().

Most of this code is dealing with translating human readable values back to machine readable ones that we can use for creating the entity. Ref. collectionFieldAliases() which provides human readable field names, and convertValueAliases() which provides human readable values (e.g. for checkboxes and radio buttons).

Then finally it passes to $this->createCollection() which distinguishes between normal fields and file upload fields:

  /**
   * Creates a collection from the given property and field data.
   *
   * @param array $values
   *   An optional associative array of values, keyed by property name.
   *
   * @return \Drupal\rdf_entity\Entity\Rdf
   *   A new collection entity.
   *
   * @throws \Exception
   *   Thrown when a given image is not found.
   */
  protected function createCollection(array $values) {
    // Add images.
    $image_fields = ['field_ar_banner', 'field_ar_logo'];
    foreach ($image_fields as $field_name) {
      if (!empty($values[$field_name])) {
        $values[$field_name] = [$this->createFile($values[$field_name], $this->getMinkParameter('files_path'))];
      }
    }

    $values['rid'] = 'collection';
    $collection = Rdf::create($values);
    $collection->save();
    $this->collections[$collection->id()] = $collection;

    return $collection;
  }

Ref. createCollection().

This calls $this->createFile() which copies the file to the files folder and returns the file ID that can be referenced in the entity:

  /**
   * Saves a file for an entity and returns the file's ID.
   *
   * @param string $filename
   *   The file name given by the user.
   * @param string $files_path
   *   The file path where the file exists in.
   *
   * @return int
   *   The file ID returned by the File::save() method.
   *
   * @throws \Exception
   *   Throws an exception when the file is not found.
   */
  public function createFile($filename, $files_path) {
    $path = rtrim(realpath($files_path), DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . $filename;
    if (!is_file($path)) {
      throw new \Exception("File '$filename' was not found in file path '$files_path'.");
    }
    // Copy the file into the public files folder and turn it into a File
    // entity before linking it to the collection.
    $uri = 'public://' . $filename;
    $destination = file_unmanaged_copy($path, $uri);
    $file = File::create(['uri' => $destination]);
    $file->save();

    $this->files[$file->id()] = $file;

    return $file->id();
  }

Ref. createFile().

Note that you also need to keep track of the files you create so you can clean them up after the test scenario ends:

  /**
   * Remove any created files.
   *
   * @AfterScenario
   */
  public function cleanFiles() {
    // Remove the image entities that were attached to the collections.
    foreach ($this->files as $file) {
      $file->delete();
    }
  }

@jonathanjfshaw
Copy link
Contributor

jonathanjfshaw commented Jan 19, 2017

This works using #300 :

    Given a "file" entity:
    | uri               | 
    | public://test.mp3 |
    Given I am viewing an "article" content:
    | title | Test audio   |
    | test | test.mp3      |
    Then I should see the link "test.mp3"

It doesn't need an actual file at the URI; it works because file fields are entity reference fields to file entities.

It's not ideal from a BDD perspective because declaring the file entities first is exposing an implementation detail. I wonder if we could make the driver autocreate the targets of entity references that did not already exist.

@jonathanjfshaw
Copy link
Contributor

jhedstrom/DrupalDriver#123 handles files in @given content.
#355 handles Drupal and @when I attach the file
Therefore this issue can be closed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants