diff --git a/README.md b/README.md
index 9f1787d..c819257 100644
--- a/README.md
+++ b/README.md
@@ -246,6 +246,36 @@ For `Second link`, this returns
* Param $label
+### grabUrlForLinkFromEmail
+
+Returns the `href` of the first link whose text exactly matches the label in
+the given email's HTML body. Matching is case-sensitive. Nested tags and HTML
+entities are decoded. Whitespace in the label is preserved.
+
+Fails if the email has no HTML body or no matching link with an `href`.
+
+Example:
+
+```php
+$email = $I->lastMessageTo('user@example.com');
+$url = $I->grabUrlForLinkFromEmail($email, 'Second link');
+```
+
+To use the last email:
+
+```php
+$url = $I->grabUrlForLinkFromEmail($I->lastMessage(), 'Second link');
+```
+
+For `Second link`, this returns
+`http://second-link.com`.
+
+Use an `Email` object from `lastMessage()`, `lastMessageTo()`, or
+`lastMessageFrom()`, or one you created directly.
+
+* Param $email
+* Param $label
+
### lastMessageFrom
Grab the full email object sent to an address.
diff --git a/src/Module/MailCatcher.php b/src/Module/MailCatcher.php
index bc22c51..eaf612d 100644
--- a/src/Module/MailCatcher.php
+++ b/src/Module/MailCatcher.php
@@ -392,7 +392,16 @@ public function grabUrlsFromNthEmail(int $nth): array
*/
public function grabUrlForLinkFromLastEmail(string $label): string
{
- $message = Message::from($this->lastMessage()->getSource(), false);
+ return $this->grabUrlForLinkFromEmail($this->lastMessage(), $label);
+ }
+
+ /**
+ * Return the URL of the first link with an exact, case-sensitive label
+ * in the given email's HTML body. Fail if no matching link has an href.
+ */
+ public function grabUrlForLinkFromEmail(Email $email, string $label): string
+ {
+ $message = Message::from($email->getSource(), false);
$html = $message->getHtmlContent();
if ($html !== null && $html !== '') {
diff --git a/tests/unit/Module/MailCatcherTest.php b/tests/unit/Module/MailCatcherTest.php
index 0f720fd..8655f02 100644
--- a/tests/unit/Module/MailCatcherTest.php
+++ b/tests/unit/Module/MailCatcherTest.php
@@ -222,19 +222,48 @@ public function testGrabUrlForLinkFromLastEmail()
$this->assertSame('http://second-link.com', $mailcatcher->grabUrlForLinkFromLastEmail('Second link'));
}
+ public function testGrabUrlForLinkFromEmail()
+ {
+ $mailcatcher = new MailCatcherTest_TestClass();
+ $email = new Email(1, [], '',
+ "Content-Type: text/html; charset=UTF-8\r\n\r\n"
+ . 'First link'
+ . 'Second link'
+ );
+
+ $this->assertSame('http://second-link.com', $mailcatcher->grabUrlForLinkFromEmail($email, 'Second link'));
+ }
+
+ public function testGrabUrlForLinkFromGivenEmail()
+ {
+ $mailcatcher = new MailCatcherTest_TestClass();
+ $mailcatcher->setLastMessage(new Email(2, [], '',
+ "Content-Type: text/html; charset=UTF-8\r\n\r\n"
+ . 'Reset password'
+ ));
+ $email = new Email(1, ['user@example.com'], '',
+ "Content-Type: text/html; charset=UTF-8\r\n\r\n"
+ . 'Reset password'
+ );
+
+ $this->assertSame('https://example.com/selected', $mailcatcher->grabUrlForLinkFromEmail($email, 'Reset password'));
+ }
+
public function testGrabUrlForLinkFromEncodedHtmlEmail()
{
$html = 'Café & "tea"'
. 'Café & "tea"';
$mailcatcher = new MailCatcherTest_TestClass();
- $mailcatcher->setLastMessage(new Email(1, [], '',
+ $email = new Email(1, [], '',
"MIME-Version: 1.0\r\nContent-Type: multipart/alternative; boundary=parts\r\n\r\n"
. "--parts\r\nContent-Type: text/plain; charset=UTF-8\r\n\r\nPlain text\r\n"
. "--parts\r\nContent-Type: text/html; charset=UTF-8\r\n"
. "Content-Transfer-Encoding: quoted-printable\r\n\r\n"
. quoted_printable_encode($html) . "\r\n--parts--\r\n"
- ));
+ );
+ $this->assertSame('https://example.com/first?one=1&two=2', $mailcatcher->grabUrlForLinkFromEmail($email, 'Café & "tea"'));
+ $mailcatcher->setLastMessage($email);
$this->assertSame('https://example.com/first?one=1&two=2', $mailcatcher->grabUrlForLinkFromLastEmail('Café & "tea"'));
}
@@ -252,6 +281,20 @@ public function testGrabUrlForLinkFromLastEmailFailsWhenNoLinkMatches(string $so
$mailcatcher->grabUrlForLinkFromLastEmail('Second link');
}
+ /**
+ * @dataProvider missingLinkEmails
+ */
+ public function testGrabUrlForLinkFromEmailFailsWhenNoLinkMatches(string $source)
+ {
+ $mailcatcher = new MailCatcherTest_TestClass();
+ $email = new Email(1, [], '', $source);
+
+ $this->expectException(AssertionFailedError::class);
+ $this->expectExceptionMessage('No link found with label: Second link');
+
+ $mailcatcher->grabUrlForLinkFromEmail($email, 'Second link');
+ }
+
public static function missingLinkEmails(): array
{
return [