FeedsParaMapperWebTestCase.test in Feeds Paragraphs 7
Common functionality for all Paragraphs Mapper tests.
File
tests/FeedsParaMapperWebTestCase.testView source
<?php
/**
* @file
* Common functionality for all Paragraphs Mapper tests.
*/
/**
* Test basic functionality via DrupalWebTestCase.
*/
class FeedsParaMapperWebTestCase extends DrupalWebTestCase {
protected $webUser;
protected $profile = 'testing';
protected $bundles;
protected $contentType;
protected $paragraphField;
protected $importer;
protected $multiValued = FALSE;
protected $multiValuedParagraph = FALSE;
protected $createdPlugins;
protected $nested = FALSE;
/**
* Prepares the test environment.
*/
public function setUp() {
$args = func_get_args();
if (isset($args[0])) {
$this->multiValued = $args[0];
}
if (isset($args[1])) {
$this->multiValuedParagraph = $args[1];
}
if (isset($args[2])) {
$this->nested = $args[2];
}
$modules = array(
'field',
'field_ui',
'image',
'paragraphs',
'feeds',
'feeds_ui',
'feeds_para_mapper',
);
$permissions = array(
'bypass node access',
'administer nodes',
'administer feeds',
'administer content types',
'administer fields',
'administer paragraphs bundles',
);
// If we are testing multi valued fields, load Feeds Tamper module:
if ($this->multiValued || $this->multiValuedParagraph) {
$modules[] = "feeds_tamper";
$modules[] = "feeds_tamper_ui";
$permissions[] = "administer feeds_tamper";
}
parent::setUp($modules);
$this->webUser = $this
->drupalCreateUser($permissions);
$this
->drupalLogin($this->webUser);
// Create paragraphs bundles:
$this
->createBundles();
// Create a content type with a paragraph field:
$this->contentType = "product";
$this->paragraphField = "details";
$last_key = count($this->bundles) - 1;
$last_bundle = array(
$this->bundles[$last_key]['name'],
);
$this
->createContentType($this->contentType, $this->paragraphField, $last_bundle);
// Create importer for the content type:
$this->importer = "product";
$this
->addImporter($this->importer, $this->contentType);
if ($this->nested) {
$this
->isNestedCorrectly();
}
// Make sure that all assets files exist:
$this
->assetsFilesExist();
// Copy the image to the public path:
$this
->copyFile('/tests/assets/image.png', 'public://image.png');
}
/**
* Creates the needed Paragraphs bundles in a loop.
*
* 1- Create a bundle with a text field.
* 2- Create another bundle with a paragraph field,
* with the previous bundle as allowed.
*/
protected function createBundles() {
$counter = $this->nested ? 2 : 1;
for ($i = 0; $i < $counter; $i++) {
if ($i === 0) {
$cardinality = $this->multiValued ? -1 : 1;
$bundle = array(
'name' => 'image_details_bundle',
'fields' => array(
array(
'name' => 'description',
'type' => "text",
'widget' => 'text_textfield',
'cardinality' => $cardinality,
'mapping' => array(
'text' => 'field_description',
),
'mapping_multiple' => array(
'text_multiple' => 'field_description',
),
),
array(
'name' => 'image',
'type' => "image",
'widget' => 'image_image',
'cardinality' => $cardinality,
'mapping' => array(
'image_alt' => 'field_image:alt',
'image_title' => 'field_image:title',
'image_uri' => 'field_image:uri',
),
'mapping_multiple' => array(
'image_multi_alt' => 'field_image:alt',
'image_multi_title' => 'field_image:title',
'image_multi_uri' => 'field_image:uri',
),
),
),
);
}
else {
$isLast = $i + 1 === $counter;
$cardinality = $this->multiValuedParagraph && $isLast ? -1 : 1;
$bundle = array(
'name' => 'image_bundle',
'fields' => array(
array(
'name' => 'images',
'type' => "paragraphs",
'widget' => 'paragraphs_embed',
'cardinality' => $cardinality,
'allowed_bundles' => array(
end($this->bundles)['name'],
),
),
),
);
}
$this->bundles[] = $bundle;
$this
->createBundle($bundle);
}
}
/**
* Creates a paragraph bundle.
*
* @param array $bundle
* Includes the bundle name and the field details.
*/
protected function createBundle(array $bundle) {
$this
->drupalGet('admin/structure/paragraphs/add');
$edit = array(
'name' => $bundle['name'],
'bundle' => $bundle['name'],
);
$this
->drupalPost(NULL, $edit, t('Save Paragraph bundle'));
$message = format_string("Created the paragraph bundle @name.", array(
'@name' => $bundle['name'],
));
$text = t("The paragraph bundle @name has been added.", array(
'@name' => $bundle['name'],
));
$this
->assertText($text, $message, 'Setup');
// Add A field to the bundle:
$bundle_name = $bundle['name'];
$this
->drupalGet("admin/structure/paragraphs/{$bundle_name}/fields");
$fields = $bundle['fields'];
foreach ($fields as $field) {
$allowed_bundles = array();
if (isset($field['allowed_bundles'])) {
$allowed_bundles = $field['allowed_bundles'];
}
$this
->createField($field['name'], $field['cardinality'], $field['type'], $field['widget'], $allowed_bundles);
}
}
/**
* Utility function to create a content type.
*
* @param string $name
* The content type name.
* @param string $paragraph_field
* The paragraph field name to add to the content type.
* @param array $allowed_bundles
* The allowed bundles for the paragraph field.
*/
protected function createContentType($name, $paragraph_field, array $allowed_bundles) {
$this
->drupalGet('admin/structure/types/add');
$edit = array(
'name' => $name,
'type' => $name,
);
$this
->drupalPost(NULL, $edit, t('Save and add fields'));
$text = t('The content type @name has been added.', array(
'@name' => $name,
));
$this
->assertText($text, NULL, "Setup");
$fields = array();
$cardinality = $this->multiValuedParagraph ? -1 : 1;
$fields[$paragraph_field] = array(
'type' => "paragraphs",
'widget' => 'paragraphs_embed',
'cardinality' => $cardinality,
'bundles' => $allowed_bundles,
);
foreach ($fields as $field_name => $details) {
$this
->createField($field_name, $details['cardinality'], $details['type'], $details['widget'], $details['bundles']);
}
// Somehow clicking "save" isn't enough, and we have to do a
// node_types_rebuild().
node_types_rebuild();
menu_rebuild();
$type_exists = db_query('SELECT 1 FROM {node_type} WHERE type = :type', array(
':type' => $name,
))
->fetchField();
$this
->assertTrue($type_exists, "The new content type has been created in the database", "Setup");
}
/**
* Utility function to create fields on a content type/paragraph bundle.
*
* @param string $field_name
* Name of the field, like field_something.
* @param int $cardinality
* Cardinality.
* @param string $type
* Field type.
* @param string $widget
* Field widget.
* @param array $bundles
* The allowed bundles if it's a paragraph field.
*/
protected function createField($field_name, $cardinality, $type, $widget, array $bundles = array()) {
// Add a singleton field_example_text field.
$edit = array(
'fields[_add_new_field][label]' => $field_name,
'fields[_add_new_field][field_name]' => $field_name,
'fields[_add_new_field][type]' => $type,
'fields[_add_new_field][widget_type]' => $widget,
);
$this
->drupalPost(NULL, $edit, t('Save'));
// There are no settings for this, so just press the button.
$this
->drupalPost(NULL, array(), t('Save field settings'));
$edit = array(
'field[cardinality]' => (string) $cardinality,
);
if (isset($bundles) && count($bundles)) {
foreach ($bundles as $bundle) {
$edit['instance[settings][allowed_bundles_table][' . $bundle . '][enabled]'] = "1";
}
}
// Using all the default settings, so press the button.
$this
->drupalPost(NULL, $edit, t('Save settings'));
$message = format_string("Field @field added successfully", array(
"@field" => $field_name,
));
$this
->assertText(t('Saved @name configuration.', array(
'@name' => $field_name,
)), $message, "Setup");
}
/**
* Creates a feed importer.
*
* @param string $name
* The importer name.
* @param string $content_type
* The content type to attach to the importer.
*/
protected function addImporter($name, $content_type) {
$this
->drupalGet('admin/structure/feeds/create');
$edit = array(
'name' => $name,
'id' => $name,
);
$this
->drupalPost(NULL, $edit, t('Create'));
$message = format_string("Created importer with the name @name", array(
'@name' => $name,
));
$text = t("Your configuration has been created with default settings.");
$this
->assertText($text, $message, "Setup");
// Change the fetcher to the File upload fetcher (FeedsFileFetcher).
$this
->drupalGet('admin/structure/feeds/' . $name . '/fetcher');
$edit = array(
'plugin_key' => 'FeedsFileFetcher',
);
$this
->drupalPost(NULL, $edit, t('Save'));
$message = "Changed the fetcher to the file fetcher";
$text = t("Changed fetcher plugin.");
$this
->assertText($text, $message, "Setup");
// Change the parser to CSV parser (FeedsCSVParser).
$this
->drupalGet('admin/structure/feeds/' . $name . '/parser');
$edit = array(
'plugin_key' => 'FeedsCSVParser',
);
$this
->drupalPost(NULL, $edit, t('Save'));
$message = "Changed the parser to the CSV Parser";
$text = t("Changed parser plugin.");
$this
->assertText($text, $message, "Setup");
// Make sure the node processor is selected.
$this
->drupalGet('admin/structure/feeds/' . $name . '/processor');
$edit = array(
'plugin_key' => 'FeedsNodeProcessor',
);
$this
->drupalPost(NULL, $edit, t('Save'));
$message = "Changed the processor to Node Processor";
$text = t("Changed processor plugin.");
$this
->assertText($text, $message, "Setup");
// Change the bundle to the $content_type.
$this
->drupalGet('admin/structure/feeds/' . $name . '/settings/FeedsNodeProcessor');
$edit = array(
'bundle' => $content_type,
'update_existing' => '2',
'skip_hash_check' => '1',
);
$this
->drupalPost(NULL, $edit, t('Save'));
$message = format_string("Selected @type content type as bundle for this importer", array(
'@type' => $content_type,
));
$text = t("Your changes have been saved.");
$this
->assertText($text, $message, "Setup");
}
/**
* Maps source fields to target fields.
*
* @param array $targets
* Contains the source and the target string names.
*/
protected function map(array $targets) {
$this
->drupalGet('admin/structure/feeds/' . $this->importer . '/mapping');
$targets = array_unique($targets);
$targets['id'] = "nid";
$config_key = 0;
$hasSettings = $this->multiValued && $this->multiValuedParagraph;
foreach ($targets as $source => $target) {
$edit = array(
'source' => $source,
'target' => $target,
);
$this
->drupalPost(NULL, $edit, t('Save'));
$this
->assertText(t("Your changes have been saved."));
$args = array(
'@source' => $source,
'@target' => $target,
);
$message = format_string("Mapped @source source to @target field", $args);
$this
->assertText(t("Mapping has been added."), $message, 'Mapping');
if ($target === 'nid') {
// Set the target as unique:
$edit = array(
"config[{$config_key}][settings][unique]" => "1",
);
}
elseif ($hasSettings) {
$edit = array(
"config[{$config_key}][settings][max_values]" => "2",
);
}
if ($target === 'nid' || $hasSettings) {
// Click the gear button so it shows the configuration form:
$button_name = "mapping_settings_edit_" . $config_key;
$this
->drupalPostAJAX(NULL, array(), $button_name);
// Click update button.
$button_name = "mapping_settings_update_" . $config_key;
$this
->drupalPostAJAX(NULL, $edit, $button_name);
// Save.
$this
->drupalPost(NULL, array(), t('Save'));
}
$config_key++;
}
}
/**
* Starts importing.
*
* - Maps a paragraph field to a source.
* - Imports node to a Paragraphs field.
*
* @param string $path
* The csv file path relative to the project root directory.
* @param bool $updating
* Whether we are updating nodes or creating new ones.
* @param bool $map
* Whether we should also map or just import.
*/
protected function import($path = NULL, $updating = FALSE, $map = TRUE) {
if ($map) {
// Map the id field:
$targets = array();
$multi = $this->multiValued || $this->multiValuedParagraph;
foreach ($this->bundles[0]['fields'] as $field) {
if ($multi) {
$targets = array_merge($targets, $field['mapping_multiple']);
}
else {
$targets = array_merge($targets, $field['mapping']);
}
}
$this
->map($targets);
if ($multi) {
foreach ($targets as $source => $target) {
$this
->addTamperPlugins($source);
}
}
}
// Import content:
$this
->drupalGet('import/' . $this->importer);
if (!isset($path)) {
$path = '/tests/assets/content.csv';
}
$file = $this
->getProjectPath() . $path;
$edit = array(
'files[feeds]' => $file,
);
$this
->drupalPost(NULL, $edit, "Import");
$text = $updating ? 'Updated 1 node.' : 'Created 1 node.';
$this
->assertText($text);
}
/**
* Check that the top host field exists on the created node.
*/
protected function topHostParagraphsFieldExists() {
$node = node_load(1, NULL, TRUE);
$host_field = "field_" . $this->paragraphField;
$host_field_exists = isset($node->{$host_field});
$message = "The host Paragraphs field exists on the node";
$this
->assertTrue($host_field_exists, $message, "Importing");
/*
Check if the host Paragraphs field has the id
of the created ParagraphsItemEntity.
*/
$lang = $node->language;
$id_exists = isset($node->{$host_field}[$lang][0]['value']);
$message = "The host Paragraphs field has the id of the created ParagraphsItemEntity";
$this
->assertTrue($id_exists, $message);
}
/**
* Check whether the top host (node) has Paragraphs entity.
*/
protected function topHostParagraphsEntityExists() {
$item = $this
->getTopHostParagraphsEntities();
$message = "The created paragraphs item entity found";
$this
->assertTrue(count($item) > 0, $message, 'Importing');
}
/**
* Gets the paragraphs items attached to the created node.
*
* @return array
* The attached items array.
*/
protected function getTopHostParagraphsEntities() {
$node = node_load(1);
$lang = $node->language;
$host_field = 'field_' . $this->paragraphField;
$items = array();
foreach ($node->{$host_field}[$lang] as $val) {
$id = array(
$val['value'],
);
$item = entity_load('paragraphs_item', $id, array(), TRUE);
$item = reset($item);
$items[] = $item;
}
return $items;
}
/**
* Checks if the Paragraphs fields are nested as expected.
*/
protected function isNestedCorrectly() {
krsort($this->bundles);
$this->bundles = array_values($this->bundles);
foreach ($this->bundles as $key => $bundle) {
foreach ($bundle['fields'] as $field) {
$field = 'field_' . $field['name'];
$instance = field_info_instance('paragraphs_item', $field, $bundle['name']);
// If we have another bundle, it should have a Paragraphs field,
// otherwise it should contain a text field:
if (isset($this->bundles[$key + 1])) {
$is_paragraph = $instance['widget']['module'] === "paragraphs";
$message = format_string("The nested field @field is paragraph", array(
"@field" => $field,
));
$this
->assertTrue($is_paragraph, $message, 'Setup');
if (isset($instance['settings']['allowed_bundles'])) {
$allowed = $instance['settings']['allowed_bundles'];
$next = $this->bundles[$key + 1];
$has_next_bundle = $allowed[$next['name']] === $next['name'];
$message = format_string("the @field has the next nested bundle as allowed", array(
"@field" => $field,
));
$this
->assertTrue($has_next_bundle, $message, "Setup");
}
}
else {
$expected = array(
'text',
'image',
);
$field_type = $instance['widget']['module'];
$is_expected = in_array($field_type, $expected);
$message = format_string("the field type @type is expected", array(
"@type" => $field_type,
));
$this
->assertTrue($is_expected, $message, "Setup");
}
}
}
krsort($this->bundles);
$this->bundles = array_values($this->bundles);
}
/**
* Get the paragraph field values.
*
* @param \ParagraphsItemEntity $paragraph
* The paragraph entity.
* @param string $target
* The target field inside the paragraph entity.
* @param array $values
* The previously collected values to append to.
*
* @return array
* array of the values.
*/
protected function getValues(\ParagraphsItemEntity $paragraph, $target, array $values = array()) {
$target_info = field_info_field($target);
$node = node_load(1, NULL, TRUE);
$lang = $node->language;
if (isset($paragraph->{$target})) {
if ($target_info['type'] === 'paragraphs' && isset($paragraph->{$target}[$lang])) {
foreach ($paragraph->{$target}[$lang] as $item) {
if (isset($item['value'])) {
$id = array(
$item['value'],
);
$entity = entity_load('paragraphs_item', $id, array(), TRUE);
$values[] = reset($entity);
}
}
}
else {
if (isset($paragraph->{$target}[$lang])) {
foreach ($paragraph->{$target}[$lang] as $item) {
if ($target_info['type'] === 'text') {
$values[] = $item['value'];
}
else {
$values[] = $item;
}
}
}
}
}
else {
$keys = get_object_vars($paragraph);
foreach ($keys as $key) {
if (is_array($key) && isset($key[$lang]) && is_array($key[$lang])) {
foreach ($key[$lang] as $item) {
$item_id = (int) $item['value'];
if ($item_id) {
$en = entity_load('paragraphs_item', array(
$item_id,
), array(), TRUE);
$en = reset($en);
if ($en) {
$values = $this
->getValues($en, $target, $values);
}
}
}
}
}
}
return $values;
}
/**
* Tests Multi-valued fields importing.
*
* @param array $expected_values
* The values that the entities should have.
* @param bool $update
* Whether we are updating or creating new node.
*/
protected function multiImport(array $expected_values, $update = FALSE) {
$this
->import();
$this
->topHostParagraphsFieldExists();
$this
->topHostParagraphsEntityExists();
$entities_count = 2;
if ($update) {
$entities_count = 3;
$path = '/tests/assets/updated_content.csv';
$this
->import($path, TRUE, FALSE);
}
$items = $this
->getTopHostParagraphsEntities();
krsort($this->bundles);
$this->bundles = array_values($this->bundles);
// Check to see if the total of the created entities is correct:
$paragraphs = NULL;
if (count($this->bundles) > 1) {
$parent = 'field_' . $this->bundles[0]['fields'][0]['name'];
$paragraphs = $this
->getValues($items[0], $parent);
}
else {
$paragraphs = $items;
}
$message = "The created Paragraphs entities are 2";
$this
->assertEqual(count($paragraphs), $entities_count, $message, "Multi-valued Importing");
// Test the entities values:
$lastKey = count($this->bundles) - 1;
foreach ($this->bundles[$lastKey]['fields'] as $field) {
$machine_name = "field_" . $field['name'];
$values = array();
$values_count = array();
$total_count = 0;
foreach ($paragraphs as $paragraph) {
$p_values = $this
->getValues($paragraph, $machine_name);
$values_count[] = count($p_values);
$total_count += count($p_values);
$values = array_merge($values, $p_values);
}
// Test if each entity has total field values of 2:
if ($total_count !== count($expected_values[$machine_name])) {
for ($i = 0; $i < count($values_count); $i++) {
$item_num = $i + 1;
$message = format_string("Paragraph entity #@num has 2 values", array(
"@num" => $item_num,
));
$this
->assertEqual($values_count[$i], 2, $message, "Multi-valued Importing");
}
}
$found_values = array();
$expected_field_values = NULL;
foreach ($expected_values as $values_field => $valuesArr) {
if ($values_field === $machine_name) {
$expected_field_values = $valuesArr;
foreach ($valuesArr as $expected_value) {
foreach ($values as $value) {
if (is_array($expected_value)) {
$total_found = 0;
foreach ($expected_value as $subVal) {
if (in_array($subVal, $value)) {
$total_found++;
}
}
if (count($expected_value) === $total_found) {
$found_values[] = $expected_value;
}
}
elseif ($value === $expected_value) {
$found_values[] = $expected_value;
}
}
}
}
}
$message = "All field values are found";
$this
->assertEqual(count($found_values), count($expected_field_values), $message);
}
}
/**
* Add the needed Tamper plugins (explode) in a loop.
*
* In order to import multiple values to a field, we need to use Tamper.
*
* @param string $source
* The source name.
*/
protected function addTamperPlugins($source) {
if (!isset($this->createdPlugins)) {
$this->createdPlugins = array();
}
if ($this->multiValuedParagraph) {
$settings = array(
'settings[separator]' => '|',
);
$des = "Separate paragraphs";
$plugin = $this
->addTamperPlugin($this->importer, $source, 'explode', $des, $settings);
$this->createdPlugins[] = array(
'source' => $source,
'plugin' => $plugin,
);
}
if ($this->multiValued) {
$settings = array(
'settings[separator]' => ',',
);
$des = "Separate values";
$plugin = $this
->addTamperPlugin($this->importer, $source, 'explode', $des, $settings);
$this->createdPlugins[] = array(
'source' => $source,
'plugin' => $plugin,
);
}
}
/**
* Creates a Tamper plugin.
*
* @param string $importer
* The importer name.
* @param string $source
* The source name.
* @param string $plugin_id
* The plugin to add.
* @param string $description
* The plugin description.
* @param array $settings
* Configuration for the plugin (e.g The separator field value).
*
* @return string
* The plugin id.
*/
protected function addTamperPlugin($importer, $source, $plugin_id, $description, array $settings = array()) {
$url = "admin/structure/feeds/{$importer}/tamper/add/" . bin2hex($source);
$edit = array(
'plugin_id' => $plugin_id,
);
$this
->drupalPost($url, $edit, t('Choose'));
$id = str_replace(' ', '_', $description);
$id = strtolower($id);
$edit = array(
'plugin_id' => $plugin_id,
'description' => $description,
'id' => $id,
);
$edit = array_merge($edit, $settings);
$this
->drupalPost(NULL, $edit, t("Add"));
return $id;
}
/**
* Checks if all assets files exist.
*/
protected function assetsFilesExist() {
$root = $this
->getProjectPath();
$assets = array(
'content.csv' => $root . '/tests/assets/content.csv',
'updated_content.csv' => $root . '/tests/assets/updated_content.csv',
'image.png' => $root . '/tests/assets/image.png',
);
foreach ($assets as $name => $path) {
$exists = file_exists($path);
$message = format_string("Asset file @name exists", array(
"@name" => $name,
));
$this
->assertTrue($exists, $message, "Setup");
}
}
/**
* Copies a file.
*
* @param string $source
* The path of the source file.
* @param string $dest
* The path of the destination directory along with the file name.
*/
public function copyFile($source, $dest) {
$full_path = $this
->getProjectPath() . $source;
$file = file_unmanaged_copy($full_path, $dest);
$message = format_string("The file @source copied successfully", array(
'@source' => $source,
));
$this
->assertTrue(is_string($file), $message);
}
/**
* Gets the project path.
*
* @return string
* The path.
*/
public function getProjectPath() {
$root = realpath(getcwd()) . '/';
$path = $root . drupal_get_path('module', 'feeds_para_mapper');
return $path;
}
}
Classes
Name | Description |
---|---|
FeedsParaMapperWebTestCase | Test basic functionality via DrupalWebTestCase. |