From 215b40afe72c4771eec124918d6681b51aab50c7 Mon Sep 17 00:00:00 2001 From: Jonathan Shaw Date: Thu, 13 Jul 2017 16:35:13 +0100 Subject: [PATCH 01/17] Initial emails commit --- behat.yml.dist | 1 + features/mail.feature | 51 +++ .../DrupalExtension/Context/MailContext.php | 326 ++++++++++++++++++ src/Drupal/DrupalMailManager.php | 95 +++++ src/Drupal/DrupalMailManagerInterface.php | 47 +++ 5 files changed, 520 insertions(+) create mode 100644 features/mail.feature create mode 100644 src/Drupal/DrupalExtension/Context/MailContext.php create mode 100644 src/Drupal/DrupalMailManager.php create mode 100644 src/Drupal/DrupalMailManagerInterface.php diff --git a/behat.yml.dist b/behat.yml.dist index eb9bcce0..7b77794c 100644 --- a/behat.yml.dist +++ b/behat.yml.dist @@ -114,6 +114,7 @@ drupal8: - Drupal\DrupalExtension\Context\MinkContext - Drupal\DrupalExtension\Context\MarkupContext - Drupal\DrupalExtension\Context\MessageContext + - Drupal\DrupalExtension\Context\MailContext filters: tags: "@d8&&~@d8wip" extensions: diff --git a/features/mail.feature b/features/mail.feature new file mode 100644 index 00000000..ac4c7db5 --- /dev/null +++ b/features/mail.feature @@ -0,0 +1,51 @@ +@api @d8 +Feature: MailContext + In order to prove the Mail context is working properly + As a developer + I need to use the step definitions of this context + + Scenario: Mail is sent + When Drupal sends an email: + | to | fred@example.com | + | subject | test | + | body | test body | + And Drupal sends a mail: + | to | jane@example.com | + | subject | test | + | body | test body 2 | + Then mails have been sent: + | to | subject | body | + | fred | | test body | + | jane | test | body 2 | + When Drupal sends a mail: + | to | jack@example.com | + | subject | test | + | body | test body with many words | + Then new email is sent: + | to | subject | body | body | + | jack | test | test | many words | + + Scenario: Mail is sent to + When Drupal sends a mail: + | to | fred@example.com | + | subject | test 1 | + And Drupal sends a mail: + | to | jane@example.com | + | subject | test 2 | + Then new mail is sent to fred: + | subject | + | test 1 | + And mail has been sent to "jane@example.com": + | subject | + | test 2 | + + Scenario: No mail is sent + Then no mail has been sent + + Scenario: I follow link in mail + When Drupal sends a mail: + | to | fred@example.com | + | subject | test link | + | body | A link to Google: http://www.Google.com | + And I follow the link to "google" from the mail with the subject "test link" + Then I should see "Search" \ No newline at end of file diff --git a/src/Drupal/DrupalExtension/Context/MailContext.php b/src/Drupal/DrupalExtension/Context/MailContext.php new file mode 100644 index 00000000..7b224d1f --- /dev/null +++ b/src/Drupal/DrupalExtension/Context/MailContext.php @@ -0,0 +1,326 @@ +mailManager)) { + $this->mailManager = new DrupalMailManager($this->getDriver()); + } + return $this->mailManager; + } + + /** + * Get collected mail, matching certain specifications. + * + * @param array $matches + * Associative array of mail fields and the values to filter by. + * @param bool $new + * Whether to ignore previously seen mail. + * @param null|int $index + * A particular mail to return, e.g. 0 for first or -1 for last. + * @param string $store + * The name of the mail store to get mail from. + * + * @return \stdClass[] + * An array of mail, each formatted as a Drupal 8 + * \Drupal\Core\Mail\MailInterface::mail $message array. + */ + protected function getMail($matches = [], $new = FALSE, $index = NULL, $store = 'default') { + $mail = $this->getMailManager()->getMail($store); + $previousMailCount = $this->getMailCount($store); + $this->mailCount[$store] = count($mail); + + // Ignore previously seen mail. + if ($new) { + $mail = array_slice($mail, $previousMailCount); + } + + // Filter mail based on $matches; keep only mail where each field mentioned + // in $matches contains the value specified for that field. + $mail = array_values(array_filter($mail, function ($singleMail) use ($matches) { + return ($this->matchesMail($singleMail, $matches)); + })); + + // Return an individual mail if specified by an index. + if (is_null($index)) { + return $mail; + } + else { + return array_slice($mail, $index, 1)[0]; + } + } + + /** + * Get the number of mails received in a particular mail store. + * + * @return int + * The number of mails received during this scenario. + */ + protected function getMailCount($store) { + if (array_key_exists($store, $this->mailCount)) { + $count = $this->mailCount[$store]; + } + else { + $count = 0; + } + return $count; + } + + /** + * Determine if a mail meets criteria. + * + * @param array $mail + * The mail, as an array of mail fields. + * @param array $matches + * The criteria: an associative array of mail fields and desired values. + * + * @return bool + * Whether the mail matches the criteria. + */ + protected function matchesMail($mail = [], $matches = []) { + // Discard criteria that are just zero-length strings. + $matches = array_filter($matches, 'strlen'); + // For each criteria, check the specified mail field contains the value. + foreach($matches as $field => $value) { + if (strpos($mail[$field], $value) === FALSE) { + return FALSE; + } + } + return TRUE; + } + + /** + * Compare actual mail with expected mail. + * + * @param array $actualMail + * An array of actual mail. + * @param array $expectedMail + * An array of expected mail. + */ + protected function compareMail($actualMail, $expectedMail) { + // Make sure there is the same number of actual and expected + $actualCount = count($actualMail); + $expectedCount = count($expectedMail); + if ($expectedCount !== $actualCount) { + throw new \Exception(sprintf('%s mail expected, but %s found.', $expectedCount, $actualCount)); + } + + // For each row of expected mail, check the corresponding actual mail. + foreach ($expectedMail as $index => $expectedMailItem) { + // For each column of the expected, check the field of the actual mail. + foreach ($expectedMailItem as $fieldName => $fieldValue) { + $expectedField = [$fieldName => $fieldValue]; + $match = $this->matchesMail($actualMail[$index], $expectedField); + if (!$match) { + throw new \Exception(sprintf('The #%s mail did not have %s in its %s field. ', $index, $fieldName, $fieldValue)); + } + } + } + } + + /** + * Get the mink context, so we can visit pages using the mink session. + */ + protected function getMinkContext() { + $minkContext = $this->getContext('\Behat\MinkExtension\Context\RawMinkContext'); + if ($minkContext === FALSE) { + throw new \Exception(sprintf('No mink context found.')); + } + return $minkContext; + } + + /** + * By default, prevent mail from being actually sent out during tests. + * + * @BeforeScenario + */ + public function disableMail() { + $this->getMailManager()->disableMail(); + // Always reset mail count, in case the default mail manager is being used + // which enables mail collecting automatically when mail is disabled, making + //the use of the @mail tag optional in this case. + $this->mailCount = []; + } + + /** + * Restore mail sending. + * + * @AfterScenario + */ + public function enableMail() { + $this->getMailManager()->enableMail(); + } + + /** + * Allow opting in to actually sending mail out. + * + * @BeforeScenario @sendmail @sendemail + */ + public function sendMail() { + $this->getMailManager()->enableMail(); + } + + /** + * Allow opting in to mail collection. When using the default mail manager + * service, it is not necessary to use this tag. + * + * @BeforeScenario @mail @email + */ + public function collectMail() { + $this->getMailManager()->startCollectingMail(); + } + + /** + * Stop collecting mail at scenario end. + * + * @AfterScenario @mail @email + */ + public function stopCollectingMail() { + $this->getMailManager()->stopCollectingMail(); + } + + /** + * This is mainly useful for testing this context. + * + * @When Drupal sends a/an (e)mail: + */ + public function DrupalSendsMail(TableNode $fields) { + $to = $this->getRandom()->name(10) . '@anonexample.com'; + if ($this->getUserManager()->getCurrentUser() && $currentMail = $this->getUserManager()->getCurrentUser()->mail) { + $to = $currentMail; + } + $mail = [ + 'body' => $this->getRandom()->name(255), + 'subject' => $this->getRandom()->name(20), + 'to' => $to, + 'langcode' => '', + ]; + foreach ($fields->getRowsHash() as $field => $value) { + $mail[$field] = $value; + } + $this->getDriver()->sendMail($mail['body'], $mail['subject'], $mail['to'], $mail['langcode']); + } + + /** + * Check all mail sent during the scenario. + * + * @Then (e)mail(s) has/have been sent: + * @Then (e)mail(s) has/have been sent to :to: + */ + public function mailHasBeenSent(TableNode $expectedMailTable, $to = NULL) { + $expectedMail = $expectedMailTable->getHash(); + $matches = []; + if (!is_null($to)) { + $matches = ['to' => $to]; + } + $actualMail = $this->getMail($matches); + $this->compareMail($actualMail, $expectedMail); + } + + /** + * Check mail sent since the last step that checked mail. + * + * @Then new (e)mail(s) is/are sent: + * @Then new (e)mail(s) is/are sent to :to: + */ + public function newMailIsSent(TableNode $expectedMailTable, $to = NULL) { + $expectedMail = $expectedMailTable->getHash(); + $matches = []; + if (!is_null($to)) { + $matches = ['to' => $to]; + } + $actualMail = $this->getMail($matches, TRUE); + $this->compareMail($actualMail, $expectedMail); + } + + /** + * Check all mail sent during the scenario. + * + * @Then no (e)mail(s) has/have been sent + * @Then no (e)mail(s) has/have been sent to :to + */ + public function noMailHasBeenSent($to = NULL) { + $matches = []; + if (!is_null($to)) { + $matches = ['to' => $to]; + } + $actualMail = $this->getMail($matches); + $this->compareMail($actualMail, []); + } + + /** + * Check mail sent since the last step that checked mail. + * + * @Then no new (e)mail(s) is/are sent + * @Then no new (e)mail(s) is/are sent to :to + */ + public function noNewMailIsSent($to = NULL) { + $matches = []; + if (!is_null($to)) { + $matches = ['to' => $to]; + } + $actualMail = $this->getMail($matches, TRUE); + $this->compareMail($actualMail, []); + } + + /** + * @When I follow the link to :urlFragment from the (e)mail + * @When I follow the link to :urlFragment from the (e)mail to :to + * @When I follow the link to :urlFragment from the (e)mail with the subject :subject + * @When I follow the link to :urlFragment from the (e)mail to :to with the subject :subject + */ + public function followLinkInMail($urlFragment, $to = '', $subject = '') { + // Get the mail + $matches = ['to' => $to, 'subject' => $subject]; + $mail = $this->getMail($matches, FALSE, -1); + $body = $mail['body']; + + // Find web URLs in the mail + $urlPattern = '`.*?((http|https)://[\w#$&+,\/:;=?@.-]+)[^\w#$&+,\/:;=?@.-]*?`i'; + if (preg_match_all($urlPattern, $body, $urls)) { + // Visit the first url that contains the desired fragment. + foreach ($urls[1] as $url) { + $match = (strpos(strtolower($url), strtolower($urlFragment)) !== FALSE); + if ($match) { + $this->getMinkContext()->visitPath($url); + return; + } + } + throw new \Exception(sprintf('No URL in mail body contained "%s".', $urlFragment)); + } + else { + throw new \Exception('No URL found in mail body.'); + } + } + +} diff --git a/src/Drupal/DrupalMailManager.php b/src/Drupal/DrupalMailManager.php new file mode 100644 index 00000000..5bd6acdd --- /dev/null +++ b/src/Drupal/DrupalMailManager.php @@ -0,0 +1,95 @@ +driver = $driver; + } + + /** + * Replace the initial mail backend with the test mail backend. + */ + protected function enableTestMailBackend() { + if (is_null($this->initialMailBackend)) { + $this->initialMailBackend = $this->driver->getMailBackend(); + } + // @todo Use a collector that supports html after D#2223967 lands. + $this->driver->setMailBackend('test_mail_collector'); + } + + /** + * Restore the initial mail backend. + */ + protected function restoreInitialMailBackend() { + $this->driver->setMailBackend($this->initialMailBackend); + } + + /** + * {@inheritdoc} + */ + public function startCollectingMail() { + $this->enableTestMailBackend(); + $this->clearMail(); + } + + /** + * {@inheritdoc} + */ + public function stopCollectingMail() { + $this->restoreInitialMailBackend(); + } + + /** + * {@inheritdoc} + */ + public function enableMail() { + $this->restoreInitialMailBackend(); + } + + /** + * {@inheritdoc} + */ + public function disableMail() { + $this->startCollectingMail(); + } + + /** + * {@inheritdoc} + */ + public function getMail($store = 'default') { + return $this->driver->getMail(); + } + + /** + * {@inheritdoc} + */ + public function clearMail($store = 'default') { + $this->driver->clearMail(); + } + +} diff --git a/src/Drupal/DrupalMailManagerInterface.php b/src/Drupal/DrupalMailManagerInterface.php new file mode 100644 index 00000000..d9ac7d27 --- /dev/null +++ b/src/Drupal/DrupalMailManagerInterface.php @@ -0,0 +1,47 @@ + Date: Fri, 14 Jul 2017 16:36:40 +0100 Subject: [PATCH 02/17] Make sending mail not require userManager --- src/Drupal/DrupalExtension/Context/MailContext.php | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/Drupal/DrupalExtension/Context/MailContext.php b/src/Drupal/DrupalExtension/Context/MailContext.php index 7b224d1f..9bcab7d6 100644 --- a/src/Drupal/DrupalExtension/Context/MailContext.php +++ b/src/Drupal/DrupalExtension/Context/MailContext.php @@ -215,14 +215,10 @@ public function stopCollectingMail() { * @When Drupal sends a/an (e)mail: */ public function DrupalSendsMail(TableNode $fields) { - $to = $this->getRandom()->name(10) . '@anonexample.com'; - if ($this->getUserManager()->getCurrentUser() && $currentMail = $this->getUserManager()->getCurrentUser()->mail) { - $to = $currentMail; - } $mail = [ 'body' => $this->getRandom()->name(255), 'subject' => $this->getRandom()->name(20), - 'to' => $to, + 'to' => $this->getRandom()->name(10) . '@anonexample.com';, 'langcode' => '', ]; foreach ($fields->getRowsHash() as $field => $value) { From 64a7f66e96978fd3dcc0e71e2c8cff436df2f21a Mon Sep 17 00:00:00 2001 From: jonathanjfshaw Date: Fri, 14 Jul 2017 18:56:42 +0100 Subject: [PATCH 03/17] Remove semicolon --- src/Drupal/DrupalExtension/Context/MailContext.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Drupal/DrupalExtension/Context/MailContext.php b/src/Drupal/DrupalExtension/Context/MailContext.php index 9bcab7d6..e496f21d 100644 --- a/src/Drupal/DrupalExtension/Context/MailContext.php +++ b/src/Drupal/DrupalExtension/Context/MailContext.php @@ -218,7 +218,7 @@ public function DrupalSendsMail(TableNode $fields) { $mail = [ 'body' => $this->getRandom()->name(255), 'subject' => $this->getRandom()->name(20), - 'to' => $this->getRandom()->name(10) . '@anonexample.com';, + 'to' => $this->getRandom()->name(10) . '@anonexample.com', 'langcode' => '', ]; foreach ($fields->getRowsHash() as $field => $value) { From a3c3e82dac9bea4b60431e581ff576b9c0d7698e Mon Sep 17 00:00:00 2001 From: jonathanjfshaw Date: Mon, 17 Jul 2017 17:18:09 +0100 Subject: [PATCH 04/17] Mail comparison insensitive to order of sending --- .../DrupalExtension/Context/MailContext.php | 50 +++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/src/Drupal/DrupalExtension/Context/MailContext.php b/src/Drupal/DrupalExtension/Context/MailContext.php index e496f21d..6114c727 100644 --- a/src/Drupal/DrupalExtension/Context/MailContext.php +++ b/src/Drupal/DrupalExtension/Context/MailContext.php @@ -136,6 +136,9 @@ protected function compareMail($actualMail, $expectedMail) { } // For each row of expected mail, check the corresponding actual mail. + // Make the comparison insensitive to the order mails were sent. + $actualMail = $this->sortMail($actualMail); + $expectedMail = $this->sortMail($expectedMail); foreach ($expectedMail as $index => $expectedMailItem) { // For each column of the expected, check the field of the actual mail. foreach ($expectedMailItem as $fieldName => $fieldValue) { @@ -148,6 +151,53 @@ protected function compareMail($actualMail, $expectedMail) { } } + /** + * Sort mail by to, subject and body. + * + * @param array $mail + * An array of mail to sort. + * + * @return array + * The same mail, but sorted. + */ + protected function sortMail($mail) { + // Can't sort an empty array. + if (count($mail) === 0) { + return []; + } + + // To, subject and body keys must be present. + // Empty strings are ignored when matching so adding them is harmless. + foreach ($mail as $key => $row) { + if (!array_key_exists('to',$row)) { + $mail[$key]['to'] = ''; + } + if (!array_key_exists('subject',$row)) { + $mail[$key]['subject'] = ''; + } + if (!array_key_exists('body',$row)) { + $mail[$key]['body'] = ''; + } + } + + // Obtain a list of columns. + foreach ($mail as $key => $row) { + if (array_key_exists('to',$row)) { + $to[$key] = $row['to']; + } + if (array_key_exists('subject',$row)) { + $subject[$key] = $row['subject']; + } + if (array_key_exists('body',$row)) { + $body[$key] = $row['body']; + } + } + + // Add $mail as the last parameter, to sort by the common key. + array_multisort($to, SORT_ASC, $subject, SORT_ASC, $body, SORT_ASC, $mail); + return $mail; + } + /** * Get the mink context, so we can visit pages using the mink session. */ From 28fe2cd0103ff5aa694868de0059fc6d514c70d8 Mon Sep 17 00:00:00 2001 From: jonathanjfshaw Date: Mon, 17 Jul 2017 17:19:21 +0100 Subject: [PATCH 05/17] Test mail order insensitivity --- features/mail.feature | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/features/mail.feature b/features/mail.feature index ac4c7db5..dacacdfc 100644 --- a/features/mail.feature +++ b/features/mail.feature @@ -48,4 +48,19 @@ Feature: MailContext | subject | test link | | body | A link to Google: http://www.Google.com | And I follow the link to "google" from the mail with the subject "test link" - Then I should see "Search" \ No newline at end of file + Then I should see "Search" + + + Scenario: We try to be order insensitive + When Drupal sends an email: + | to | fred@example.com | + | subject | test | + | body | test body | + And Drupal sends a mail: + | to | jane@example.com | + | subject | test | + | body | test body 2 | + Then mails have been sent: + | to | subject | body | + | jane | test | body 2 | + | fred | | test body | From 717a6a3ed2d0d1df0f1cf8aaadfa5e102fb11d30 Mon Sep 17 00:00:00 2001 From: jonathanjfshaw Date: Mon, 17 Jul 2017 17:21:59 +0100 Subject: [PATCH 06/17] Case insensitive text comparison, better errors --- src/Drupal/DrupalExtension/Context/MailContext.php | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/Drupal/DrupalExtension/Context/MailContext.php b/src/Drupal/DrupalExtension/Context/MailContext.php index 6114c727..23590973 100644 --- a/src/Drupal/DrupalExtension/Context/MailContext.php +++ b/src/Drupal/DrupalExtension/Context/MailContext.php @@ -112,7 +112,8 @@ protected function matchesMail($mail = [], $matches = []) { $matches = array_filter($matches, 'strlen'); // For each criteria, check the specified mail field contains the value. foreach($matches as $field => $value) { - if (strpos($mail[$field], $value) === FALSE) { + // Case insensitive. + if (stripos($mail[$field], $value) === FALSE) { return FALSE; } } @@ -132,7 +133,11 @@ protected function compareMail($actualMail, $expectedMail) { $actualCount = count($actualMail); $expectedCount = count($expectedMail); if ($expectedCount !== $actualCount) { - throw new \Exception(sprintf('%s mail expected, but %s found.', $expectedCount, $actualCount)); + $prettyActualMail = []; + foreach ($actualMail as $singleActualMail) { + $prettyActualMail[] = ['to' => $singleActualMail['to'],'subject' => $singleActualMail['subject'],]; + } + throw new \Exception(sprintf("%s mail expected, but %s found:\n\n%s", $expectedCount, $actualCount, print_r($prettyActualMail, TRUE))); } // For each row of expected mail, check the corresponding actual mail. @@ -145,7 +150,7 @@ protected function compareMail($actualMail, $expectedMail) { $expectedField = [$fieldName => $fieldValue]; $match = $this->matchesMail($actualMail[$index], $expectedField); if (!$match) { - throw new \Exception(sprintf('The #%s mail did not have %s in its %s field. ', $index, $fieldName, $fieldValue)); + throw new \Exception(sprintf("The #%s mail did not have '%s' in its %s field. It had:\n%s", $index, $fieldValue, $fieldName, mb_strimwidth($actualMail[$index][$fieldName],0, 30, "..."))); } } } From 91b8c82f4c15ec0a2cda78e55ea10ad038a07194 Mon Sep 17 00:00:00 2001 From: jonathanjfshaw Date: Tue, 18 Jul 2017 16:03:59 +0100 Subject: [PATCH 07/17] Quote errors --- src/Drupal/DrupalExtension/Context/MailContext.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Drupal/DrupalExtension/Context/MailContext.php b/src/Drupal/DrupalExtension/Context/MailContext.php index 23590973..9281efd0 100644 --- a/src/Drupal/DrupalExtension/Context/MailContext.php +++ b/src/Drupal/DrupalExtension/Context/MailContext.php @@ -150,7 +150,7 @@ protected function compareMail($actualMail, $expectedMail) { $expectedField = [$fieldName => $fieldValue]; $match = $this->matchesMail($actualMail[$index], $expectedField); if (!$match) { - throw new \Exception(sprintf("The #%s mail did not have '%s' in its %s field. It had:\n%s", $index, $fieldValue, $fieldName, mb_strimwidth($actualMail[$index][$fieldName],0, 30, "..."))); + throw new \Exception(sprintf("The #%s mail did not have '%s' in its %s field. It had:\n'%s'", $index, $fieldValue, $fieldName, mb_strimwidth($actualMail[$index][$fieldName],0, 30, "..."))); } } } From b35b0a05d4dc8ac69b233a72f85253cd16e9e0e1 Mon Sep 17 00:00:00 2001 From: Jonathan Shaw Date: Wed, 19 Jul 2017 13:55:26 +0100 Subject: [PATCH 08/17] create RawMailContext --- .../DrupalExtension/Context/MailContext.php | 209 +---------------- .../Context/RawMailContext.php | 216 ++++++++++++++++++ 2 files changed, 218 insertions(+), 207 deletions(-) create mode 100644 src/Drupal/DrupalExtension/Context/RawMailContext.php diff --git a/src/Drupal/DrupalExtension/Context/MailContext.php b/src/Drupal/DrupalExtension/Context/MailContext.php index 9281efd0..b7edf032 100644 --- a/src/Drupal/DrupalExtension/Context/MailContext.php +++ b/src/Drupal/DrupalExtension/Context/MailContext.php @@ -2,217 +2,12 @@ namespace Drupal\DrupalExtension\Context; -use Drupal\DrupalMailManager; use Behat\Gherkin\Node\TableNode; /** - * Provides pre-built step definitions for interacting with Drupal. + * Provides pre-built step definitions for interacting with mail. */ -class MailContext extends RawDrupalContext { - - /** - * The mail manager. - * - * @var \Drupal\DrupalMailManagerInterface - */ - protected $mailManager; - - /** - * The number of mails received so far in this scenario, for each mail store. - * - * @var array - */ - protected $mailCount = []; - - /** - * Get the mail manager service that handles stored test mail. - * - * @return \Drupal\DrupalMailManagerInterface - * The mail manager service. - */ - protected function getMailManager() { - // Persist the mail manager between invocations. This is necessary for - // remembering and reinstating the original mail backend. - if (is_null($this->mailManager)) { - $this->mailManager = new DrupalMailManager($this->getDriver()); - } - return $this->mailManager; - } - - /** - * Get collected mail, matching certain specifications. - * - * @param array $matches - * Associative array of mail fields and the values to filter by. - * @param bool $new - * Whether to ignore previously seen mail. - * @param null|int $index - * A particular mail to return, e.g. 0 for first or -1 for last. - * @param string $store - * The name of the mail store to get mail from. - * - * @return \stdClass[] - * An array of mail, each formatted as a Drupal 8 - * \Drupal\Core\Mail\MailInterface::mail $message array. - */ - protected function getMail($matches = [], $new = FALSE, $index = NULL, $store = 'default') { - $mail = $this->getMailManager()->getMail($store); - $previousMailCount = $this->getMailCount($store); - $this->mailCount[$store] = count($mail); - - // Ignore previously seen mail. - if ($new) { - $mail = array_slice($mail, $previousMailCount); - } - - // Filter mail based on $matches; keep only mail where each field mentioned - // in $matches contains the value specified for that field. - $mail = array_values(array_filter($mail, function ($singleMail) use ($matches) { - return ($this->matchesMail($singleMail, $matches)); - })); - - // Return an individual mail if specified by an index. - if (is_null($index)) { - return $mail; - } - else { - return array_slice($mail, $index, 1)[0]; - } - } - - /** - * Get the number of mails received in a particular mail store. - * - * @return int - * The number of mails received during this scenario. - */ - protected function getMailCount($store) { - if (array_key_exists($store, $this->mailCount)) { - $count = $this->mailCount[$store]; - } - else { - $count = 0; - } - return $count; - } - - /** - * Determine if a mail meets criteria. - * - * @param array $mail - * The mail, as an array of mail fields. - * @param array $matches - * The criteria: an associative array of mail fields and desired values. - * - * @return bool - * Whether the mail matches the criteria. - */ - protected function matchesMail($mail = [], $matches = []) { - // Discard criteria that are just zero-length strings. - $matches = array_filter($matches, 'strlen'); - // For each criteria, check the specified mail field contains the value. - foreach($matches as $field => $value) { - // Case insensitive. - if (stripos($mail[$field], $value) === FALSE) { - return FALSE; - } - } - return TRUE; - } - - /** - * Compare actual mail with expected mail. - * - * @param array $actualMail - * An array of actual mail. - * @param array $expectedMail - * An array of expected mail. - */ - protected function compareMail($actualMail, $expectedMail) { - // Make sure there is the same number of actual and expected - $actualCount = count($actualMail); - $expectedCount = count($expectedMail); - if ($expectedCount !== $actualCount) { - $prettyActualMail = []; - foreach ($actualMail as $singleActualMail) { - $prettyActualMail[] = ['to' => $singleActualMail['to'],'subject' => $singleActualMail['subject'],]; - } - throw new \Exception(sprintf("%s mail expected, but %s found:\n\n%s", $expectedCount, $actualCount, print_r($prettyActualMail, TRUE))); - } - - // For each row of expected mail, check the corresponding actual mail. - // Make the comparison insensitive to the order mails were sent. - $actualMail = $this->sortMail($actualMail); - $expectedMail = $this->sortMail($expectedMail); - foreach ($expectedMail as $index => $expectedMailItem) { - // For each column of the expected, check the field of the actual mail. - foreach ($expectedMailItem as $fieldName => $fieldValue) { - $expectedField = [$fieldName => $fieldValue]; - $match = $this->matchesMail($actualMail[$index], $expectedField); - if (!$match) { - throw new \Exception(sprintf("The #%s mail did not have '%s' in its %s field. It had:\n'%s'", $index, $fieldValue, $fieldName, mb_strimwidth($actualMail[$index][$fieldName],0, 30, "..."))); - } - } - } - } - - /** - * Sort mail by to, subject and body. - * - * @param array $mail - * An array of mail to sort. - * - * @return array - * The same mail, but sorted. - */ - protected function sortMail($mail) { - // Can't sort an empty array. - if (count($mail) === 0) { - return []; - } - - // To, subject and body keys must be present. - // Empty strings are ignored when matching so adding them is harmless. - foreach ($mail as $key => $row) { - if (!array_key_exists('to',$row)) { - $mail[$key]['to'] = ''; - } - if (!array_key_exists('subject',$row)) { - $mail[$key]['subject'] = ''; - } - if (!array_key_exists('body',$row)) { - $mail[$key]['body'] = ''; - } - } - - // Obtain a list of columns. - foreach ($mail as $key => $row) { - if (array_key_exists('to',$row)) { - $to[$key] = $row['to']; - } - if (array_key_exists('subject',$row)) { - $subject[$key] = $row['subject']; - } - if (array_key_exists('body',$row)) { - $body[$key] = $row['body']; - } - } - - // Add $mail as the last parameter, to sort by the common key. - array_multisort($to, SORT_ASC, $subject, SORT_ASC, $body, SORT_ASC, $mail); - return $mail; - } - - /** - * Get the mink context, so we can visit pages using the mink session. - */ - protected function getMinkContext() { - $minkContext = $this->getContext('\Behat\MinkExtension\Context\RawMinkContext'); - if ($minkContext === FALSE) { - throw new \Exception(sprintf('No mink context found.')); - } - return $minkContext; - } +class MailContext extends RawMailContext { /** * By default, prevent mail from being actually sent out during tests. diff --git a/src/Drupal/DrupalExtension/Context/RawMailContext.php b/src/Drupal/DrupalExtension/Context/RawMailContext.php new file mode 100644 index 00000000..2133a689 --- /dev/null +++ b/src/Drupal/DrupalExtension/Context/RawMailContext.php @@ -0,0 +1,216 @@ +mailManager)) { + $this->mailManager = new DrupalMailManager($this->getDriver()); + } + return $this->mailManager; + } + + /** + * Get collected mail, matching certain specifications. + * + * @param array $matches + * Associative array of mail fields and the values to filter by. + * @param bool $new + * Whether to ignore previously seen mail. + * @param null|int $index + * A particular mail to return, e.g. 0 for first or -1 for last. + * @param string $store + * The name of the mail store to get mail from. + * + * @return \stdClass[] + * An array of mail, each formatted as a Drupal 8 + * \Drupal\Core\Mail\MailInterface::mail $message array. + */ + protected function getMail($matches = [], $new = FALSE, $index = NULL, $store = 'default') { + $mail = $this->getMailManager()->getMail($store); + $previousMailCount = $this->getMailCount($store); + $this->mailCount[$store] = count($mail); + + // Ignore previously seen mail. + if ($new) { + $mail = array_slice($mail, $previousMailCount); + } + + // Filter mail based on $matches; keep only mail where each field mentioned + // in $matches contains the value specified for that field. + $mail = array_values(array_filter($mail, function ($singleMail) use ($matches) { + return ($this->matchesMail($singleMail, $matches)); + })); + + // Return an individual mail if specified by an index. + if (is_null($index)) { + return $mail; + } + else { + return array_slice($mail, $index, 1)[0]; + } + } + + /** + * Get the number of mails received in a particular mail store. + * + * @return int + * The number of mails received during this scenario. + */ + protected function getMailCount($store) { + if (array_key_exists($store, $this->mailCount)) { + $count = $this->mailCount[$store]; + } + else { + $count = 0; + } + return $count; + } + + /** + * Determine if a mail meets criteria. + * + * @param array $mail + * The mail, as an array of mail fields. + * @param array $matches + * The criteria: an associative array of mail fields and desired values. + * + * @return bool + * Whether the mail matches the criteria. + */ + protected function matchesMail($mail = [], $matches = []) { + // Discard criteria that are just zero-length strings. + $matches = array_filter($matches, 'strlen'); + // For each criteria, check the specified mail field contains the value. + foreach($matches as $field => $value) { + // Case insensitive. + if (stripos($mail[$field], $value) === FALSE) { + return FALSE; + } + } + return TRUE; + } + + /** + * Compare actual mail with expected mail. + * + * @param array $actualMail + * An array of actual mail. + * @param array $expectedMail + * An array of expected mail. + */ + protected function compareMail($actualMail, $expectedMail) { + // Make sure there is the same number of actual and expected + $actualCount = count($actualMail); + $expectedCount = count($expectedMail); + if ($expectedCount !== $actualCount) { + $prettyActualMail = []; + foreach ($actualMail as $singleActualMail) { + $prettyActualMail[] = ['to' => $singleActualMail['to'],'subject' => $singleActualMail['subject'],]; + } + throw new \Exception(sprintf("%s mail expected, but %s found:\n\n%s", $expectedCount, $actualCount, print_r($prettyActualMail, TRUE))); + } + + // For each row of expected mail, check the corresponding actual mail. + // Make the comparison insensitive to the order mails were sent. + $actualMail = $this->sortMail($actualMail); + $expectedMail = $this->sortMail($expectedMail); + foreach ($expectedMail as $index => $expectedMailItem) { + // For each column of the expected, check the field of the actual mail. + foreach ($expectedMailItem as $fieldName => $fieldValue) { + $expectedField = [$fieldName => $fieldValue]; + $match = $this->matchesMail($actualMail[$index], $expectedField); + if (!$match) { + throw new \Exception(sprintf("The #%s mail did not have '%s' in its %s field. It had:\n'%s'", $index, $fieldValue, $fieldName, mb_strimwidth($actualMail[$index][$fieldName],0, 30, "..."))); + } + } + } + } + + /** + * Sort mail by to, subject and body. + * + * @param array $mail + * An array of mail to sort. + * + * @return array + * The same mail, but sorted. + */ + protected function sortMail($mail) { + // Can't sort an empty array. + if (count($mail) === 0) { + return []; + } + + // To, subject and body keys must be present. + // Empty strings are ignored when matching so adding them is harmless. + foreach ($mail as $key => $row) { + if (!array_key_exists('to',$row)) { + $mail[$key]['to'] = ''; + } + if (!array_key_exists('subject',$row)) { + $mail[$key]['subject'] = ''; + } + if (!array_key_exists('body',$row)) { + $mail[$key]['body'] = ''; + } + } + + // Obtain a list of columns. + foreach ($mail as $key => $row) { + if (array_key_exists('to',$row)) { + $to[$key] = $row['to']; + } + if (array_key_exists('subject',$row)) { + $subject[$key] = $row['subject']; + } + if (array_key_exists('body',$row)) { + $body[$key] = $row['body']; + } + } + + // Add $mail as the last parameter, to sort by the common key. + array_multisort($to, SORT_ASC, $subject, SORT_ASC, $body, SORT_ASC, $mail); + return $mail; + } + + /** + * Get the mink context, so we can visit pages using the mink session. + */ + protected function getMinkContext() { + $minkContext = $this->getContext('\Behat\MinkExtension\Context\RawMinkContext'); + if ($minkContext === FALSE) { + throw new \Exception(sprintf('No mink context found.')); + } + return $minkContext; + } + +} From dc386dc0f51d901f6d819149b8743002f9082ce3 Mon Sep 17 00:00:00 2001 From: Jonathan Shaw Date: Thu, 20 Jul 2017 11:57:37 +0100 Subject: [PATCH 09/17] Improved mail step syntaxes --- features/mail.feature | 43 +++++++++--- .../DrupalExtension/Context/MailContext.php | 69 ++++++++++--------- .../Context/RawMailContext.php | 43 +++++++++--- 3 files changed, 103 insertions(+), 52 deletions(-) diff --git a/features/mail.feature b/features/mail.feature index dacacdfc..4890ee30 100644 --- a/features/mail.feature +++ b/features/mail.feature @@ -19,13 +19,26 @@ Feature: MailContext | jane | test | body 2 | When Drupal sends a mail: | to | jack@example.com | - | subject | test | + | subject | for jack | | body | test body with many words | Then new email is sent: - | to | subject | body | body | - | jack | test | test | many words | + | to | body | body | + | jack | test | many words | + And a mail has been sent to "jane@example.com" + And a mail has been sent to "jane@example.com": + | subject | + | test | + And an email has been sent with the subject "test" + And emails have been sent with the subject "test": + | to | + | fred | + | jane | + And a mail has been sent to "fred" with the subject "test" + And emails have been sent to "fred" with the subject "test": + | body | + | test body | - Scenario: Mail is sent to + Scenario: New mail is sent to someone When Drupal sends a mail: | to | fred@example.com | | subject | test 1 | @@ -35,13 +48,26 @@ Feature: MailContext Then new mail is sent to fred: | subject | | test 1 | - And mail has been sent to "jane@example.com": - | subject | - | test 2 | + Scenario: No mail is sent Then no mail has been sent + Scenario: Count sent mail + When Drupal sends an email: + | to | fred@example.com | + | subject | test | + And Drupal sends a mail: + | to | jane@example.com | + | subject | test | + And Drupal sends a mail: + | to | jane@example.com | + | subject | something else | + Then 2 new emails are sent with the subject "test" + And 1 mail has been sent to "jane" with the subject "something else" + And no new emails are sent + And no mail has been sent to "hans" + Scenario: I follow link in mail When Drupal sends a mail: | to | fred@example.com | @@ -49,8 +75,7 @@ Feature: MailContext | body | A link to Google: http://www.Google.com | And I follow the link to "google" from the mail with the subject "test link" Then I should see "Search" - - + Scenario: We try to be order insensitive When Drupal sends an email: | to | fred@example.com | diff --git a/src/Drupal/DrupalExtension/Context/MailContext.php b/src/Drupal/DrupalExtension/Context/MailContext.php index b7edf032..9275ad92 100644 --- a/src/Drupal/DrupalExtension/Context/MailContext.php +++ b/src/Drupal/DrupalExtension/Context/MailContext.php @@ -80,63 +80,61 @@ public function DrupalSendsMail(TableNode $fields) { /** * Check all mail sent during the scenario. * - * @Then (e)mail(s) has/have been sent: - * @Then (e)mail(s) has/have been sent to :to: + * @Then (a )(an )(e)mail(s) has/have been sent: + * @Then (a )(an )(e)mail(s) has/have been sent to :to: + * @Then (a )(an )(e)mail(s) has/have been sent with the subject :subject: + * @Then (a )(an )(e)mail(s) has/have been sent to :to with the subject :subject: */ - public function mailHasBeenSent(TableNode $expectedMailTable, $to = NULL) { + public function mailHasBeenSent(TableNode $expectedMailTable, $to = '', $subject = '') { $expectedMail = $expectedMailTable->getHash(); - $matches = []; - if (!is_null($to)) { - $matches = ['to' => $to]; - } - $actualMail = $this->getMail($matches); + $actualMail = $this->getMail(['to' => $to, 'subject' => $subject], FALSE); $this->compareMail($actualMail, $expectedMail); } /** * Check mail sent since the last step that checked mail. * - * @Then new (e)mail(s) is/are sent: - * @Then new (e)mail(s) is/are sent to :to: + * @Then (a )(an )new (e)mail(s) is/are sent: + * @Then (a )(an )new (e)mail(s) is/are sent to :to: + * @Then (a )(an )new (e)mail(s) is/are sent with the subject :subject: + * @Then (a )(an )new (e)mail(s) is/are sent to :to with the subject :subject: */ - public function newMailIsSent(TableNode $expectedMailTable, $to = NULL) { + public function newMailIsSent(TableNode $expectedMailTable, $to = '', $subject = '') { $expectedMail = $expectedMailTable->getHash(); - $matches = []; - if (!is_null($to)) { - $matches = ['to' => $to]; - } - $actualMail = $this->getMail($matches, TRUE); + $actualMail = $this->getMail(['to' => $to, 'subject' => $subject], TRUE); $this->compareMail($actualMail, $expectedMail); } /** * Check all mail sent during the scenario. * - * @Then no (e)mail(s) has/have been sent - * @Then no (e)mail(s) has/have been sent to :to + * @Then :count (e)mail(s) has/have been sent + * @Then :count (e)mail(s) has/have been sent to :to + * @Then :count (e)mail(s) has/have been sent with the subject :subject + * @Then :count (e)mail(s) has/have been sent to :to with the subject :subject */ - public function noMailHasBeenSent($to = NULL) { - $matches = []; - if (!is_null($to)) { - $matches = ['to' => $to]; - } - $actualMail = $this->getMail($matches); - $this->compareMail($actualMail, []); + public function noMailHasBeenSent($count, $to = '', $subject = '') { + $actualMail = $this->getMail(['to' => $to, 'subject' => $subject], FALSE); + $count = $count === 'no' ? 0 : $count; + $count = $count === 'a' ? NULL : $count; + $count = $count === 'an' ? NULL : $count; + $this->assertMailCount($actualMail, $count); } /** * Check mail sent since the last step that checked mail. * - * @Then no new (e)mail(s) is/are sent - * @Then no new (e)mail(s) is/are sent to :to + * @Then :count new (e)mail(s) is/are sent + * @Then :count new (e)mail(s) is/are sent to :to + * @Then :count new (e)mail(s) is/are sent with the subject :subject + * @Then :count new (e)mail(s) is/are sent to :to with the subject :subject */ - public function noNewMailIsSent($to = NULL) { - $matches = []; - if (!is_null($to)) { - $matches = ['to' => $to]; - } - $actualMail = $this->getMail($matches, TRUE); - $this->compareMail($actualMail, []); + public function noNewMailIsSent($count, $to = '', $subject = '') { + $actualMail = $this->getMail(['to' => $to, 'subject' => $subject], TRUE); + $count = $count === 'no' ? 0 : $count; + $count = $count === 'a' ? 1 : $count; + $count = $count === 'an' ? 1 : $count; + $this->assertMailCount($actualMail, $count); } /** @@ -149,6 +147,9 @@ public function followLinkInMail($urlFragment, $to = '', $subject = '') { // Get the mail $matches = ['to' => $to, 'subject' => $subject]; $mail = $this->getMail($matches, FALSE, -1); + if (count($mail) == 0) { + throw new \Exception('No such mail found.'); + } $body = $mail['body']; // Find web URLs in the mail diff --git a/src/Drupal/DrupalExtension/Context/RawMailContext.php b/src/Drupal/DrupalExtension/Context/RawMailContext.php index 2133a689..115d3bc9 100644 --- a/src/Drupal/DrupalExtension/Context/RawMailContext.php +++ b/src/Drupal/DrupalExtension/Context/RawMailContext.php @@ -128,16 +128,9 @@ protected function matchesMail($mail = [], $matches = []) { * An array of expected mail. */ protected function compareMail($actualMail, $expectedMail) { - // Make sure there is the same number of actual and expected - $actualCount = count($actualMail); + // Make sure there is the same number of actual and expected. $expectedCount = count($expectedMail); - if ($expectedCount !== $actualCount) { - $prettyActualMail = []; - foreach ($actualMail as $singleActualMail) { - $prettyActualMail[] = ['to' => $singleActualMail['to'],'subject' => $singleActualMail['subject'],]; - } - throw new \Exception(sprintf("%s mail expected, but %s found:\n\n%s", $expectedCount, $actualCount, print_r($prettyActualMail, TRUE))); - } + $this->assertMailCount($actualMail, $expectedCount); // For each row of expected mail, check the corresponding actual mail. // Make the comparison insensitive to the order mails were sent. @@ -155,6 +148,38 @@ protected function compareMail($actualMail, $expectedMail) { } } + /** + * Assert there is the expected number of mails, or that there are some mails + * if the exact number expected is not specified. + * + * @param array $actualMail + * An array of actual mail. + * @param int $expectedCount + * Optional. The number of mails expected. + */ + protected function assertMailCount($actualMail, $expectedCount = NULL) { + $actualCount = count($actualMail); + if (is_null($expectedCount)) { + // If number to expect is not specified, expect more than zero. + if ($actualCount === 0) { + throw new \Exception("Expected some mail, but none found."); + } + } + else { + if ($expectedCount != $actualCount) { + // Prepare a simple list of actual mail. + $prettyActualMail = []; + foreach ($actualMail as $singleActualMail) { + $prettyActualMail[] = [ + 'to' => $singleActualMail['to'], + 'subject' => $singleActualMail['subject'], + ]; + } + throw new \Exception(sprintf("Expected %s mail, but %s found:\n\n%s", $expectedCount, $actualCount, print_r($prettyActualMail, TRUE))); + } + } + } + /** * Sort mail by to, subject and body. * From 4ed55da379a1f905bceb2780942985098aa7f860 Mon Sep 17 00:00:00 2001 From: jonathanjfshaw Date: Thu, 27 Jul 2017 19:11:31 +0100 Subject: [PATCH 10/17] Alter @sendmail tags logic Generic scenario hooks run after tagged ones, and so were undoing their effect. The solution is to test for the presence of the tag in the generic hook. --- .../DrupalExtension/Context/MailContext.php | 33 +++++++++---------- 1 file changed, 15 insertions(+), 18 deletions(-) diff --git a/src/Drupal/DrupalExtension/Context/MailContext.php b/src/Drupal/DrupalExtension/Context/MailContext.php index 9275ad92..88ef26b7 100644 --- a/src/Drupal/DrupalExtension/Context/MailContext.php +++ b/src/Drupal/DrupalExtension/Context/MailContext.php @@ -9,17 +9,20 @@ */ class MailContext extends RawMailContext { - /** + /** * By default, prevent mail from being actually sent out during tests. * * @BeforeScenario */ - public function disableMail() { - $this->getMailManager()->disableMail(); - // Always reset mail count, in case the default mail manager is being used - // which enables mail collecting automatically when mail is disabled, making - //the use of the @mail tag optional in this case. - $this->mailCount = []; + public function disableMail($event) { + $tags = array_merge($event->getFeature()->getTags(), $event->getScenario()->getTags()); + if (!in_array('sendmail', $tags) && !!in_array('sendemail', $tags)) { + $this->getMailManager()->disableMail(); + // Always reset mail count, in case the default mail manager is being used + // which enables mail collecting automatically when mail is disabled, making + //the use of the @mail tag optional in this case. + $this->mailCount = []; + } } /** @@ -27,17 +30,11 @@ public function disableMail() { * * @AfterScenario */ - public function enableMail() { - $this->getMailManager()->enableMail(); - } - - /** - * Allow opting in to actually sending mail out. - * - * @BeforeScenario @sendmail @sendemail - */ - public function sendMail() { - $this->getMailManager()->enableMail(); + public function enableMail($event) { + $tags = array_merge($event->getFeature()->getTags(), $event->getScenario()->getTags()); + if (!in_array('sendmail', $tags) && !!in_array('sendemail', $tags)) { + $this->getMailManager()->enableMail(); + } } /** From e4efcedd21c4a26aa243b3a18a8e17f2348b4419 Mon Sep 17 00:00:00 2001 From: jonathanjfshaw Date: Fri, 28 Jul 2017 15:34:33 +0100 Subject: [PATCH 11/17] Remove double negative --- src/Drupal/DrupalExtension/Context/MailContext.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Drupal/DrupalExtension/Context/MailContext.php b/src/Drupal/DrupalExtension/Context/MailContext.php index 88ef26b7..247eea64 100644 --- a/src/Drupal/DrupalExtension/Context/MailContext.php +++ b/src/Drupal/DrupalExtension/Context/MailContext.php @@ -16,7 +16,7 @@ class MailContext extends RawMailContext { */ public function disableMail($event) { $tags = array_merge($event->getFeature()->getTags(), $event->getScenario()->getTags()); - if (!in_array('sendmail', $tags) && !!in_array('sendemail', $tags)) { + if (!in_array('sendmail', $tags) && !in_array('sendemail', $tags)) { $this->getMailManager()->disableMail(); // Always reset mail count, in case the default mail manager is being used // which enables mail collecting automatically when mail is disabled, making @@ -32,7 +32,7 @@ public function disableMail($event) { */ public function enableMail($event) { $tags = array_merge($event->getFeature()->getTags(), $event->getScenario()->getTags()); - if (!in_array('sendmail', $tags) && !!in_array('sendemail', $tags)) { + if (!in_array('sendmail', $tags) && !in_array('sendemail', $tags)) { $this->getMailManager()->enableMail(); } } From 9115fef72c280cbcf256fea13fa3c617935a5779 Mon Sep 17 00:00:00 2001 From: jonathanjfshaw Date: Sat, 20 Jan 2018 22:21:16 +0000 Subject: [PATCH 12/17] Better failure with unexpectedly missing emails --- src/Drupal/DrupalExtension/Context/RawMailContext.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Drupal/DrupalExtension/Context/RawMailContext.php b/src/Drupal/DrupalExtension/Context/RawMailContext.php index 115d3bc9..d5e778d9 100644 --- a/src/Drupal/DrupalExtension/Context/RawMailContext.php +++ b/src/Drupal/DrupalExtension/Context/RawMailContext.php @@ -71,7 +71,7 @@ protected function getMail($matches = [], $new = FALSE, $index = NULL, $store = })); // Return an individual mail if specified by an index. - if (is_null($index)) { + if (is_null($index) || count($mail) === 0) { return $mail; } else { From 5413e26c5e60e917e2d08c1ccf931b9ee79d8a65 Mon Sep 17 00:00:00 2001 From: jonathanjfshaw Date: Sun, 11 Feb 2018 12:37:29 +0000 Subject: [PATCH 13/17] Simplified handling of config in driver --- src/Drupal/DrupalMailManager.php | 31 +++---------------------------- 1 file changed, 3 insertions(+), 28 deletions(-) diff --git a/src/Drupal/DrupalMailManager.php b/src/Drupal/DrupalMailManager.php index 5bd6acdd..a865990f 100644 --- a/src/Drupal/DrupalMailManager.php +++ b/src/Drupal/DrupalMailManager.php @@ -19,41 +19,16 @@ class DrupalMailManager implements DrupalMailManagerInterface { * @var \Drupal\Driver\DriverInterface */ protected $driver; - - /** - * The name or config array of the initial mail backend. - * - * @var mixed - */ - protected $initialMailBackend; public function __construct(DriverInterface $driver) { $this->driver = $driver; } - /** - * Replace the initial mail backend with the test mail backend. - */ - protected function enableTestMailBackend() { - if (is_null($this->initialMailBackend)) { - $this->initialMailBackend = $this->driver->getMailBackend(); - } - // @todo Use a collector that supports html after D#2223967 lands. - $this->driver->setMailBackend('test_mail_collector'); - } - - /** - * Restore the initial mail backend. - */ - protected function restoreInitialMailBackend() { - $this->driver->setMailBackend($this->initialMailBackend); - } - /** * {@inheritdoc} */ public function startCollectingMail() { - $this->enableTestMailBackend(); + $this->driver->startCollectingMail(); $this->clearMail(); } @@ -61,14 +36,14 @@ public function startCollectingMail() { * {@inheritdoc} */ public function stopCollectingMail() { - $this->restoreInitialMailBackend(); + $this->driver->stopCollectingMail(); } /** * {@inheritdoc} */ public function enableMail() { - $this->restoreInitialMailBackend(); + $this->stopCollectingMail(); } /** From da77a5ad5d6eb80d1b7fa0d67eaeb3211f4c0d00 Mon Sep 17 00:00:00 2001 From: Jonathan Hedstrom Date: Thu, 15 Mar 2018 15:51:49 -0700 Subject: [PATCH 14/17] Coding standards for #195. --- .../DrupalExtension/Context/MailContext.php | 177 ++++++------ .../Context/RawMailContext.php | 267 +++++++++--------- src/Drupal/DrupalMailManager.php | 59 ++-- src/Drupal/DrupalMailManagerInterface.php | 16 +- 4 files changed, 270 insertions(+), 249 deletions(-) diff --git a/src/Drupal/DrupalExtension/Context/MailContext.php b/src/Drupal/DrupalExtension/Context/MailContext.php index 247eea64..74994d27 100644 --- a/src/Drupal/DrupalExtension/Context/MailContext.php +++ b/src/Drupal/DrupalExtension/Context/MailContext.php @@ -7,100 +7,108 @@ /** * Provides pre-built step definitions for interacting with mail. */ -class MailContext extends RawMailContext { +class MailContext extends RawMailContext +{ /** * By default, prevent mail from being actually sent out during tests. * * @BeforeScenario */ - public function disableMail($event) { - $tags = array_merge($event->getFeature()->getTags(), $event->getScenario()->getTags()); - if (!in_array('sendmail', $tags) && !in_array('sendemail', $tags)) { - $this->getMailManager()->disableMail(); - // Always reset mail count, in case the default mail manager is being used - // which enables mail collecting automatically when mail is disabled, making - //the use of the @mail tag optional in this case. - $this->mailCount = []; + public function disableMail($event) + { + $tags = array_merge($event->getFeature()->getTags(), $event->getScenario()->getTags()); + if (!in_array('sendmail', $tags) && !in_array('sendemail', $tags)) { + $this->getMailManager()->disableMail(); + // Always reset mail count, in case the default mail manager is being used + // which enables mail collecting automatically when mail is disabled, making + //the use of the @mail tag optional in this case. + $this->mailCount = []; + } } - } /** * Restore mail sending. * * @AfterScenario */ - public function enableMail($event) { - $tags = array_merge($event->getFeature()->getTags(), $event->getScenario()->getTags()); - if (!in_array('sendmail', $tags) && !in_array('sendemail', $tags)) { - $this->getMailManager()->enableMail(); + public function enableMail($event) + { + $tags = array_merge($event->getFeature()->getTags(), $event->getScenario()->getTags()); + if (!in_array('sendmail', $tags) && !in_array('sendemail', $tags)) { + $this->getMailManager()->enableMail(); + } } - } /** - * Allow opting in to mail collection. When using the default mail manager + * Allow opting in to mail collection. When using the default mail manager * service, it is not necessary to use this tag. * * @BeforeScenario @mail @email */ - public function collectMail() { - $this->getMailManager()->startCollectingMail(); - } + public function collectMail() + { + $this->getMailManager()->startCollectingMail(); + } /** * Stop collecting mail at scenario end. * * @AfterScenario @mail @email */ - public function stopCollectingMail() { - $this->getMailManager()->stopCollectingMail(); - } + public function stopCollectingMail() + { + $this->getMailManager()->stopCollectingMail(); + } /** * This is mainly useful for testing this context. - * + * * @When Drupal sends a/an (e)mail: */ - public function DrupalSendsMail(TableNode $fields) { - $mail = [ - 'body' => $this->getRandom()->name(255), - 'subject' => $this->getRandom()->name(20), - 'to' => $this->getRandom()->name(10) . '@anonexample.com', - 'langcode' => '', - ]; - foreach ($fields->getRowsHash() as $field => $value) { - $mail[$field] = $value; + public function drupalSendsMail(TableNode $fields) + { + $mail = [ + 'body' => $this->getRandom()->name(255), + 'subject' => $this->getRandom()->name(20), + 'to' => $this->getRandom()->name(10) . '@anonexample.com', + 'langcode' => '', + ]; + foreach ($fields->getRowsHash() as $field => $value) { + $mail[$field] = $value; + } + $this->getDriver()->sendMail($mail['body'], $mail['subject'], $mail['to'], $mail['langcode']); } - $this->getDriver()->sendMail($mail['body'], $mail['subject'], $mail['to'], $mail['langcode']); - } /** * Check all mail sent during the scenario. - * + * * @Then (a )(an )(e)mail(s) has/have been sent: * @Then (a )(an )(e)mail(s) has/have been sent to :to: * @Then (a )(an )(e)mail(s) has/have been sent with the subject :subject: * @Then (a )(an )(e)mail(s) has/have been sent to :to with the subject :subject: */ - public function mailHasBeenSent(TableNode $expectedMailTable, $to = '', $subject = '') { - $expectedMail = $expectedMailTable->getHash(); - $actualMail = $this->getMail(['to' => $to, 'subject' => $subject], FALSE); - $this->compareMail($actualMail, $expectedMail); - } + public function mailHasBeenSent(TableNode $expectedMailTable, $to = '', $subject = '') + { + $expectedMail = $expectedMailTable->getHash(); + $actualMail = $this->getMail(['to' => $to, 'subject' => $subject], false); + $this->compareMail($actualMail, $expectedMail); + } /** * Check mail sent since the last step that checked mail. - * + * * @Then (a )(an )new (e)mail(s) is/are sent: * @Then (a )(an )new (e)mail(s) is/are sent to :to: * @Then (a )(an )new (e)mail(s) is/are sent with the subject :subject: * @Then (a )(an )new (e)mail(s) is/are sent to :to with the subject :subject: */ - public function newMailIsSent(TableNode $expectedMailTable, $to = '', $subject = '') { - $expectedMail = $expectedMailTable->getHash(); - $actualMail = $this->getMail(['to' => $to, 'subject' => $subject], TRUE); - $this->compareMail($actualMail, $expectedMail); - } + public function newMailIsSent(TableNode $expectedMailTable, $to = '', $subject = '') + { + $expectedMail = $expectedMailTable->getHash(); + $actualMail = $this->getMail(['to' => $to, 'subject' => $subject], true); + $this->compareMail($actualMail, $expectedMail); + } /** * Check all mail sent during the scenario. @@ -110,13 +118,14 @@ public function newMailIsSent(TableNode $expectedMailTable, $to = '', $subject = * @Then :count (e)mail(s) has/have been sent with the subject :subject * @Then :count (e)mail(s) has/have been sent to :to with the subject :subject */ - public function noMailHasBeenSent($count, $to = '', $subject = '') { - $actualMail = $this->getMail(['to' => $to, 'subject' => $subject], FALSE); - $count = $count === 'no' ? 0 : $count; - $count = $count === 'a' ? NULL : $count; - $count = $count === 'an' ? NULL : $count; - $this->assertMailCount($actualMail, $count); - } + public function noMailHasBeenSent($count, $to = '', $subject = '') + { + $actualMail = $this->getMail(['to' => $to, 'subject' => $subject], false); + $count = $count === 'no' ? 0 : $count; + $count = $count === 'a' ? null : $count; + $count = $count === 'an' ? null : $count; + $this->assertMailCount($actualMail, $count); + } /** * Check mail sent since the last step that checked mail. @@ -126,13 +135,14 @@ public function noMailHasBeenSent($count, $to = '', $subject = '') { * @Then :count new (e)mail(s) is/are sent with the subject :subject * @Then :count new (e)mail(s) is/are sent to :to with the subject :subject */ - public function noNewMailIsSent($count, $to = '', $subject = '') { - $actualMail = $this->getMail(['to' => $to, 'subject' => $subject], TRUE); - $count = $count === 'no' ? 0 : $count; - $count = $count === 'a' ? 1 : $count; - $count = $count === 'an' ? 1 : $count; - $this->assertMailCount($actualMail, $count); - } + public function noNewMailIsSent($count, $to = '', $subject = '') + { + $actualMail = $this->getMail(['to' => $to, 'subject' => $subject], true); + $count = $count === 'no' ? 0 : $count; + $count = $count === 'a' ? 1 : $count; + $count = $count === 'an' ? 1 : $count; + $this->assertMailCount($actualMail, $count); + } /** * @When I follow the link to :urlFragment from the (e)mail @@ -140,31 +150,30 @@ public function noNewMailIsSent($count, $to = '', $subject = '') { * @When I follow the link to :urlFragment from the (e)mail with the subject :subject * @When I follow the link to :urlFragment from the (e)mail to :to with the subject :subject */ - public function followLinkInMail($urlFragment, $to = '', $subject = '') { - // Get the mail - $matches = ['to' => $to, 'subject' => $subject]; - $mail = $this->getMail($matches, FALSE, -1); - if (count($mail) == 0) { - throw new \Exception('No such mail found.'); - } - $body = $mail['body']; + public function followLinkInMail($urlFragment, $to = '', $subject = '') + { + // Get the mail + $matches = ['to' => $to, 'subject' => $subject]; + $mail = $this->getMail($matches, false, -1); + if (count($mail) == 0) { + throw new \Exception('No such mail found.'); + } + $body = $mail['body']; - // Find web URLs in the mail - $urlPattern = '`.*?((http|https)://[\w#$&+,\/:;=?@.-]+)[^\w#$&+,\/:;=?@.-]*?`i'; - if (preg_match_all($urlPattern, $body, $urls)) { - // Visit the first url that contains the desired fragment. - foreach ($urls[1] as $url) { - $match = (strpos(strtolower($url), strtolower($urlFragment)) !== FALSE); - if ($match) { - $this->getMinkContext()->visitPath($url); - return; + // Find web URLs in the mail + $urlPattern = '`.*?((http|https)://[\w#$&+,\/:;=?@.-]+)[^\w#$&+,\/:;=?@.-]*?`i'; + if (preg_match_all($urlPattern, $body, $urls)) { + // Visit the first url that contains the desired fragment. + foreach ($urls[1] as $url) { + $match = (strpos(strtolower($url), strtolower($urlFragment)) !== false); + if ($match) { + $this->getMinkContext()->visitPath($url); + return; + } + } + throw new \Exception(sprintf('No URL in mail body contained "%s".', $urlFragment)); + } else { + throw new \Exception('No URL found in mail body.'); } - } - throw new \Exception(sprintf('No URL in mail body contained "%s".', $urlFragment)); } - else { - throw new \Exception('No URL found in mail body.'); - } - } - } diff --git a/src/Drupal/DrupalExtension/Context/RawMailContext.php b/src/Drupal/DrupalExtension/Context/RawMailContext.php index d5e778d9..366de2c7 100644 --- a/src/Drupal/DrupalExtension/Context/RawMailContext.php +++ b/src/Drupal/DrupalExtension/Context/RawMailContext.php @@ -7,36 +7,38 @@ /** * Provides helper methods for interacting with mail. */ -class RawMailContext extends RawDrupalContext { +class RawMailContext extends RawDrupalContext +{ /** * The mail manager. * * @var \Drupal\DrupalMailManagerInterface */ - protected $mailManager; + protected $mailManager; /** * The number of mails received so far in this scenario, for each mail store. * * @var array */ - protected $mailCount = []; + protected $mailCount = []; /** * Get the mail manager service that handles stored test mail. - * + * * @return \Drupal\DrupalMailManagerInterface * The mail manager service. */ - protected function getMailManager() { - // Persist the mail manager between invocations. This is necessary for - // remembering and reinstating the original mail backend. - if (is_null($this->mailManager)) { - $this->mailManager = new DrupalMailManager($this->getDriver()); + protected function getMailManager() + { + // Persist the mail manager between invocations. This is necessary for + // remembering and reinstating the original mail backend. + if (is_null($this->mailManager)) { + $this->mailManager = new DrupalMailManager($this->getDriver()); + } + return $this->mailManager; } - return $this->mailManager; - } /** * Get collected mail, matching certain specifications. @@ -54,30 +56,30 @@ protected function getMailManager() { * An array of mail, each formatted as a Drupal 8 * \Drupal\Core\Mail\MailInterface::mail $message array. */ - protected function getMail($matches = [], $new = FALSE, $index = NULL, $store = 'default') { - $mail = $this->getMailManager()->getMail($store); - $previousMailCount = $this->getMailCount($store); - $this->mailCount[$store] = count($mail); - - // Ignore previously seen mail. - if ($new) { - $mail = array_slice($mail, $previousMailCount); - } - - // Filter mail based on $matches; keep only mail where each field mentioned - // in $matches contains the value specified for that field. - $mail = array_values(array_filter($mail, function ($singleMail) use ($matches) { - return ($this->matchesMail($singleMail, $matches)); - })); + protected function getMail($matches = [], $new = false, $index = null, $store = 'default') + { + $mail = $this->getMailManager()->getMail($store); + $previousMailCount = $this->getMailCount($store); + $this->mailCount[$store] = count($mail); + + // Ignore previously seen mail. + if ($new) { + $mail = array_slice($mail, $previousMailCount); + } - // Return an individual mail if specified by an index. - if (is_null($index) || count($mail) === 0) { - return $mail; - } - else { - return array_slice($mail, $index, 1)[0]; + // Filter mail based on $matches; keep only mail where each field mentioned + // in $matches contains the value specified for that field. + $mail = array_values(array_filter($mail, function ($singleMail) use ($matches) { + return ($this->matchesMail($singleMail, $matches)); + })); + + // Return an individual mail if specified by an index. + if (is_null($index) || count($mail) === 0) { + return $mail; + } else { + return array_slice($mail, $index, 1)[0]; + } } - } /** * Get the number of mails received in a particular mail store. @@ -85,15 +87,15 @@ protected function getMail($matches = [], $new = FALSE, $index = NULL, $store = * @return int * The number of mails received during this scenario. */ - protected function getMailCount($store) { - if (array_key_exists($store, $this->mailCount)) { - $count = $this->mailCount[$store]; - } - else { - $count = 0; + protected function getMailCount($store) + { + if (array_key_exists($store, $this->mailCount)) { + $count = $this->mailCount[$store]; + } else { + $count = 0; + } + return $count; } - return $count; - } /** * Determine if a mail meets criteria. @@ -106,18 +108,19 @@ protected function getMailCount($store) { * @return bool * Whether the mail matches the criteria. */ - protected function matchesMail($mail = [], $matches = []) { - // Discard criteria that are just zero-length strings. - $matches = array_filter($matches, 'strlen'); - // For each criteria, check the specified mail field contains the value. - foreach($matches as $field => $value) { - // Case insensitive. - if (stripos($mail[$field], $value) === FALSE) { - return FALSE; - } + protected function matchesMail($mail = [], $matches = []) + { + // Discard criteria that are just zero-length strings. + $matches = array_filter($matches, 'strlen'); + // For each criteria, check the specified mail field contains the value. + foreach ($matches as $field => $value) { + // Case insensitive. + if (stripos($mail[$field], $value) === false) { + return false; + } + } + return true; } - return TRUE; - } /** * Compare actual mail with expected mail. @@ -127,26 +130,27 @@ protected function matchesMail($mail = [], $matches = []) { * @param array $expectedMail * An array of expected mail. */ - protected function compareMail($actualMail, $expectedMail) { - // Make sure there is the same number of actual and expected. - $expectedCount = count($expectedMail); - $this->assertMailCount($actualMail, $expectedCount); - - // For each row of expected mail, check the corresponding actual mail. - // Make the comparison insensitive to the order mails were sent. - $actualMail = $this->sortMail($actualMail); - $expectedMail = $this->sortMail($expectedMail); - foreach ($expectedMail as $index => $expectedMailItem) { - // For each column of the expected, check the field of the actual mail. - foreach ($expectedMailItem as $fieldName => $fieldValue) { - $expectedField = [$fieldName => $fieldValue]; - $match = $this->matchesMail($actualMail[$index], $expectedField); - if (!$match) { - throw new \Exception(sprintf("The #%s mail did not have '%s' in its %s field. It had:\n'%s'", $index, $fieldValue, $fieldName, mb_strimwidth($actualMail[$index][$fieldName],0, 30, "..."))); + protected function compareMail($actualMail, $expectedMail) + { + // Make sure there is the same number of actual and expected. + $expectedCount = count($expectedMail); + $this->assertMailCount($actualMail, $expectedCount); + + // For each row of expected mail, check the corresponding actual mail. + // Make the comparison insensitive to the order mails were sent. + $actualMail = $this->sortMail($actualMail); + $expectedMail = $this->sortMail($expectedMail); + foreach ($expectedMail as $index => $expectedMailItem) { + // For each column of the expected, check the field of the actual mail. + foreach ($expectedMailItem as $fieldName => $fieldValue) { + $expectedField = [$fieldName => $fieldValue]; + $match = $this->matchesMail($actualMail[$index], $expectedField); + if (!$match) { + throw new \Exception(sprintf("The #%s mail did not have '%s' in its %s field. It had:\n'%s'", $index, $fieldValue, $fieldName, mb_strimwidth($actualMail[$index][$fieldName], 0, 30, "..."))); + } + } } - } } - } /** * Assert there is the expected number of mails, or that there are some mails @@ -157,28 +161,28 @@ protected function compareMail($actualMail, $expectedMail) { * @param int $expectedCount * Optional. The number of mails expected. */ - protected function assertMailCount($actualMail, $expectedCount = NULL) { - $actualCount = count($actualMail); - if (is_null($expectedCount)) { - // If number to expect is not specified, expect more than zero. - if ($actualCount === 0) { - throw new \Exception("Expected some mail, but none found."); - } - } - else { - if ($expectedCount != $actualCount) { - // Prepare a simple list of actual mail. - $prettyActualMail = []; - foreach ($actualMail as $singleActualMail) { - $prettyActualMail[] = [ - 'to' => $singleActualMail['to'], - 'subject' => $singleActualMail['subject'], - ]; + protected function assertMailCount($actualMail, $expectedCount = null) + { + $actualCount = count($actualMail); + if (is_null($expectedCount)) { + // If number to expect is not specified, expect more than zero. + if ($actualCount === 0) { + throw new \Exception("Expected some mail, but none found."); + } + } else { + if ($expectedCount != $actualCount) { + // Prepare a simple list of actual mail. + $prettyActualMail = []; + foreach ($actualMail as $singleActualMail) { + $prettyActualMail[] = [ + 'to' => $singleActualMail['to'], + 'subject' => $singleActualMail['subject'], + ]; + } + throw new \Exception(sprintf("Expected %s mail, but %s found:\n\n%s", $expectedCount, $actualCount, print_r($prettyActualMail, true))); + } } - throw new \Exception(sprintf("Expected %s mail, but %s found:\n\n%s", $expectedCount, $actualCount, print_r($prettyActualMail, TRUE))); - } } - } /** * Sort mail by to, subject and body. @@ -189,53 +193,54 @@ protected function assertMailCount($actualMail, $expectedCount = NULL) { * @return array * The same mail, but sorted. */ - protected function sortMail($mail) { - // Can't sort an empty array. - if (count($mail) === 0) { - return []; - } + protected function sortMail($mail) + { + // Can't sort an empty array. + if (count($mail) === 0) { + return []; + } - // To, subject and body keys must be present. - // Empty strings are ignored when matching so adding them is harmless. - foreach ($mail as $key => $row) { - if (!array_key_exists('to',$row)) { - $mail[$key]['to'] = ''; - } - if (!array_key_exists('subject',$row)) { - $mail[$key]['subject'] = ''; - } - if (!array_key_exists('body',$row)) { - $mail[$key]['body'] = ''; - } - } + // To, subject and body keys must be present. + // Empty strings are ignored when matching so adding them is harmless. + foreach ($mail as $key => $row) { + if (!array_key_exists('to', $row)) { + $mail[$key]['to'] = ''; + } + if (!array_key_exists('subject', $row)) { + $mail[$key]['subject'] = ''; + } + if (!array_key_exists('body', $row)) { + $mail[$key]['body'] = ''; + } + } - // Obtain a list of columns. - foreach ($mail as $key => $row) { - if (array_key_exists('to',$row)) { - $to[$key] = $row['to']; - } - if (array_key_exists('subject',$row)) { - $subject[$key] = $row['subject']; - } - if (array_key_exists('body',$row)) { - $body[$key] = $row['body']; - } - } + // Obtain a list of columns. + foreach ($mail as $key => $row) { + if (array_key_exists('to', $row)) { + $to[$key] = $row['to']; + } + if (array_key_exists('subject', $row)) { + $subject[$key] = $row['subject']; + } + if (array_key_exists('body', $row)) { + $body[$key] = $row['body']; + } + } - // Add $mail as the last parameter, to sort by the common key. - array_multisort($to, SORT_ASC, $subject, SORT_ASC, $body, SORT_ASC, $mail); - return $mail; - } + // Add $mail as the last parameter, to sort by the common key. + array_multisort($to, SORT_ASC, $subject, SORT_ASC, $body, SORT_ASC, $mail); + return $mail; + } /** * Get the mink context, so we can visit pages using the mink session. */ - protected function getMinkContext() { - $minkContext = $this->getContext('\Behat\MinkExtension\Context\RawMinkContext'); - if ($minkContext === FALSE) { - throw new \Exception(sprintf('No mink context found.')); + protected function getMinkContext() + { + $minkContext = $this->getContext('\Behat\MinkExtension\Context\RawMinkContext'); + if ($minkContext === false) { + throw new \Exception(sprintf('No mink context found.')); + } + return $minkContext; } - return $minkContext; - } - } diff --git a/src/Drupal/DrupalMailManager.php b/src/Drupal/DrupalMailManager.php index a865990f..5eeddca9 100644 --- a/src/Drupal/DrupalMailManager.php +++ b/src/Drupal/DrupalMailManager.php @@ -6,65 +6,72 @@ /** * Default implementation of the Drupal mail manager service. - * + * * This uses Drupal core's test_mail_collector mail backend, which both * collects outbound mail and prevents it from being sent. Therefore using * this implementation, mail is collected if and only if sending is disabled. */ -class DrupalMailManager implements DrupalMailManagerInterface { +class DrupalMailManager implements DrupalMailManagerInterface +{ /** * The active Drupal driver. * * @var \Drupal\Driver\DriverInterface */ - protected $driver; + protected $driver; - public function __construct(DriverInterface $driver) { - $this->driver = $driver; - } + public function __construct(DriverInterface $driver) + { + $this->driver = $driver; + } /** * {@inheritdoc} */ - public function startCollectingMail() { - $this->driver->startCollectingMail(); - $this->clearMail(); - } + public function startCollectingMail() + { + $this->driver->startCollectingMail(); + $this->clearMail(); + } /** * {@inheritdoc} */ - public function stopCollectingMail() { - $this->driver->stopCollectingMail(); - } + public function stopCollectingMail() + { + $this->driver->stopCollectingMail(); + } /** * {@inheritdoc} */ - public function enableMail() { - $this->stopCollectingMail(); - } + public function enableMail() + { + $this->stopCollectingMail(); + } /** * {@inheritdoc} */ - public function disableMail() { - $this->startCollectingMail(); - } + public function disableMail() + { + $this->startCollectingMail(); + } /** * {@inheritdoc} */ - public function getMail($store = 'default') { - return $this->driver->getMail(); - } + public function getMail($store = 'default') + { + return $this->driver->getMail(); + } /** * {@inheritdoc} */ - public function clearMail($store = 'default') { - $this->driver->clearMail(); - } - + public function clearMail($store = 'default') + { + $this->driver->clearMail(); + } } diff --git a/src/Drupal/DrupalMailManagerInterface.php b/src/Drupal/DrupalMailManagerInterface.php index d9ac7d27..b5bc4ee3 100644 --- a/src/Drupal/DrupalMailManagerInterface.php +++ b/src/Drupal/DrupalMailManagerInterface.php @@ -5,27 +5,28 @@ /** * Interface for classes that manage mail created during tests. */ -interface DrupalMailManagerInterface { +interface DrupalMailManagerInterface +{ /** * Collect outbound mail for analysis. */ - public function startCollectingMail(); + public function startCollectingMail(); /** * Stop collecting outbound mail. */ - public function stopCollectingMail(); + public function stopCollectingMail(); /** * Allow mail to be actually sent out. */ - public function enableMail(); + public function enableMail(); /** * Prevent mail from being actually sent out. */ - public function disableMail(); + public function disableMail(); /** * Get all collected mail. @@ -37,11 +38,10 @@ public function disableMail(); * An array of collected emails, each formatted as a Drupal 8 * \Drupal\Core\Mail\MailInterface::mail $message array. */ - public function getMail($store); + public function getMail($store); /** * Empty the store of collected mail. */ - public function clearMail($store); - + public function clearMail($store); } From c2906a2f85a621c069efc94246055c3f7aaf7514 Mon Sep 17 00:00:00 2001 From: Jonathan Hedstrom Date: Thu, 15 Mar 2018 15:53:52 -0700 Subject: [PATCH 15/17] Temporarily test on corresponding DrupalDriver branch. --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 51ac4210..1153d86e 100644 --- a/composer.json +++ b/composer.json @@ -27,7 +27,7 @@ "behat/mink-extension": "~2.0", "behat/mink-goutte-driver": "~1.0", "behat/mink-selenium2-driver": "~1.1", - "drupal/drupal-driver": "^1.3.2", + "drupal/drupal-driver": "dev-reroll-email-support", "symfony/dependency-injection": "~2.7|~3.0", "symfony/event-dispatcher": "~2.7|~3.0" }, From 7684c3f2aab16c1b5329f34225f1c2b2a073f175 Mon Sep 17 00:00:00 2001 From: Jonathan Hedstrom Date: Mon, 19 Mar 2018 11:05:03 -0700 Subject: [PATCH 16/17] Temporary testing on DrupalDriver master. --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 13214348..adc3441f 100644 --- a/composer.json +++ b/composer.json @@ -27,7 +27,7 @@ "behat/mink-extension": "~2.0", "behat/mink-goutte-driver": "~1.0", "behat/mink-selenium2-driver": "~1.1", - "drupal/drupal-driver": "dev-reroll-email-support", + "drupal/drupal-driver": "dev-master", "symfony/dependency-injection": "~3.0", "symfony/event-dispatcher": "~3.0" }, From 016a454cd23c489daa7199d134c40cbb5b62aba2 Mon Sep 17 00:00:00 2001 From: Jonathan Hedstrom Date: Mon, 19 Mar 2018 12:49:47 -0700 Subject: [PATCH 17/17] Prep for 4.0.0 alpha4. --- CHANGELOG.md | 18 ++++++++++++++---- composer.json | 2 +- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a7035403..919f45ee 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,8 +6,17 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to [Semantic Versioning](http://semver.org/). ## [Unreleased] +## [4.0.0 alpha4] 2018-03-19 +### Added + * [#195](https://github.com/jhedstrom/drupalextension/issues/195): Support for testing emails sent from Drupal. + * [#477](https://github.com/jhedstrom/drupalextension/pull/477): Adds a `composer test` script. +### Changed + * Switches from `1.x` to `2.x` version of the DrupalDriver. This was necessary for the email testing in + [#195](https://github.com/jhedstrom/drupalextension/issues/195). +### Fixed + * [#464](https://github.com/jhedstrom/drupalextension/pull/464): Remove outdated `sudo` installation instructions. -## [4.0.0 alpha3] +## [4.0.0 alpha3] 2018-02-26 ### Added * [#467](https://github.com/jhedstrom/drupalextension/pull/467): Allows AJAX timeout to be overridden via an `ajax_timeout` parameter in `behat.yml`. @@ -23,14 +32,14 @@ and this project adheres to [Semantic Versioning](http://semver.org/). * [#462](https://github.com/jhedstrom/drupalextension/pull/462): The version of DrupalDriver is now pinned to a stable release instead of `dev-master`. -## [4.0.0 alpha2] +## [4.0.0 alpha2] 2017-12-14 ### Added * [#448](https://github.com/jhedstrom/drupalextension/pull/448): Additional use of the fast logout method. ### Fixed * [#447](https://github.com/jhedstrom/drupalextension/pull/447): Only reset session if it is started during fast logout. -## [4.0.0 alpha1] +## [4.0.0 alpha1] 2017-12-06 ### Added * [#268](https://github.com/jhedstrom/drupalextension/pull/268): Added a `BatchContext` for testing queue-runners and other batch-related functionality. @@ -60,7 +69,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/). * [#437](https://github.com/jhedstrom/drupalextension/pull/437): Radio button selector fix. * [#439](https://github.com/jhedstrom/drupalextension/pull/439): Symfony 3 compatibility follow-up fix. -[Unreleased]: https://github.com/jhedstrom/drupalextension/compare/v4.0.0alpha2...HEAD +[Unreleased]: https://github.com/jhedstrom/drupalextension/compare/v4.0.0alpha4...HEAD +[4.0.0 alpha4]:https://github.com/jhedstrom/drupalextension/compare/v4.0.0alpha3...v4.0.0alpha4 [4.0.0 alpha3]:https://github.com/jhedstrom/drupalextension/compare/v4.0.0alpha2...v4.0.0alpha3 [4.0.0 alpha2]:https://github.com/jhedstrom/drupalextension/compare/v4.0.0alpha1...v4.0.0alpha2 [4.0.0 alpha1]:https://github.com/jhedstrom/drupalextension/compare/v3.4.0...v4.0.0alpha1 diff --git a/composer.json b/composer.json index adc3441f..734f9465 100644 --- a/composer.json +++ b/composer.json @@ -27,7 +27,7 @@ "behat/mink-extension": "~2.0", "behat/mink-goutte-driver": "~1.0", "behat/mink-selenium2-driver": "~1.1", - "drupal/drupal-driver": "dev-master", + "drupal/drupal-driver": "^2.0.0", "symfony/dependency-injection": "~3.0", "symfony/event-dispatcher": "~3.0" },