contacts-8.x-1.x-dev/tests/src/FunctionalJavascript/ContactsDashboardJavascriptTest.php
tests/src/FunctionalJavascript/ContactsDashboardJavascriptTest.php
<?php
namespace Drupal\Tests\contacts\Functional;
use Drupal\Core\File\FileSystemInterface;
use Drupal\Core\Url;
use Drupal\FunctionalJavascriptTests\WebDriverTestBase;
use Drupal\decoupled_auth\Entity\DecoupledAuthUser;
use Drupal\file\Entity\File;
use Drupal\image\Entity\ImageStyle;
use Drupal\profile\Entity\Profile;
use Drupal\user\Entity\Role;
use Drupal\user\Entity\User;
/**
* Tests CRM fields and views.
*
* @group contacts
*/
class ContactsDashboardJavascriptTest extends WebDriverTestBase {
/**
* {@inheritdoc}
*/
protected static $modules = [
'contacts',
];
/**
* Testing staff user.
*
* @var \Drupal\user\Entity\User
*/
protected User $staffUser;
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'claro';
/**
* {@inheritdoc}
*/
protected $profile = 'standard';
/**
* {@inheritdoc}
*/
protected function setUp() : void {
parent::setUp();
$this->staffUser = $this->createUser();
$this->staffUser->addRole('crm_manager');
$this->staffUser->save();
}
/**
* Create a contact of the given type.
*
* @param string $type
* The type of contact, either 'crm_indiv' or 'crm_org'.
* @param bool $decoupled
* Whether the user should be decoupled. Defaults to TRUE.
*
* @return \Drupal\decoupled_auth\Entity\DecoupledAuthUser
* The contact that was created.
*/
protected function createContact($type, $decoupled = TRUE) {
// Create our user.
$name = $this->randomMachineName();
$contact = DecoupledAuthUser::create([
'name' => $decoupled ? NULL : $name,
'mail' => $name . '@example.com',
'status' => 1,
]);
$contact->addRole($type);
$contact->save();
// Generate a random image.
/** @var \Drupal\Core\File\FileSystemInterface $filesystem */
$filesystem = $this->container->get('file_system');
$tmp_file = $filesystem->tempnam('temporary://', 'contactImage_');
$destination = $tmp_file . '.png';
$filesystem->move($tmp_file, $destination, FileSystemInterface::CREATE_DIRECTORY);
$path = $this->getRandomGenerator()->image($filesystem->realpath($destination), '100x100', '100x100');
$image = File::create();
$image->setFileUri($path);
$image->setOwnerId($contact->id());
$image->setMimeType($this->container->get('file.mime_type.guesser')->guessMimeType($path));
$image->setFileName($filesystem->basename($path));
$destination = 'public://contactImage_' . $contact->id() . '.png';
$file = \Drupal::service('file.repository')->move($image, $destination, FileSystemInterface::CREATE_DIRECTORY);
// Build our profile.
switch ($type) {
case 'crm_indiv':
$values = [
'type' => 'crm_indiv',
'crm_name' => $this->randomString(20),
'crm_gender' => 'female',
'crm_address' => [
'country_code' => 'GB',
'locality' => $this->randomString(),
],
'crm_photo' => $file->id(),
];
break;
case 'crm_org':
$values = [
'type' => 'crm_org',
'crm_org_name' => $this->randomString(20),
'crm_org_address' => [
'country_code' => 'GB',
'locality' => $this->randomString(),
],
'crm_logo' => $file->id(),
];
break;
default:
return $contact;
}
$values += [
'uid' => $contact->id(),
'status' => 1,
'is_default' => 1,
];
$profile = Profile::create($values);
$profile->save();
// @todo Remove when onUpdate is added.
$contact->updateProfileFields([$type]);
return $contact;
}
/**
* Test installing contacts and accessing the contact dashboard.
*/
public function testViewDashboard() {
// Create some same users.
$contacts[] = DecoupledAuthUser::load(1);
$contacts[] = $this->staffUser;
$contacts[] = $this->createContact('crm_indiv');
$contacts[] = $this->createContact('crm_indiv', FALSE);
$contacts[] = $this->createContact('crm_org');
// Gain access to the contacts dashboard.
$this->drupalLogin($this->staffUser);
// Make sure our items are indexed.
$this->reIndex();
// All scenarios are in 1 test to prevent multiple drupal installs.
$this->checkContactsList($contacts);
$new_user_id = $this->checkCanAddContact();
$this->checkCanEditContact($new_user_id);
}
/**
* Checks a contact can be added.
*
* @return string
* ID of the added user.
*/
protected function checkCanAddContact() {
$this->assertSession();
$this->drupalGet('admin/contacts/all');
$this->drupalGet('admin/contacts/indiv');
$this->drupalGet('admin/contacts/org');
$this->drupalGet('admin/contacts');
$page = $this->getSession()->getPage();
$elem = $page->find('css', "a[href='/admin/contacts/add/indiv']");
$elem->click();
$this->screenshotOutput('add-click-add-person');
$first_name = $page->find('css', '#edit-crm-name-0-given');
$last_name = $page->find('css', '#edit-crm-name-0-family');
$email = $page->find('css', '#edit-contacts-mail');
$first_name->setValue('Test1');
$last_name->setValue('Test1');
$email->setValue('test1@test.local');
$this->screenshotOutput('add-fill-form');
$button = $page->find('css', "#edit-submit");
$button->click();
$this->screenshotOutput('submit-add-contact-form');
// Extract user id from url of newly created user.
$url = $this->getSession()->getCurrentUrl();
preg_match('#admin/contacts/([0-9]+)#', $url, $matches);
if (empty($matches[1])) {
$this->fail('Failed to get user ID from the url.');
}
/** @var \Drupal\user\Entity\User $user */
$user = \Drupal::entityTypeManager()->getStorage('user')
->loadUnchanged($matches[1]);
// Check they have the email & name that were entered above.
$this->assertEquals('test1@test.local', $user->getEmail());
$this->assertEquals('Test1', $user->profile_crm_indiv->entity->crm_name->given);
$this->assertEquals('Test1', $user->profile_crm_indiv->entity->crm_name->family);
return $matches[1];
}
/**
* Checks that a contact can be edited through the dashboard.
*
* @param string $user_id
* ID of user to edit.
*
* @throws \Behat\Mink\Exception\ElementNotFoundException
*/
protected function checkCanEditContact($user_id) {
$this->drupalGet('/admin/contacts/' . $user_id);
// Click the Individual profile tab.
$page = $this->getSession()->getPage();
// Find the edit button and click it.
$edit = $page->find('css', "a[href='/admin/contacts/{$user_id}?edit=indiv']");
$edit->click();
$this->assertSession()->assertWaitOnAjaxRequest();
$this->screenshotOutput('edit-click-edit');
// Update the first name (leave last name as is)
$page = $this->getSession()->getPage();
$first_name = $page->find('css', 'input[name="crm_name[0][given]"]');
$first_name->setValue('Test2');
$this->screenshotOutput('edit-fill-form');
// Submit the form.
$page->pressButton('Save');
$this->assertSession()->assertWaitOnAjaxRequest();
$this->screenshotOutput('edit-click-save');
// Validate the updated name appears in the page.
$text = $page->find('css', 'div.field--name-crm-name .field__item')->getText();
$this->assertEquals('Test2 Test1', $text);
}
/**
* Tests that the contacts list is shown.
*
* @param array $contacts
* Array of expected contacts.
*/
protected function checkContactsList(array $contacts) {
$session = $this->assertSession();
$this->drupalGet('admin/contacts/all');
// Sort our contacts.
usort($contacts, [ContactsDashboardTest::class, 'sortContacts']);
// Load our image style for building URLs.
$style = ImageStyle::load('contacts_small');
// Check our expected users are listed.
$index = 1;
foreach ($contacts as $contact) {
// Gather our relevant values.
$values = [];
// Get user roles.
$roles = Role::loadMultiple();
uasort($roles, 'contacts_sort_roles');
$roles = array_map(function ($item) {
return $item->label();
}, $roles);
$values['roles'] = implode(', ', array_intersect_key($roles, array_fill_keys($contact->getRoles(), TRUE)));
$values['email'] = $contact->getEmail();
$values['image'] = $contact->hasField('user_picture') && $contact->user_picture[0] ? $contact->user_picture[0]->entity->getFileUri() : FALSE;
$values['label'] = $contact->getDisplayName();
$values['is_visible'] = FALSE;
if ($contact->hasRole('crm_indiv')) {
$values['is_visible'] = TRUE;
$profile = $contact->profile_crm_indiv->entity;
$values['city'] = $profile->crm_address->locality;
if ($profile->crm_photo->target_id) {
$values['image'] = $profile->crm_photo->entity->getFileUri();
}
if (!$values['image']) {
$values['image'] = 'contacts://images/default-indiv.png';
}
}
elseif ($contact->hasRole('crm_org')) {
$values['is_visible'] = TRUE;
$profile = $contact->profile_crm_org->entity;
$values['city'] = $profile->crm_org_address->locality;
if (!$values['image']) {
$values['image'] = 'contacts://images/default-org.png';
}
}
else {
$values['city'] = FALSE;
if (!$values['image']) {
$values['image'] = 'contacts://images/default-indiv.png';
}
}
// Convert the image URI to a URL.
$values['image'] = \Drupal::service('file_url_generator')->generateString($style->buildUri($values['image']));
$values['url'] = Url::fromRoute('contacts.contact', [
'user' => $contact->id(),
])->toString();
// Check our row is correctly rendered.
$base_selector = "div.views-row:nth-of-type({$index}) ";
if (!$values['is_visible']) {
// Assert that contact is not visible and skip the rest.
// @todo Somehow check that contact is not present on page.
continue;
}
// Check our row link.
$session->elementAttributeContains('css', $base_selector, 'data-row-link', $values['url']);
// Check the image.
$session->elementAttributeContains('css', $base_selector . '.contacts-row-image a', 'href', $values['url']);
$session->elementAttributeContains('css', $base_selector . '.contacts-row-image img', 'src', $values['image']);
// Check the label.
if ($values['label']) {
$session->elementAttributeContains('css', $base_selector . '.contacts-row-main .contact-label a', 'href', $values['url']);
$session->elementTextContains('css', $base_selector . '.contacts-row-main .contact-label a', $values['label']);
}
else {
$session->elementNotExists('css', $base_selector . '.contacts-row-main .contact-label');
}
// Check the email.
if ($values['email']) {
$session->elementTextContains('css', $base_selector . '.contacts-row-main .contact-email', $values['email']);
}
else {
$session->elementNotExists('css', $base_selector . '.contacts-row-main .contact-email');
}
// Check the city.
if ($values['city']) {
$session->elementTextContains('css', $base_selector . '.contacts-row-main .contact-address', $values['city']);
}
else {
$session->elementNotExists('css', $base_selector . '.contacts-row-main .contact-address');
}
// Check the roles.
if ($values['roles']) {
$session->elementTextContains('css', $base_selector . '.contacts-row-main .contact-roles', $values['roles']);
}
else {
$session->elementNotExists('css', $base_selector . '.contacts-row-main .contact-roles');
}
// Check the ID.
$session->elementTextContains('css', $base_selector . '.contacts-row-supporting small.contact-id', 'ID: ' . $contact->id());
$index++;
}
}
/**
* Re-indexes contacts.
*/
protected function reIndex(): void {
/** @var \Drupal\search_api\IndexInterface $index */
$index = \Drupal::entityTypeManager()
->getStorage('search_api_index')
->load('contacts_index');
$index->indexItems();
}
/**
* {@inheritdoc}
*/
protected function htmlOutput($message = NULL) {
parent::htmlOutput($message);
if (!$this->htmlOutputEnabled) {
return;
}
// If getting the full browser output or the result of drupalGet, include a
// screenshot as well.
$caller = (new \Exception())->getTrace()[1];
$screenshot_callers = ['drupalGet', 'submitForm', 'click'];
if (!$message || in_array($caller['function'], $screenshot_callers)) {
$this->screenshotOutput();
}
}
/**
* Capture a screenshot of the current page and create a HTML output page.
*
* @param string $name
* The filename suffix and alt text for the screenshot.
*/
protected function screenshotOutput($name = NULL) {
if (!$this->htmlOutputEnabled) {
return;
}
// If we were given a name, use that as a label and filename identifier.
$output = '';
if ($name) {
$output .= "<h4>{$name}</h4>";
$filename = $this->htmlOutputClassName . '-' . $this->htmlOutputCounter . '-' . $this->htmlOutputTestId . '-' . $name . '.jpg';
}
else {
$filename = $this->htmlOutputClassName . '-' . $this->htmlOutputCounter . '-' . $this->htmlOutputTestId . '.jpg';
}
$this->createScreenshot($this->htmlOutputDirectory . '/' . $filename);
$this->htmlOutput($output . '<img src="' . $filename . '"/>');
}
}
