workbench_email-8.x-1.x-dev/tests/src/Functional/WorkbenchEmailTestBase.php
tests/src/Functional/WorkbenchEmailTestBase.php
<?php
namespace Drupal\Tests\workbench_email\Functional;
use Drupal\Core\Entity\Entity\EntityFormDisplay;
use Drupal\Core\Test\AssertMailTrait;
use Drupal\Core\Url;
use Drupal\field\Entity\FieldConfig;
use Drupal\field\Entity\FieldStorageConfig;
use Drupal\node\Entity\NodeType;
use Drupal\node\NodeInterface;
use Drupal\node\NodeTypeInterface;
use Drupal\Tests\block\Traits\BlockCreationTrait;
use Drupal\Tests\BrowserTestBase;
use Drupal\Tests\node\Traits\NodeCreationTrait;
use Drupal\workbench_email\Entity\Template;
/**
* Defines a base class for workbench email tests.
*/
abstract class WorkbenchEmailTestBase extends BrowserTestBase {
use AssertMailTrait;
use NodeCreationTrait;
use BlockCreationTrait;
/**
* The default theme.
*
* @var string
*/
protected $defaultTheme = 'stark';
/**
* Test node type.
*
* @var \Drupal\node\NodeTypeInterface
*/
protected $nodeType;
/**
* Approver role.
*
* @var \Drupal\user\RoleInterface
*/
protected $approverRole;
/**
* Editor role.
*
* @var \Drupal\user\RoleInterface
*/
protected $editorRole;
/**
* Approver 1.
*
* @var \Drupal\user\UserInterface
*/
protected $approver1;
/**
* Approver 2.
*
* @var \Drupal\user\UserInterface
*/
protected $approver2;
/**
* Approver 3 - blocked.
*
* @var \Drupal\user\UserInterface
*/
protected $approver3;
/**
* Approver 4 - no email address.
*
* @var \Drupal\user\UserInterface
*/
protected $approver4;
/**
* Editor.
*
* @var \Drupal\user\UserInterface
*/
protected $editor;
/**
* Admin.
*
* @var \Drupal\user\UserInterface
*/
protected $admin;
/**
* {@inheritdoc}
*/
protected static $modules = [
'workbench_email',
'workbench_email_test',
'node',
'options',
'user',
'system',
'filter',
'block',
'field',
];
/**
* {@inheritdoc}
*/
public function setUp(): void {
parent::setUp();
// Place some blocks.
$this->placeBlock('local_tasks_block', ['id' => 'tabs_block']);
$this->placeBlock('page_title_block');
$this->placeBlock('local_actions_block', ['id' => 'actions_block']);
// Create two node-types and make them moderated.
$this->nodeType = NodeType::create([
'type' => 'test',
'name' => 'Test',
]);
$this->setupModerationForNodeType($this->nodeType);
$this->nodeType = NodeType::create([
'type' => 'another',
'name' => 'Another Test',
]);
$this->setupModerationForNodeType($this->nodeType);
// Create an approver role and two users.
$this->approverRole = $this->drupalCreateRole($this->getApproverPermissions(), 'approver', 'Approver');
$this->approver1 = $this->drupalCreateUser();
$this->approver1->addRole('approver');
$this->approver1->save();
$this->approver2 = $this->drupalCreateUser();
$this->approver2->addRole('approver');
$this->approver2->save();
$this->approver3 = $this->drupalCreateUser();
$this->approver3->addRole('approver');
$this->approver3->block();
$this->approver3->save();
$this->approver4 = $this->drupalCreateUser();
$this->approver4->addRole('approver');
$this->approver4->setEmail(NULL);
$this->approver4->save();
// Create a editor role and user.
$this->editorRole = $this->drupalCreateRole($this->getEditorPermissions(), 'editor', 'Editor');
$this->editor = $this->drupalCreateUser();
$this->editor->addRole('editor');
$this->editor->save();
// Create an admin user.
$this->admin = $this->drupalCreateUser($this->getAdminPermissions());
// Add an email field notify to the node-type.
FieldStorageConfig::create([
'cardinality' => 1,
'entity_type' => 'node',
'field_name' => 'field_email',
'type' => 'email',
])->save();
FieldConfig::create([
'field_name' => 'field_email',
'bundle' => 'test',
'label' => 'Notify',
'entity_type' => 'node',
])->save();
if (!$entity_form_display = EntityFormDisplay::load('node.test.default')) {
$entity_form_display = EntityFormDisplay::create([
'targetEntityType' => 'node',
'bundle' => 'test',
'mode' => 'default',
'status' => TRUE,
]);
}
$entity_form_display->setComponent('field_email', ['type' => 'email_default'])
->save();
}
/**
* Enables moderation for a given node type.
*
* @param \Drupal\node\NodeTypeInterface $node_type
* Node type to enable moderation for.
*/
abstract protected function setupModerationForNodeType(NodeTypeInterface $nodeType);
/**
* Gets approver permissions.
*
* @return array
* Permission names.
*/
abstract protected function getApproverPermissions();
/**
* Gets editor permissions.
*
* @return array
* Permission names.
*/
abstract protected function getEditorPermissions();
/**
* Gets permisisons for admin user.
*
* @return array
* Permission names.
*/
abstract protected function getAdminPermissions();
/**
* Enables template for given transition or workflow.
*
* @return \Drupal\Core\Entity\EntityInterface
* Transition or workflow.
*/
abstract protected function enableTemplateForTransitionOrWorkflow($transition_name, $template_name);
/**
* Get submit button title text for transition.
*
* @return string
* Button title.
*/
abstract protected function getSubmitButtonTitleForTransition($from_state, $to_state);
/**
* Gets additional field data.
*
* @param string $from_state
* From state.
* @param string $to_state
* To state.
* @param string $node_type_id
* Node type.
*
* @return array
* Additional fields.
*/
abstract protected function getAdditionalFieldsForTransition($from_state, $to_state, string $node_type = 'test');
/**
* Test administration.
*/
public function testEndToEnd(): void {
// Create some templates as admin.
// - stuff got approved; and
// - stuff needs review.
$this->drupalLogin($this->admin);
$this->visitAdminMenuParent();
$page = $this->getSession()->getPage();
$page->clickLink('Email Templates');
$assert = $this->assertSession();
$this->assertEquals($this->getSession()->getCurrentUrl(), Url::fromUri('internal:/admin/structure/workbench-moderation/workbench-email-template')->setOption('absolute', TRUE)->toString());
$assert->pageTextContains('Email Template');
$page->clickLink('Add Email Template');
$this->submitForm([
'id' => 'approved',
'label' => 'Content approved',
'body[value]' => 'Content with [node:field_does_not_exist]title [node:title] was approved. You can view it at [node:url].',
'subject' => 'Content [node:field_does_not_exist]approved: [node:title][node:field_does_not_exist]',
'enabled_recipient_types[author]' => TRUE,
'enabled_recipient_types[email]' => TRUE,
'enabled_recipient_types[role]' => TRUE,
'recipient_types[email][settings][fields][node:field_email]' => TRUE,
'recipient_types[role][settings][roles][editor]' => TRUE,
], t('Save'));
$assert->pageTextContains('Created the Content approved Email Template');
$page->clickLink('Add Email Template');
$values = [
'id' => 'needs_review',
'label' => 'Content needs review',
'body[value]' => 'Content with [node:field_does_not_exist]title [node:title] needs review. You can view it at [node:url].[node:field_does_not_exist]',
'subject' => 'Content needs review',
'replyTo' => '[node:author:mail]',
'enabled_recipient_types[role]' => TRUE,
'recipient_types[role][settings][roles][approver]' => TRUE,
];
if (!\Drupal::moduleHandler()->moduleExists('content_moderation')) {
$values['bundles[node:test]'] = TRUE;
}
$this->submitForm($values, t('Save'));
$assert->pageTextContains('Created the Content needs review Email Template');
// Test dependencies.
$approver = Template::load('needs_review');
$dependencies = $approver->calculateDependencies()->getDependencies()['config'];
$this->assertTrue(in_array('user.role.approver', $dependencies, TRUE));
if (!\Drupal::moduleHandler()->moduleExists('content_moderation')) {
$this->assertTrue(in_array('node.type.test', $dependencies, TRUE));
}
$approver = Template::load('approved');
$dependencies = $approver->calculateDependencies()->getDependencies()['config'];
$this->assertTrue(in_array('field.storage.node.field_email', $dependencies, TRUE));
// Edit the template and test values persisted.
$page->clickLink('Content approved');
$assert->checkboxChecked('Notify (Content)');
$this->getSession()->back();
// Test editing a template.
$page->clickLink('Content needs review');
$assert->checkboxChecked('Approver', $page->find('css', '#edit-recipient-types-role-settings-roles--wrapper'));
$this->submitForm([
'label' => 'Content needs review',
'body[value]' => 'Content with[node:field_does_not_exist] title [node:title] needs review. You can view it at [node:url].[node:field_does_not_exist]',
'subject' => 'Content needs[node:field_does_not_exist] review: [node:title][node:field_does_not_exist]',
'replyTo' => '[node:author:mail]',
], t('Save'));
$assert->pageTextContains('Saved the Content needs review Email Template');
// Edit the transition from needs review to published and use the
// needs_review email template.
$transitionOrWorkflow = $this->enableTemplateForTransitionOrWorkflow('needs_review_published', 'approved');
$this->assertEquals($this->getExpectedThirdPartySetting('needs_review_published', 'approved'), $transitionOrWorkflow->getThirdPartySetting('workbench_email', 'workbench_email_templates'));
$dependencies = $transitionOrWorkflow->calculateDependencies()->getDependencies()['config'];
$this->assertTrue(in_array('workbench_email.workbench_email_template.approved', $dependencies, TRUE));
// Edit the transition from draft to needs review and add email config:
// approver template.
$this->enableTemplateForTransitionOrWorkflow('draft_needs_review', 'needs_review');
// Create a node and add to the notifier field.
$this->drupalLogin($this->editor);
$this->drupalGet('node/add/test');
$this->submitForm([
'title[0][value]' => 'Test node',
'field_email[0][value]' => 'foo@example.com',
] + $this->getAdditionalFieldsForTransition('draft', 'draft'), $this->getSubmitButtonTitleForTransition('draft', 'draft'));
$node = $this->getNodeByTitle('Test node');
// Transition to needs review.
$this->drupalGet('node/' . $node->id() . '/edit');
// Reset collected email.
$this->container->get('state')->set('system.test_mail_collector', []);
$this->submitForm([] + $this->getAdditionalFieldsForTransition('draft', 'needs_review'), $this->getSubmitButtonTitleForTransition('draft', 'needs_review'));
$this->assertNeedsReviewNotifications($node);
// Now try again going straight to needs review (no draft).
// Reset collected email.
$this->container->get('state')->set('system.test_mail_collector', []);
// Create a node and add to the notifier field.
$this->drupalGet('node/add/test');
$this->submitForm([
'title[0][value]' => 'Test node 2',
] + $this->getAdditionalFieldsForTransition('draft', 'needs_review'), $this->getSubmitButtonTitleForTransition('draft', 'needs_review'));
$node2 = $this->getNodeByTitle('Test node 2');
$this->assertNeedsReviewNotifications($node2);
// Login as approver and transition to approved.
$this->container->get('state')->set('system.test_mail_collector', []);
$this->drupalLogin($this->approver1);
$this->drupalGet('node/' . $node->id() . '/edit');
$this->submitForm([] + $this->getAdditionalFieldsForTransition('needs_review', 'published'), $this->getSubmitButtonTitleForTransition('needs_review', 'published'));
// Check mail goes to author and notifier.
$captured_emails = $this->container->get('state')->get('system.test_mail_collector') ?: [];
$last = end($captured_emails);
$prev = prev($captured_emails);
$mails = [$last['to'], $prev['to']];
sort($mails);
$expected = [$this->editor->getEmail(), 'foo@example.com'];
sort($expected);
$this->assertEquals($expected, $mails);
// The node id text is added to the email subject in the
// workbench_email_test_mail_alter() function.
// We check that it is set here.
$this->assertEquals(sprintf('Content approved: %s (node id: %s)', $node->getTitle(), $node->id()), $last['subject']);
$this->assertEquals(sprintf('Content approved: %s (node id: %s)', $node->getTitle(), $node->id()), $prev['subject']);
$this->assertStringContainsString(sprintf('Content with title %s was approved. You can view it at', $node->label()), preg_replace('/\s+/', ' ', $prev['body']));
$this->assertStringContainsString(sprintf('Content with title %s was approved. You can view it at', $node->label()), preg_replace('/\s+/', ' ', $last['body']));
// Check that empty tokens are removed.
$this->assertStringNotContainsString('[node:field_does_not_exist]', preg_replace('/\s+/', ' ', $prev['body']));
$this->assertStringNotContainsString('[node:field_does_not_exist]', preg_replace('/\s+/', ' ', $last['body']));
$this->assertStringContainsString($node->toUrl('canonical', ['absolute' => TRUE])->toString(), preg_replace('/\s+/', ' ', $prev['body']));
$this->assertStringContainsString($node->toUrl('canonical', ['absolute' => TRUE])->toString(), preg_replace('/\s+/', ' ', $last['body']));
// Test again with node that was previously published.
// Log back in as editor.
$this->drupalLogin($this->editor);
$this->drupalGet('node/' . $node->id() . '/edit');
$this->submitForm([] + $this->getAdditionalFieldsForTransition('published', 'draft'), $this->getSubmitButtonTitleForTransition('published', 'draft'));
// And now request a review.
$this->drupalGet('node/' . $node->id() . '/edit');
// Reset collected email.
$this->container->get('state')->set('system.test_mail_collector', []);
$this->submitForm([] + $this->getAdditionalFieldsForTransition('draft', 'needs_review'), $this->getSubmitButtonTitleForTransition('draft', 'needs_review'));
$this->assertNeedsReviewNotifications($node);
// Try with the other node type, that isn't enabled.
$this->container->get('state')->set('system.test_mail_collector', []);
$this->drupalGet('node/add/another');
$this->submitForm([
'title[0][value]' => 'Another test node',
]+ $this->getAdditionalFieldsForTransition('draft', 'draft', 'another'), $this->getSubmitButtonTitleForTransition('draft', 'draft'));
$node = $this->getNodeByTitle('Another test node');
// Transition to needs review.
$this->drupalGet('node/' . $node->id() . '/edit');
$this->submitForm([] + $this->getAdditionalFieldsForTransition('draft', 'needs_review', 'another'), $this->getSubmitButtonTitleForTransition('draft', 'needs_review'));
// No mail should be sent.
$captured_emails = $this->container->get('state')->get('system.test_mail_collector') ?: [];
$this->assertEmpty($captured_emails);
}
/**
* Visits admin parent menu link.
*/
abstract protected function visitAdminMenuParent();
/**
* Gets expected third party settings.
*
* @param string $transition_name
* Transition name.
* @param string $template
* Template ID.
*
* @return array
* Expected settings.
*/
abstract protected function getExpectedThirdPartySetting($transition_name, $template);
/**
* Assert notifications sent for needs review.
*
* @param \Drupal\node\NodeInterface $node
* Node updated.
*/
protected function assertNeedsReviewNotifications(NodeInterface $node): void {
// Check mail goes to approvers.
$captured_emails = $this->container->get('state')->get('system.test_mail_collector') ?: [];
// Should only be two emails.
$this->assertCount(2, $captured_emails);
$last = end($captured_emails);
$prev = prev($captured_emails);
$mails = [$last['to'], $prev['to']];
sort($mails);
$expected = [$this->approver1->getEmail(), $this->approver2->getEmail()];
sort($expected);
$this->assertEquals($expected, $mails);
// The node id text is added to the email subject in the
// workbench_email_test_mail_alter() function.
// We check that it is set here.
$this->assertEquals(sprintf('Content needs review: %s (node id: %s)', $node->label(), $node->id()), preg_replace('/\s+/', ' ', $last['subject']));
$this->assertEquals(sprintf('Content needs review: %s (node id: %s)', $node->label(), $node->id()), preg_replace('/\s+/', ' ', $prev['subject']));
$this->assertEquals($this->editor->getEmail(), $last['reply-to']);
$this->assertEquals($this->editor->getEmail(), $prev['reply-to']);
$this->assertStringContainsString(sprintf('Content with title %s needs review. You can view it at', $node->label()), preg_replace('/\s+/', ' ', $prev['body']));
$this->assertStringContainsString(sprintf('Content with title %s needs review. You can view it at', $node->label()), preg_replace('/\s+/', ' ', $last['body']));
// Check that empty tokens are removed.
$this->assertStringNotContainsString('[node:field_does_not_exist]', preg_replace('/\s+/', ' ', $prev['body']));
$this->assertStringNotContainsString('[node:field_does_not_exist]', preg_replace('/\s+/', ' ', $last['body']));
$this->assertStringContainsString($node->toUrl('canonical', ['absolute' => TRUE])->toString(), preg_replace('/\s+/', ' ', $prev['body']));
$this->assertStringContainsString($node->toUrl('canonical', ['absolute' => TRUE])->toString(), preg_replace('/\s+/', ' ', $last['body']));
}
}
